diff --git a/.gitignore b/.gitignore index c74a54d71e..14243deb9e 100644 --- a/.gitignore +++ b/.gitignore @@ -233,3 +233,4 @@ pip-log.txt #Mr Developer .mr.developer.cfg MediaBrowser.WebDashboard/dashboard-ui/.idea/ +/.vs diff --git a/BDInfo/BDInfo.csproj b/BDInfo/BDInfo.csproj index 97abe74846..f38fc81018 100644 --- a/BDInfo/BDInfo.csproj +++ b/BDInfo/BDInfo.csproj @@ -1,73 +1,12 @@ - - - + + + + + + - 11.0 - Debug - AnyCPU - {88AE38DF-19D7-406F-A6A9-09527719A21E} - Library - Properties - BDInfo - BDInfo - en-US - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 + netcoreapp2.1 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - - - \ No newline at end of file + + diff --git a/BDInfo/BDInfo.nuget.targets b/BDInfo/BDInfo.nuget.targets deleted file mode 100644 index e69ce0e64f..0000000000 --- a/BDInfo/BDInfo.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs index d86648364f..f00d9825a8 100644 --- a/BDInfo/BDROM.cs +++ b/BDInfo/BDROM.cs @@ -57,8 +57,6 @@ namespace BDInfo public Dictionary InterleavedFiles = new Dictionary(); - private static List ExcludeDirs = new List { "ANY!", "AACS", "BDSVM", "ANYVM", "SLYVM" }; - public delegate bool OnStreamClipFileScanError( TSStreamClipFile streamClipFile, Exception ex); @@ -77,7 +75,7 @@ namespace BDInfo public BDROM( string path, IFileSystem fileSystem, ITextEncoding textEncoding) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } @@ -336,7 +334,7 @@ namespace BDInfo private FileSystemMetadata GetDirectoryBDMV( string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } @@ -421,7 +419,7 @@ namespace BDInfo return dir.Name; } - public static int CompareStreamFiles( + public int CompareStreamFiles( TSStreamFile x, TSStreamFile y) { diff --git a/BDInfo/BitVector32.cs b/BDInfo/BitVector32.cs deleted file mode 100644 index 1ac94bfbeb..0000000000 --- a/BDInfo/BitVector32.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BDInfo -{ - using System.Diagnostics; - using System.Text; - using System; - - /// - /// Provides a simple light bit vector with easy integer or Boolean access to - /// a 32 bit storage. - /// - public struct BitVector32 - { - private uint data; - - /// - /// Initializes a new instance of the BitVector32 structure with the specified internal data. - /// - public BitVector32(int data) - { - this.data = (uint)data; - } - - /// - /// Initializes a new instance of the BitVector32 structure with the information in the specified - /// value. - /// - public BitVector32(BitVector32 value) - { - this.data = value.data; - } - - /// - /// Gets or sets a value indicating whether all the specified bits are set. - /// - public bool this[int bit] - { - get - { - return (data & bit) == (uint)bit; - } - set - { - if (value) - { - data |= (uint)bit; - } - else - { - data &= ~(uint)bit; - } - } - } - - /// - /// Gets or sets the value for the specified section. - /// - public int this[Section section] - { - get - { - return (int)((data & (uint)(section.Mask << section.Offset)) >> section.Offset); - } - set - { - value <<= section.Offset; - int offsetMask = (0xFFFF & (int)section.Mask) << section.Offset; - data = (data & ~(uint)offsetMask) | ((uint)value & (uint)offsetMask); - } - } - - /// - /// returns the raw data stored in this bit vector... - /// - public int Data - { - get - { - return (int)data; - } - } - - private static short CountBitsSet(short mask) - { - - // yes, I know there are better algorithms, however, we know the - // bits are always right aligned, with no holes (i.e. always 00000111, - // never 000100011), so this is just fine... - // - short value = 0; - while ((mask & 0x1) != 0) - { - value++; - mask >>= 1; - } - return value; - } - - /// - /// Creates the first mask in a series. - /// - public static int CreateMask() - { - return CreateMask(0); - } - - /// - /// Creates the next mask in a series. - /// - public static int CreateMask(int previous) - { - if (previous == 0) - { - return 1; - } - - if (previous == unchecked((int)0x80000000)) - { - throw new InvalidOperationException("Bit vector full"); - } - - return previous << 1; - } - - /// - /// Given a highValue, creates the mask - /// - private static short CreateMaskFromHighValue(short highValue) - { - short required = 16; - while ((highValue & 0x8000) == 0) - { - required--; - highValue <<= 1; - } - - ushort value = 0; - while (required > 0) - { - required--; - value <<= 1; - value |= 0x1; - } - - return unchecked((short)value); - } - - /// - /// Creates the first section in a series, with the specified maximum value. - /// - public static Section CreateSection(short maxValue) - { - return CreateSectionHelper(maxValue, 0, 0); - } - - /// - /// Creates the next section in a series, with the specified maximum value. - /// - public static Section CreateSection(short maxValue, Section previous) - { - return CreateSectionHelper(maxValue, previous.Mask, previous.Offset); - } - - private static Section CreateSectionHelper(short maxValue, short priorMask, short priorOffset) - { - if (maxValue < 1) - { - throw new ArgumentOutOfRangeException("maxValue"); - } -#if DEBUG - int maskCheck = CreateMaskFromHighValue(maxValue); - int offsetCheck = priorOffset + CountBitsSet(priorMask); - Debug.Assert(maskCheck <= short.MaxValue && offsetCheck < 32, "Overflow on BitVector32"); -#endif - short offset = (short)(priorOffset + CountBitsSet(priorMask)); - if (offset >= 32) - { - throw new InvalidOperationException("Bit vector full"); - } - return new Section(CreateMaskFromHighValue(maxValue), offset); - } - - public override bool Equals(object o) - { - if (!(o is BitVector32)) - { - return false; - } - - return data == ((BitVector32)o).data; - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// - public static string ToString(BitVector32 value) - { - StringBuilder sb = new StringBuilder(/*"BitVector32{".Length*/12 + /*32 bits*/32 + /*"}".Length"*/1); - sb.Append("BitVector32{"); - int locdata = (int)value.data; - for (int i = 0; i < 32; i++) - { - if ((locdata & 0x80000000) != 0) - { - sb.Append("1"); - } - else - { - sb.Append("0"); - } - locdata <<= 1; - } - sb.Append("}"); - return sb.ToString(); - } - - /// - /// - public override string ToString() - { - return BitVector32.ToString(this); - } - - /// - /// - /// Represents an section of the vector that can contain a integer number. - /// - public struct Section - { - private readonly short mask; - private readonly short offset; - - internal Section(short mask, short offset) - { - this.mask = mask; - this.offset = offset; - } - - public short Mask - { - get - { - return mask; - } - } - - public short Offset - { - get - { - return offset; - } - } - - public override bool Equals(object o) - { - if (o is Section) - return Equals((Section)o); - else - return false; - } - - public bool Equals(Section obj) - { - return obj.mask == mask && obj.offset == offset; - } - - public static bool operator ==(Section a, Section b) - { - return a.Equals(b); - } - - public static bool operator !=(Section a, Section b) - { - return !(a == b); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// - public static string ToString(Section value) - { - return "Section{0x" + Convert.ToString(value.Mask, 16) + ", 0x" + Convert.ToString(value.Offset, 16) + "}"; - } - - /// - /// - public override string ToString() - { - return Section.ToString(this); - } - - } - } -} diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs index 46d66f513e..da6fd37cc0 100644 --- a/BDInfo/TSPlaylistFile.cs +++ b/BDInfo/TSPlaylistFile.cs @@ -954,7 +954,7 @@ namespace BDInfo } } - public static int CompareVideoStreams( + public int CompareVideoStreams( TSVideoStream x, TSVideoStream y) { @@ -995,7 +995,7 @@ namespace BDInfo } } - public static int CompareAudioStreams( + public int CompareAudioStreams( TSAudioStream x, TSAudioStream y) { @@ -1067,7 +1067,7 @@ namespace BDInfo } } - public static int CompareTextStreams( + public int CompareTextStreams( TSTextStream x, TSTextStream y) { @@ -1123,7 +1123,7 @@ namespace BDInfo } } - private static int CompareGraphicsStreams( + private int CompareGraphicsStreams( TSGraphicsStream x, TSGraphicsStream y) { @@ -1189,7 +1189,7 @@ namespace BDInfo } } - private static int GetStreamTypeSortIndex(TSStreamType streamType) + private int GetStreamTypeSortIndex(TSStreamType streamType) { switch (streamType) { diff --git a/BDInfo/TSStreamFile.cs b/BDInfo/TSStreamFile.cs index 31020cbf40..cfd402434d 100644 --- a/BDInfo/TSStreamFile.cs +++ b/BDInfo/TSStreamFile.cs @@ -865,7 +865,7 @@ namespace BDInfo k += streamInfoLength; } } - catch (Exception ex) + catch { // TODO //Console.WriteLine(ex.Message); diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj index 9ed197c591..f38fc81018 100644 --- a/DvdLib/DvdLib.csproj +++ b/DvdLib/DvdLib.csproj @@ -1,67 +1,12 @@ - - - + + + + + + - 11.0 - Debug - AnyCPU - {713F42B5-878E-499D-A878-E4C652B1D5E8} - Library - Properties - DvdLib - DvdLib - en-US - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 + netcoreapp2.1 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - - - \ No newline at end of file + + diff --git a/DvdLib/DvdLib.nuget.targets b/DvdLib/DvdLib.nuget.targets deleted file mode 100644 index e69ce0e64f..0000000000 --- a/DvdLib/DvdLib.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs index 3179f73cd6..57cb5d6bd0 100644 --- a/DvdLib/Ifo/ProgramChain.cs +++ b/DvdLib/Ifo/ProgramChain.cs @@ -15,8 +15,6 @@ namespace DvdLib.Ifo public class ProgramChain { - private ushort _unknown1; - private byte _programCount; public readonly List Programs; diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs similarity index 66% rename from MediaBrowser.Api/Dlna/DlnaServerService.cs rename to Emby.Dlna/Api/DlnaServerService.cs index 6a0cea4df9..663728f362 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -1,15 +1,16 @@ using MediaBrowser.Controller.Dlna; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; +using MediaBrowser.Common.Extensions; +using System.Text; +using MediaBrowser.Controller.Net; +using System.Linq; +using Emby.Dlna.Main; -namespace MediaBrowser.Api.Dlna +namespace Emby.Dlna.Api { [Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")] [Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")] @@ -98,30 +99,55 @@ namespace MediaBrowser.Api.Dlna [Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")] public class GetIcon { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UuId { get; set; } [ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Filename { get; set; } } - public class DlnaServerService : BaseApiService + public class DlnaServerService : IService, IRequiresRequest { private readonly IDlnaManager _dlnaManager; - private readonly IContentDirectory _contentDirectory; - private readonly IConnectionManager _connectionManager; - private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar; private const string XMLContentType = "text/xml; charset=UTF-8"; - private readonly IMemoryStreamFactory _memoryStreamProvider; - public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar, IMemoryStreamFactory memoryStreamProvider) + public IRequest Request { get; set; } + private IHttpResultFactory _resultFactory; + + private IContentDirectory ContentDirectory + { + get + { + return DlnaEntryPoint.Current.ContentDirectory; + } + } + + private IConnectionManager ConnectionManager + { + get + { + return DlnaEntryPoint.Current.ConnectionManager; + } + } + + private IMediaReceiverRegistrar MediaReceiverRegistrar + { + get + { + return DlnaEntryPoint.Current.MediaReceiverRegistrar; + } + } + + public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory) { _dlnaManager = dlnaManager; - _contentDirectory = contentDirectory; - _connectionManager = connectionManager; - _mediaReceiverRegistrar = mediaReceiverRegistrar; - _memoryStreamProvider = memoryStreamProvider; + _resultFactory = httpResultFactory; + } + + private string GetHeader(string name) + { + return Request.Headers[name]; } public object Get(GetDescriptionXml request) @@ -130,49 +156,53 @@ namespace MediaBrowser.Api.Dlna var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress); - return ResultFactory.GetResult(xml, XMLContentType); + var cacheLength = TimeSpan.FromDays(1); + var cacheKey = Request.RawUrl.GetMD5(); + var bytes = Encoding.UTF8.GetBytes(xml); + + return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult(new MemoryStream(bytes))); } public object Get(GetContentDirectory request) { - var xml = _contentDirectory.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary()); - return ResultFactory.GetResult(xml, XMLContentType); + return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetMediaReceiverRegistrar request) { - var xml = _mediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); + var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); - return ResultFactory.GetResult(xml, XMLContentType); + return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetConnnectionManager request) { - var xml = _connectionManager.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary()); - return ResultFactory.GetResult(xml, XMLContentType); + return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Post(ProcessMediaReceiverRegistrarControlRequest request) { - var response = PostAsync(request.RequestStream, _mediaReceiverRegistrar); + var response = PostAsync(request.RequestStream, MediaReceiverRegistrar); - return ResultFactory.GetResult(response.Xml, XMLContentType); + return _resultFactory.GetResult(Request, response.Xml, XMLContentType); } public object Post(ProcessContentDirectoryControlRequest request) { - var response = PostAsync(request.RequestStream, _contentDirectory); + var response = PostAsync(request.RequestStream, ContentDirectory); - return ResultFactory.GetResult(response.Xml, XMLContentType); + return _resultFactory.GetResult(Request, response.Xml, XMLContentType); } public object Post(ProcessConnectionManagerControlRequest request) { - var response = PostAsync(request.RequestStream, _connectionManager); + var response = PostAsync(request.RequestStream, ConnectionManager); - return ResultFactory.GetResult(response.Xml, XMLContentType); + return _resultFactory.GetResult(Request, response.Xml, XMLContentType); } private ControlResponse PostAsync(Stream requestStream, IUpnpService service) @@ -188,49 +218,76 @@ namespace MediaBrowser.Api.Dlna }); } + protected string GetPathValue(int index) + { + var pathInfo = Parse(Request.PathInfo); + var first = pathInfo[0]; + + // backwards compatibility + if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) || + string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase)) + { + index++; + } + + return pathInfo[index]; + } + + private List Parse(string pathUri) + { + var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); + + var pathInfo = actionParts[actionParts.Length - 1]; + + var optionsPos = pathInfo.LastIndexOf('?'); + if (optionsPos != -1) + { + pathInfo = pathInfo.Substring(0, optionsPos); + } + + var args = pathInfo.Split('/'); + + return args.Skip(1).ToList(); + } + public object Get(GetIcon request) { - using (var response = _dlnaManager.GetIcon(request.Filename)) - { - using (var ms = _memoryStreamProvider.CreateNew()) - { - response.Stream.CopyTo(ms); + var contentType = "image/" + Path.GetExtension(request.Filename).TrimStart('.').ToLower(); - ms.Position = 0; - var bytes = ms.ToArray(); - return ResultFactory.GetResult(bytes, "image/" + response.Format.ToString().ToLower()); - } - } + var cacheLength = TimeSpan.FromDays(365); + var cacheKey = Request.RawUrl.GetMD5(); + + return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); } public object Subscribe(ProcessContentDirectoryEventRequest request) { - return ProcessEventRequest(_contentDirectory); + return ProcessEventRequest(ContentDirectory); } public object Subscribe(ProcessConnectionManagerEventRequest request) { - return ProcessEventRequest(_connectionManager); + return ProcessEventRequest(ConnectionManager); } public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) { - return ProcessEventRequest(_mediaReceiverRegistrar); + return ProcessEventRequest(MediaReceiverRegistrar); } public object Unsubscribe(ProcessContentDirectoryEventRequest request) { - return ProcessEventRequest(_contentDirectory); + return ProcessEventRequest(ContentDirectory); } public object Unsubscribe(ProcessConnectionManagerEventRequest request) { - return ProcessEventRequest(_connectionManager); + return ProcessEventRequest(ConnectionManager); } public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) { - return ProcessEventRequest(_mediaReceiverRegistrar); + return ProcessEventRequest(MediaReceiverRegistrar); } private object ProcessEventRequest(IEventManager eventManager) @@ -257,7 +314,7 @@ namespace MediaBrowser.Api.Dlna private object GetSubscriptionResponse(EventSubscriptionResponse response) { - return ResultFactory.GetResult(response.Content, response.ContentType, response.Headers); + return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers); } } } diff --git a/MediaBrowser.Api/Dlna/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs similarity index 84% rename from MediaBrowser.Api/Dlna/DlnaService.cs rename to Emby.Dlna/Api/DlnaService.cs index 4dd71f4463..fec610d230 100644 --- a/MediaBrowser.Api/Dlna/DlnaService.cs +++ b/Emby.Dlna/Api/DlnaService.cs @@ -4,7 +4,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Services; -namespace MediaBrowser.Api.Dlna +namespace Emby.Dlna.Api { [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")] public class GetProfileInfos : IReturn @@ -41,7 +41,7 @@ namespace MediaBrowser.Api.Dlna } [Authenticated(Roles = "Admin")] - public class DlnaService : BaseApiService + public class DlnaService : IService { private readonly IDlnaManager _dlnaManager; @@ -52,23 +52,17 @@ namespace MediaBrowser.Api.Dlna public object Get(GetProfileInfos request) { - var result = _dlnaManager.GetProfileInfos().ToArray(); - - return ToOptimizedResult(result); + return _dlnaManager.GetProfileInfos().ToArray(); } public object Get(GetProfile request) { - var result = _dlnaManager.GetProfile(request.Id); - - return ToOptimizedResult(result); + return _dlnaManager.GetProfile(request.Id); } public object Get(GetDefaultProfile request) { - var result = _dlnaManager.GetDefaultProfile(); - - return ToOptimizedResult(result); + return _dlnaManager.GetDefaultProfile(); } public void Delete(DeleteProfile request) diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs index 7e0bc6ae8b..571e17d060 100644 --- a/Emby.Dlna/Common/StateVariable.cs +++ b/Emby.Dlna/Common/StateVariable.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System; namespace Emby.Dlna.Common { @@ -10,7 +11,7 @@ namespace Emby.Dlna.Common public bool SendsEvents { get; set; } - public List AllowedValues { get; set; } + public string[] AllowedValues { get; set; } public override string ToString() { @@ -19,7 +20,7 @@ namespace Emby.Dlna.Common public StateVariable() { - AllowedValues = new List(); + AllowedValues = Array.Empty(); } } } diff --git a/MediaBrowser.Model/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs similarity index 86% rename from MediaBrowser.Model/Configuration/DlnaOptions.cs rename to Emby.Dlna/Configuration/DlnaOptions.cs index 71a24d707d..6ab337752d 100644 --- a/MediaBrowser.Model/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -1,5 +1,5 @@  -namespace MediaBrowser.Model.Configuration +namespace Emby.Dlna.Configuration { public class DlnaOptions { @@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Configuration EnableServer = true; BlastAliveMessages = true; ClientDiscoveryIntervalSeconds = 60; - BlastAliveMessageIntervalSeconds = 30; + BlastAliveMessageIntervalSeconds = 1800; } } } diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs index cec885e4a7..f19f0dfb5d 100644 --- a/Emby.Dlna/ConfigurationExtension.cs +++ b/Emby.Dlna/ConfigurationExtension.cs @@ -1,5 +1,5 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; +using Emby.Dlna.Configuration; using System.Collections.Generic; namespace Emby.Dlna @@ -16,7 +16,7 @@ namespace Emby.Dlna { public IEnumerable GetConfigurations() { - return new List + return new ConfigurationStore[] { new ConfigurationStore { diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs index 2a415c58e0..0666eda229 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs @@ -42,7 +42,7 @@ namespace Emby.Dlna.ConnectionManager DataType = "string", SendsEvents = false, - AllowedValues = new List + AllowedValues = new string[] { "OK", "ContentFormatMismatch", @@ -65,7 +65,7 @@ namespace Emby.Dlna.ConnectionManager DataType = "string", SendsEvents = false, - AllowedValues = new List + AllowedValues = new string[] { "Output", "Input" diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index fbd7090104..0aabe099c1 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -26,10 +25,9 @@ namespace Emby.Dlna.ContentDirectory private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; private readonly ILocalizationManager _localization; - private readonly IChannelManager _channelManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IUserViewManager _userViewManager; - private readonly Func _mediaEncoder; + private readonly IMediaEncoder _mediaEncoder; protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; private readonly ITVSeriesManager _tvSeriesManager; @@ -40,7 +38,7 @@ namespace Emby.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) + IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) : base(logger, httpClient) { _dlna = dlna; @@ -50,7 +48,6 @@ namespace Emby.Dlna.ContentDirectory _config = config; _userManager = userManager; _localization = localization; - _channelManager = channelManager; _mediaSourceManager = mediaSourceManager; _userViewManager = userViewManager; _mediaEncoder = mediaEncoder; @@ -95,10 +92,9 @@ namespace Emby.Dlna.ContentDirectory SystemUpdateId, _config, _localization, - _channelManager, _mediaSourceManager, _userViewManager, - _mediaEncoder(), + _mediaEncoder, XmlReaderSettingsFactory, _tvSeriesManager) .ProcessControlRequest(request); diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryBrowser.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryBrowser.cs deleted file mode 100644 index 61501635fa..0000000000 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryBrowser.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Linq; -using System.Xml.Linq; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Channels; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using System.Globalization; -using System.IO; -using System.Security; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.Dlna.Server; - -namespace Emby.Dlna.ContentDirectory -{ - public class ContentDirectoryBrowser - { - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - - public ContentDirectoryBrowser(IHttpClient httpClient, ILogger logger) - { - _httpClient = httpClient; - _logger = logger; - } - - private static XNamespace UNamespace = "u"; - - public async Task> Browse(ContentDirectoryBrowseRequest request, CancellationToken cancellationToken) - { - var options = new HttpRequestOptions - { - CancellationToken = cancellationToken, - UserAgent = "Emby", - RequestContentType = "text/xml", - LogErrorResponseBody = true, - Url = request.ContentDirectoryUrl, - BufferContent = false - }; - - options.RequestHeaders["SOAPACTION"] = "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"; - - options.RequestContent = GetRequestBody(request); - - using (var response = await _httpClient.SendAsync(options, "POST")) - { - using (var reader = new StreamReader(response.Content)) - { - var doc = XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); - - var queryResult = new QueryResult(); - - if (doc.Document == null) - return queryResult; - - var responseElement = doc.Document.Descendants(UNamespace + "BrowseResponse").ToList(); - - var countElement = responseElement.Select(i => i.Element("TotalMatches")).FirstOrDefault(i => i != null); - var countValue = countElement == null ? null : countElement.Value; - - int count; - if (!string.IsNullOrWhiteSpace(countValue) && int.TryParse(countValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out count)) - { - queryResult.TotalRecordCount = count; - - var resultElement = responseElement.Select(i => i.Element("Result")).FirstOrDefault(i => i != null); - var resultString = (string)resultElement; - - if (resultElement != null) - { - var xElement = XElement.Parse(resultString); - } - } - - return queryResult; - } - } - } - - private string GetRequestBody(ContentDirectoryBrowseRequest request) - { - var builder = new StringBuilder(); - - builder.Append(""); - - builder.Append(""); - builder.Append(""); - - if (string.IsNullOrWhiteSpace(request.ParentId)) - { - request.ParentId = "1"; - } - - builder.AppendFormat("{0}", DescriptionXmlBuilder.Escape(request.ParentId)); - builder.Append("BrowseDirectChildren"); - - //builder.Append("BrowseMetadata"); - - builder.Append("*"); - - request.StartIndex = request.StartIndex ?? 0; - builder.AppendFormat("{0}", DescriptionXmlBuilder.Escape(request.StartIndex.Value.ToString(CultureInfo.InvariantCulture))); - - request.Limit = request.Limit ?? 20; - if (request.Limit.HasValue) - { - builder.AppendFormat("{0}", DescriptionXmlBuilder.Escape(request.Limit.Value.ToString(CultureInfo.InvariantCulture))); - } - - builder.Append(""); - - builder.Append(""); - builder.Append(""); - - return builder.ToString(); - } - } - - public class ContentDirectoryBrowseRequest - { - public int? StartIndex { get; set; } - public int? Limit { get; set; } - public string ParentId { get; set; } - public string ContentDirectoryUrl { get; set; } - } -} diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs index 7a9bc18ad8..facc252146 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs @@ -99,7 +99,7 @@ namespace Emby.Dlna.ContentDirectory DataType = "string", SendsEvents = false, - AllowedValues = new List + AllowedValues = new string[] { "BrowseMetadata", "BrowseDirectChildren" diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 5d6ef1adfe..91ec54b55d 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -31,13 +31,13 @@ using MediaBrowser.Controller.TV; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Xml; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.LiveTv; namespace Emby.Dlna.ContentDirectory { public class ControlHandler : BaseControlHandler { private readonly ILibraryManager _libraryManager; - private readonly IChannelManager _channelManager; private readonly IUserDataManager _userDataManager; private readonly IServerConfigurationManager _config; private readonly User _user; @@ -56,14 +56,13 @@ namespace Emby.Dlna.ContentDirectory private readonly DeviceProfile _profile; - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) + public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) : base(config, logger, xmlReaderSettingsFactory) { _libraryManager = libraryManager; _userDataManager = userDataManager; _user = user; _systemUpdateId = systemUpdateId; - _channelManager = channelManager; _userViewManager = userViewManager; _tvSeriesManager = tvSeriesManager; _profile = profile; @@ -125,7 +124,7 @@ namespace Emby.Dlna.ContentDirectory userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; - _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, + _userDataManager.SaveUserData(user, item, userdata, UserDataSaveReason.TogglePlayed, CancellationToken.None); return new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -246,6 +245,8 @@ namespace Emby.Dlna.ContentDirectory int totalCount; + var dlnaOptions = _config.GetDlnaConfiguration(); + using (XmlWriter writer = XmlWriter.Create(builder, settings)) { //writer.WriteStartDocument(); @@ -274,7 +275,7 @@ namespace Emby.Dlna.ContentDirectory } else { - _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, item, user, null, null, deviceId, filter); + _didlBuilder.WriteItemElement(dlnaOptions, writer, item, user, null, null, deviceId, filter); } provided++; @@ -300,7 +301,7 @@ namespace Emby.Dlna.ContentDirectory } else { - _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, childItem, user, item, serverItem.StubType, deviceId, filter); + _didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, user, item, serverItem.StubType, deviceId, filter); } } } @@ -310,7 +311,7 @@ namespace Emby.Dlna.ContentDirectory var resXML = builder.ToString(); - return new List> + return new [] { new KeyValuePair("Result", resXML), new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), @@ -385,6 +386,8 @@ namespace Emby.Dlna.ContentDirectory provided = childrenResult.Items.Length; + var dlnaOptions = _config.GetDlnaConfiguration(); + foreach (var i in childrenResult.Items) { if (i.IsDisplayedAsFolder) @@ -396,7 +399,7 @@ namespace Emby.Dlna.ContentDirectory } else { - _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, i, user, item, serverItem.StubType, deviceId, filter); + _didlBuilder.WriteItemElement(dlnaOptions, writer, i, user, item, serverItem.StubType, deviceId, filter); } } @@ -458,7 +461,7 @@ namespace Emby.Dlna.ContentDirectory { Limit = limit, StartIndex = startIndex, - OrderBy = sortOrders.Select(i => new Tuple(i, sort.SortOrder)).ToArray(), + OrderBy = sortOrders.Select(i => new ValueTuple(i, sort.SortOrder)).ToArray(), User = user, Recursive = true, IsMissing = false, @@ -478,22 +481,22 @@ namespace Emby.Dlna.ContentDirectory { if (item is MusicGenre) { - return GetMusicGenreItems(item, null, user, sort, startIndex, limit); + return GetMusicGenreItems(item, Guid.Empty, user, sort, startIndex, limit); } if (item is MusicArtist) { - return GetMusicArtistItems(item, null, user, sort, startIndex, limit); + return GetMusicArtistItems(item, Guid.Empty, user, sort, startIndex, limit); } if (item is Genre) { - return GetGenreItems(item, null, user, sort, startIndex, limit); + return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit); } if (!stubType.HasValue || stubType.Value != StubType.Folder) { - var collectionFolder = item as ICollectionFolder; + var collectionFolder = item as IHasCollectionType; if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) { return GetMusicFolders(item, user, stubType, sort, startIndex, limit); @@ -507,21 +510,18 @@ namespace Emby.Dlna.ContentDirectory return GetTvFolders(item, user, stubType, sort, startIndex, limit); } - var userView = item as UserView; - if (userView != null && string.Equals(CollectionType.Folders, userView.ViewType, StringComparison.OrdinalIgnoreCase)) + if (collectionFolder != null && string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) { return GetFolders(item, user, stubType, sort, startIndex, limit); } + if (collectionFolder != null && string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) + { + return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit); + } } if (stubType.HasValue) { - var person = item as Person; - if (person != null) - { - return GetItemsFromPerson(person, user, startIndex, limit); - } - if (stubType.Value != StubType.Folder) { return ApplyPaging(new QueryResult(), startIndex, limit); @@ -530,13 +530,11 @@ namespace Emby.Dlna.ContentDirectory var folder = (Folder)item; - var query = new InternalItemsQuery + var query = new InternalItemsQuery(user) { Limit = limit, StartIndex = startIndex, - User = user, IsVirtualItem = false, - PresetViews = new string[] { }, ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name }, IsPlaceHolder = false, DtoOptions = GetDtoOptions() @@ -549,6 +547,22 @@ namespace Emby.Dlna.ContentDirectory return ToResult(queryResult); } + private QueryResult GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + { + var query = new InternalItemsQuery(user) + { + StartIndex = startIndex, + Limit = limit, + }; + query.IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }; + + SetSorting(query, sort, false); + + var result = _libraryManager.GetItemsResult(query); + + return ToResult(result); + } + private QueryResult GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) @@ -747,7 +761,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { - var folders = user.RootFolder.GetChildren(user, true) + var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) .OrderBy(i => i.SortName) .Select(i => new ServerItem(i) { @@ -856,10 +870,10 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.OrderBy = new Tuple[] + query.OrderBy = new ValueTuple[] { - new Tuple (ItemSortBy.DatePlayed, SortOrder.Descending), - new Tuple (ItemSortBy.SortName, SortOrder.Ascending) + new ValueTuple (ItemSortBy.DatePlayed, SortOrder.Descending), + new ValueTuple (ItemSortBy.SortName, SortOrder.Ascending) }; query.IsResumable = true; @@ -1004,7 +1018,7 @@ namespace Emby.Dlna.ContentDirectory { var genresResult = _libraryManager.GetGenres(new InternalItemsQuery(user) { - AncestorIds = new[] { parent.Id.ToString("N") }, + AncestorIds = new[] { parent.Id }, StartIndex = query.StartIndex, Limit = query.Limit }); @@ -1022,7 +1036,7 @@ namespace Emby.Dlna.ContentDirectory { var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user) { - AncestorIds = new[] { parent.Id.ToString("N") }, + AncestorIds = new[] { parent.Id }, StartIndex = query.StartIndex, Limit = query.Limit }); @@ -1040,7 +1054,7 @@ namespace Emby.Dlna.ContentDirectory { var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user) { - AncestorIds = new[] { parent.Id.ToString("N") }, + AncestorIds = new[] { parent.Id }, StartIndex = query.StartIndex, Limit = query.Limit }); @@ -1058,7 +1072,7 @@ namespace Emby.Dlna.ContentDirectory { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) { - AncestorIds = new[] { parent.Id.ToString("N") }, + AncestorIds = new[] { parent.Id }, StartIndex = query.StartIndex, Limit = query.Limit }); @@ -1076,7 +1090,7 @@ namespace Emby.Dlna.ContentDirectory { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) { - AncestorIds = new[] { parent.Id.ToString("N") }, + AncestorIds = new[] { parent.Id }, StartIndex = query.StartIndex, Limit = query.Limit, IsFavorite = true @@ -1105,77 +1119,77 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new Tuple[] { }; + query.OrderBy = new ValueTuple[] { }; var items = _userViewManager.GetLatestItems(new LatestItemsQuery { - UserId = user.Id.ToString("N"), + UserId = user.Id, Limit = 50, IncludeItemTypes = new[] { typeof(Audio).Name }, - ParentId = parent == null ? null : parent.Id.ToString("N"), + ParentId = parent == null ? Guid.Empty : parent.Id, GroupItems = true - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToList(); + }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } private QueryResult GetNextUp(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new Tuple[] { }; + query.OrderBy = new ValueTuple[] { }; var result = _tvSeriesManager.GetNextUp(new NextUpQuery { Limit = query.Limit, StartIndex = query.StartIndex, - UserId = query.User.Id.ToString("N") + UserId = query.User.Id - }, new List { parent }, query.DtoOptions); + }, new [] { parent }, query.DtoOptions); return ToResult(result); } private QueryResult GetTvLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new Tuple[] { }; + query.OrderBy = new ValueTuple[] { }; var items = _userViewManager.GetLatestItems(new LatestItemsQuery { - UserId = user.Id.ToString("N"), + UserId = user.Id, Limit = 50, IncludeItemTypes = new[] { typeof(Episode).Name }, - ParentId = parent == null ? null : parent.Id.ToString("N"), + ParentId = parent == null ? Guid.Empty : parent.Id, GroupItems = false - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToList(); + }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } private QueryResult GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new Tuple[] { }; + query.OrderBy = new ValueTuple[] { }; var items = _userViewManager.GetLatestItems(new LatestItemsQuery { - UserId = user.Id.ToString("N"), + UserId = user.Id, Limit = 50, IncludeItemTypes = new[] { typeof(Movie).Name }, - ParentId = parent == null ? null : parent.Id.ToString("N"), + ParentId = parent == null ? Guid.Empty : parent.Id, GroupItems = true - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToList(); + }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } - private QueryResult GetMusicArtistItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { Recursive = true, ParentId = parentId, - ArtistIds = new[] { item.Id.ToString("N") }, + ArtistIds = new[] { item.Id }, IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, Limit = limit, StartIndex = startIndex, @@ -1189,13 +1203,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetGenreItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { Recursive = true, ParentId = parentId, - GenreIds = new[] { item.Id.ToString("N") }, + GenreIds = new[] { item.Id }, IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name }, Limit = limit, StartIndex = startIndex, @@ -1209,13 +1223,13 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicGenreItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { Recursive = true, ParentId = parentId, - GenreIds = new[] { item.Id.ToString("N") }, + GenreIds = new[] { item.Id }, IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, Limit = limit, StartIndex = startIndex, @@ -1229,15 +1243,15 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult ToResult(List result) + private QueryResult ToResult(BaseItem[] result) { var serverItems = result .Select(i => new ServerItem(i)) - .ToArray(result.Count); + .ToArray(result.Length); return new QueryResult { - TotalRecordCount = result.Count, + TotalRecordCount = result.Length, Items = serverItems }; } @@ -1247,7 +1261,7 @@ namespace Emby.Dlna.ContentDirectory var serverItems = result .Items .Select(i => new ServerItem(i)) - .ToArray(result.Items.Length); + .ToArray(); return new QueryResult { @@ -1264,28 +1278,7 @@ namespace Emby.Dlna.ContentDirectory sortOrders.Add(ItemSortBy.SortName); } - query.OrderBy = sortOrders.Select(i => new Tuple(i, sort.SortOrder)).ToArray(); - } - - private QueryResult GetItemsFromPerson(Person person, User user, int? startIndex, int? limit) - { - var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) - { - PersonIds = new[] { person.Id.ToString("N") }, - IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name, typeof(Trailer).Name }, - OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple(i, SortOrder.Ascending)).ToArray(), - Limit = limit, - StartIndex = startIndex, - DtoOptions = GetDtoOptions() - }); - - var serverItems = itemsResult.Items.Select(i => new ServerItem(i)).ToArray(itemsResult.Items.Length); - - return new QueryResult - { - TotalRecordCount = itemsResult.TotalRecordCount, - Items = serverItems - }; + query.OrderBy = sortOrders.Select(i => new ValueTuple(i, sort.SortOrder)).ToArray(); } private QueryResult ApplyPaging(QueryResult result, int? startIndex, int? limit) @@ -1299,7 +1292,7 @@ namespace Emby.Dlna.ContentDirectory { return DidlBuilder.IsIdRoot(id) - ? new ServerItem(user.RootFolder) + ? new ServerItem(_libraryManager.GetUserRootFolder()) : ParseItemId(id, user); } @@ -1343,7 +1336,7 @@ namespace Emby.Dlna.ContentDirectory Logger.Error("Error parsing item Id: {0}. Returning user root folder.", id); - return new ServerItem(user.RootFolder); + return new ServerItem(_libraryManager.GetUserRootFolder()); } } diff --git a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs index 8e3821e7c8..8e5c07ce2f 100644 --- a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs @@ -7,7 +7,7 @@ namespace Emby.Dlna.ContentDirectory { public IEnumerable GetActions() { - var list = new List + return new [] { GetSearchCapabilitiesAction(), GetSortCapabilitiesAction(), @@ -18,8 +18,6 @@ namespace Emby.Dlna.ContentDirectory GetXSetBookmarkAction(), GetBrowseByLetterAction() }; - - return list; } private ServiceAction GetGetSystemUpdateIDAction() diff --git a/MediaBrowser.Controller/Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs similarity index 91% rename from MediaBrowser.Controller/Dlna/ControlRequest.cs rename to Emby.Dlna/ControlRequest.cs index ff951ec9eb..2b8ce844ae 100644 --- a/MediaBrowser.Controller/Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.IO; -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public class ControlRequest { diff --git a/MediaBrowser.Controller/Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs similarity index 89% rename from MediaBrowser.Controller/Dlna/ControlResponse.cs rename to Emby.Dlna/ControlResponse.cs index 8d19a81096..889a6940c7 100644 --- a/MediaBrowser.Controller/Dlna/ControlResponse.cs +++ b/Emby.Dlna/ControlResponse.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public class ControlResponse { diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index a6fa187d4c..42727bb927 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -21,7 +21,7 @@ using System.Text; using System.Threading.Tasks; using System.Xml; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Configuration; +using Emby.Dlna.Configuration; using MediaBrowser.Model.Globalization; namespace Emby.Dlna.Didl @@ -62,6 +62,11 @@ namespace Emby.Dlna.Didl _user = user; } + public static string NormalizeDlnaMediaUrl(string url) + { + return url + "&dlnaheaders=true"; + } + public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) { var settings = new XmlWriterSettings @@ -72,28 +77,29 @@ namespace Emby.Dlna.Didl ConformanceLevel = ConformanceLevel.Fragment }; - StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); - - using (XmlWriter writer = XmlWriter.Create(builder, settings)) + using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8)) { - //writer.WriteStartDocument(); + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + //writer.WriteStartDocument(); - writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); + writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); - writer.WriteAttributeString("xmlns", "dc", null, NS_DC); - writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); - writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); - //didl.SetAttribute("xmlns:sec", NS_SEC); + writer.WriteAttributeString("xmlns", "dc", null, NS_DC); + writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); + writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); + //didl.SetAttribute("xmlns:sec", NS_SEC); - WriteXmlRootAttributes(_profile, writer); + WriteXmlRootAttributes(_profile, writer); - WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo); + WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo); - writer.WriteFullEndElement(); - //writer.WriteEndDocument(); + writer.WriteFullEndElement(); + //writer.WriteEndDocument(); + } + + return builder.ToString(); } - - return builder.ToString(); } public static void WriteXmlRootAttributes(DeviceProfile profile, XmlWriter writer) @@ -136,9 +142,9 @@ namespace Emby.Dlna.Didl else { var parent = item.DisplayParentId; - if (parent.HasValue) + if (!parent.Equals(Guid.Empty)) { - writer.WriteAttributeString("parentID", GetClientId(parent.Value, null)); + writer.WriteAttributeString("parentID", GetClientId(parent, null)); } } @@ -155,11 +161,11 @@ namespace Emby.Dlna.Didl { if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - AddAudioResource(options, writer, hasMediaSources, deviceId, filter, streamInfo); + AddAudioResource(options, writer, item, deviceId, filter, streamInfo); } else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - AddVideoResource(options, writer, hasMediaSources, deviceId, filter, streamInfo); + AddVideoResource(options, writer, item, deviceId, filter, streamInfo); } } @@ -181,6 +187,7 @@ namespace Emby.Dlna.Didl { var mime = MimeTypes.GetMimeType(input); + // TODO: Instead of being hard-coded here, this should probably be moved into all of the existing profiles if (string.Equals(mime, "video/mp2t", StringComparison.OrdinalIgnoreCase)) { mime = "video/mpeg"; @@ -189,7 +196,7 @@ namespace Emby.Dlna.Didl return mime; } - private void AddVideoResource(DlnaOptions options, XmlWriter writer, IHasMediaSources video, string deviceId, Filter filter, StreamInfo streamInfo = null) + private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null) { if (streamInfo == null) { @@ -197,8 +204,8 @@ namespace Emby.Dlna.Didl streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions { - ItemId = GetClientId(video), - MediaSources = sources.ToArray(sources.Count), + ItemId = video.Id, + MediaSources = sources.ToArray(), Profile = _profile, DeviceId = deviceId, MaxBitrate = _profile.MaxStreamingBitrate @@ -217,10 +224,10 @@ namespace Emby.Dlna.Didl streamInfo.TargetVideoBitrate, streamInfo.TargetTimestamp, streamInfo.IsDirectStream, - streamInfo.RunTimeTicks, + streamInfo.RunTimeTicks ?? 0, streamInfo.TargetVideoProfile, streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate, + streamInfo.TargetFramerate ?? 0, streamInfo.TargetPacketLength, streamInfo.TranscodeSeekInfo, streamInfo.IsTargetAnamorphic, @@ -296,11 +303,11 @@ namespace Emby.Dlna.Didl return true; } - private void AddVideoResource(XmlWriter writer, IHasMediaSources video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo) + private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo) { writer.WriteStartElement(string.Empty, "res", NS_DIDL); - var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken); + var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken)); var mediaSource = streamInfo.MediaSource; @@ -361,7 +368,7 @@ namespace Emby.Dlna.Didl streamInfo.TargetVideoBitDepth, streamInfo.TargetVideoProfile, streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate, + streamInfo.TargetFramerate ?? 0, streamInfo.TargetPacketLength, streamInfo.TargetTimestamp, streamInfo.IsTargetAnamorphic, @@ -494,7 +501,7 @@ namespace Emby.Dlna.Didl return item.Name; } - private void AddAudioResource(DlnaOptions options, XmlWriter writer, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null) + private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { writer.WriteStartElement(string.Empty, "res", NS_DIDL); @@ -504,14 +511,14 @@ namespace Emby.Dlna.Didl streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions { - ItemId = GetClientId(audio), + ItemId = audio.Id, MediaSources = sources.ToArray(sources.Count), Profile = _profile, DeviceId = deviceId }); } - var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken); + var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken)); var mediaSource = streamInfo.MediaSource; @@ -573,7 +580,7 @@ namespace Emby.Dlna.Didl targetChannels, targetAudioBitDepth, streamInfo.IsDirectStream, - streamInfo.RunTimeTicks, + streamInfo.RunTimeTicks ?? 0, streamInfo.TranscodeSeekInfo); writer.WriteAttributeString("protocolInfo", String.Format( @@ -606,7 +613,7 @@ namespace Emby.Dlna.Didl { writer.WriteStartElement(string.Empty, "container", NS_DIDL); - writer.WriteAttributeString("restricted", "0"); + writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("searchable", "1"); writer.WriteAttributeString("childCount", childCount.ToString(_usCulture)); @@ -628,13 +635,13 @@ namespace Emby.Dlna.Didl else { var parent = folder.DisplayParentId; - if (!parent.HasValue) + if (parent.Equals(Guid.Empty)) { writer.WriteAttributeString("parentID", "0"); } else { - writer.WriteAttributeString("parentID", GetClientId(parent.Value, null)); + writer.WriteAttributeString("parentID", GetClientId(parent, null)); } } } @@ -669,7 +676,7 @@ namespace Emby.Dlna.Didl return; } - var userdata = _userDataManager.GetUserData(user.Id, item); + var userdata = _userDataManager.GetUserData(user, item); if (userdata.PlaybackPositionTicks > 0) { @@ -713,21 +720,24 @@ namespace Emby.Dlna.Didl AddValue(writer, "upnp", "publisher", studio, NS_UPNP); } - if (filter.Contains("dc:description")) + if (!(item is Folder)) { - var desc = item.Overview; + if (filter.Contains("dc:description")) + { + var desc = item.Overview; - if (!string.IsNullOrWhiteSpace(desc)) - { - AddValue(writer, "dc", "description", desc, NS_DC); - } - } - if (filter.Contains("upnp:longDescription")) - { - if (!string.IsNullOrWhiteSpace(item.Overview)) - { - AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP); + if (!string.IsNullOrWhiteSpace(desc)) + { + AddValue(writer, "dc", "description", desc, NS_DC); + } } + //if (filter.Contains("upnp:longDescription")) + //{ + // if (!string.IsNullOrWhiteSpace(item.Overview)) + // { + // AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP); + // } + //} } if (!string.IsNullOrEmpty(item.OfficialRating)) @@ -823,37 +833,37 @@ namespace Emby.Dlna.Didl private void AddPeople(BaseItem item, XmlWriter writer) { - var types = new[] - { - PersonType.Director, - PersonType.Writer, - PersonType.Producer, - PersonType.Composer, - "Creator" - }; + //var types = new[] + //{ + // PersonType.Director, + // PersonType.Writer, + // PersonType.Producer, + // PersonType.Composer, + // "Creator" + //}; - var people = _libraryManager.GetPeople(item); + //var people = _libraryManager.GetPeople(item); - var index = 0; + //var index = 0; - // Seeing some LG models locking up due content with large lists of people - // The actual issue might just be due to processing a more metadata than it can handle - var limit = 6; + //// Seeing some LG models locking up due content with large lists of people + //// The actual issue might just be due to processing a more metadata than it can handle + //var limit = 6; - foreach (var actor in people) - { - var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) - ?? PersonType.Actor; + //foreach (var actor in people) + //{ + // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) + // ?? PersonType.Actor; - AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP); + // AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP); - index++; + // index++; - if (index >= limit) - { - break; - } - } + // if (index >= limit) + // { + // break; + // } + //} } private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) @@ -935,19 +945,6 @@ namespace Emby.Dlna.Didl { ImageDownloadInfo imageInfo = null; - if (context is UserView) - { - var episode = item as Episode; - if (episode != null) - { - var parent = episode.Series; - if (parent != null) - { - imageInfo = GetImageInfo(parent); - } - } - } - // Finally, just use the image from the item if (imageInfo == null) { @@ -959,34 +956,7 @@ namespace Emby.Dlna.Didl return; } - var playbackPercentage = 0; - var unplayedCount = 0; - - if (item is Video) - { - var userData = _userDataManager.GetUserDataDto(item, _user); - - playbackPercentage = Convert.ToInt32(userData.PlayedPercentage ?? 0); - if (playbackPercentage >= 100 || userData.Played) - { - playbackPercentage = 100; - } - } - else if (item is Series || item is Season || item is BoxSet) - { - var userData = _userDataManager.GetUserDataDto(item, _user); - - if (userData.Played) - { - playbackPercentage = 100; - } - else - { - unplayedCount = userData.UnplayedItemCount ?? 0; - } - } - - var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, unplayedCount, "jpg"); + var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP); writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn); @@ -994,7 +964,7 @@ namespace Emby.Dlna.Didl writer.WriteFullEndElement(); // TOOD: Remove these default values - var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, unplayedCount, "jpg"); + var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url); if (!_profile.EnableAlbumArtInDidl) @@ -1009,15 +979,15 @@ namespace Emby.Dlna.Didl } } - AddImageResElement(item, writer, 160, 160, playbackPercentage, unplayedCount, "jpg", "JPEG_TN"); + AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); - if (!_profile.EnableSingleAlbumArtLimit) + if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) { - AddImageResElement(item, writer, 4096, 4096, playbackPercentage, unplayedCount, "jpg", "JPEG_LRG"); - AddImageResElement(item, writer, 1024, 768, playbackPercentage, unplayedCount, "jpg", "JPEG_MED"); - AddImageResElement(item, writer, 640, 480, playbackPercentage, unplayedCount, "jpg", "JPEG_SM"); - AddImageResElement(item, writer, 4096, 4096, playbackPercentage, unplayedCount, "png", "PNG_LRG"); - AddImageResElement(item, writer, 160, 160, playbackPercentage, unplayedCount, "png", "PNG_TN"); + AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG"); + AddImageResElement(item, writer, 1024, 768, "jpg", "JPEG_MED"); + AddImageResElement(item, writer, 640, 480, "jpg", "JPEG_SM"); + AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG"); + AddImageResElement(item, writer, 160, 160, "png", "PNG_TN"); } } @@ -1035,8 +1005,6 @@ namespace Emby.Dlna.Didl XmlWriter writer, int maxWidth, int maxHeight, - int playbackPercentage, - int unplayedCount, string format, string org_Pn) { @@ -1047,7 +1015,7 @@ namespace Emby.Dlna.Didl return; } - var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, unplayedCount, format); + var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format); writer.WriteStartElement(string.Empty, "res", NS_DIDL); @@ -1151,7 +1119,7 @@ namespace Emby.Dlna.Didl return new ImageDownloadInfo { - ItemId = item.Id.ToString("N"), + ItemId = item.Id, Type = type, ImageTag = tag, Width = width, @@ -1163,7 +1131,7 @@ namespace Emby.Dlna.Didl class ImageDownloadInfo { - internal string ItemId; + internal Guid ItemId; internal string ImageTag; internal ImageType Type; @@ -1202,25 +1170,16 @@ namespace Emby.Dlna.Didl return id; } - public static string GetClientId(IHasMediaSources item) + private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) { - var id = item.Id.ToString("N"); - - return id; - } - - private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, int playbackPercentage, int unplayedCount, string format) - { - var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/{7}/{8}", + var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", _serverAddress, - info.ItemId, + info.ItemId.ToString("N"), info.Type, info.ImageTag, format, maxWidth.ToString(CultureInfo.InvariantCulture), - maxHeight.ToString(CultureInfo.InvariantCulture), - playbackPercentage.ToString(CultureInfo.InvariantCulture), - unplayedCount.ToString(CultureInfo.InvariantCulture) + maxHeight.ToString(CultureInfo.InvariantCulture) ); var width = info.Width; @@ -1235,7 +1194,7 @@ namespace Emby.Dlna.Didl Height = height.Value, Width = width.Value - }, null, null, maxWidth, maxHeight); + }, 0, 0, maxWidth, maxHeight); width = Convert.ToInt32(newSize.Width); height = Convert.ToInt32(newSize.Height); @@ -1249,6 +1208,9 @@ namespace Emby.Dlna.Didl } } + // just lie + info.IsDirectStream = true; + return new ImageUrlInfo { Url = url, diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 8caa086ee2..62d1eb57ce 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -132,55 +132,55 @@ namespace Emby.Dlna private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - if (!string.IsNullOrWhiteSpace(profileInfo.DeviceDescription)) + if (!string.IsNullOrEmpty(profileInfo.DeviceDescription)) { if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.FriendlyName)) + if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) { if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.Manufacturer)) + if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) { if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.ManufacturerUrl)) + if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) { if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.ModelDescription)) + if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) { if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.ModelName)) + if (!string.IsNullOrEmpty(profileInfo.ModelName)) { if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.ModelNumber)) + if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) { if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.ModelUrl)) + if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) { if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) return false; } - if (!string.IsNullOrWhiteSpace(profileInfo.SerialNumber)) + if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) { if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) return false; @@ -220,7 +220,7 @@ namespace Emby.Dlna } else { - var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray(headers.Count)); + var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray()); _logger.Debug("No matching device profile found. {0}", headerString); } @@ -235,7 +235,7 @@ namespace Emby.Dlna private bool IsMatch(IDictionary headers, HttpHeaderInfo header) { // Handle invalid user setup - if (string.IsNullOrWhiteSpace(header.Name)) + if (string.IsNullOrEmpty(header.Name)) { return false; } @@ -332,7 +332,7 @@ namespace Emby.Dlna public DeviceProfile GetProfile(string id) { - if (string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } @@ -429,7 +429,7 @@ namespace Emby.Dlna { profile = ReserializeProfile(profile); - if (string.IsNullOrWhiteSpace(profile.Name)) + if (string.IsNullOrEmpty(profile.Name)) { throw new ArgumentException("Profile is missing Name"); } @@ -444,11 +444,11 @@ namespace Emby.Dlna { profile = ReserializeProfile(profile); - if (string.IsNullOrWhiteSpace(profile.Id)) + if (string.IsNullOrEmpty(profile.Id)) { throw new ArgumentException("Profile is missing Id"); } - if (string.IsNullOrWhiteSpace(profile.Name)) + if (string.IsNullOrEmpty(profile.Name)) { throw new ArgumentException("Profile is missing Name"); } @@ -531,7 +531,7 @@ namespace Emby.Dlna } } - class DlnaProfileEntryPoint : IServerEntryPoint + class DlnaProfileEntryPoint /*: IServerEntryPoint*/ { private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; @@ -546,7 +546,7 @@ namespace Emby.Dlna public void Run() { - //DumpProfiles(); + DumpProfiles(); } private void DumpProfiles() @@ -595,7 +595,6 @@ namespace Emby.Dlna public void Dispose() { - GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index eb8b178a85..f71d544220 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -1,121 +1,17 @@ - - - - - 11.0 - Debug - AnyCPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5} - Library - Properties - Emby.Dlna - Emby.Dlna - en-US - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + netcoreapp2.1 + false + + @@ -128,27 +24,7 @@ - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - {21002819-c39a-4d3e-be83-2a276a77fb1f} - RSSDP - - - - - + @@ -179,12 +55,5 @@ - - - \ No newline at end of file + + diff --git a/MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs similarity index 90% rename from MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs rename to Emby.Dlna/EventSubscriptionResponse.cs index 8b551c2a7c..1c405e601d 100644 --- a/MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs +++ b/Emby.Dlna/EventSubscriptionResponse.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public class EventSubscriptionResponse { diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index 67eac640e1..0638cff897 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/EventManager.cs @@ -85,7 +85,7 @@ namespace Emby.Dlna.Eventing int val; - if (int.TryParse(header, NumberStyles.Any, _usCulture, out val)) + if (int.TryParse(header, NumberStyles.Integer, _usCulture, out val)) { return val; } @@ -118,7 +118,7 @@ namespace Emby.Dlna.Eventing }; response.Headers["SID"] = subscriptionId; - response.Headers["TIMEOUT"] = string.IsNullOrWhiteSpace(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString; + response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString; return response; } diff --git a/MediaBrowser.Controller/Dlna/IConnectionManager.cs b/Emby.Dlna/IConnectionManager.cs similarity index 69% rename from MediaBrowser.Controller/Dlna/IConnectionManager.cs rename to Emby.Dlna/IConnectionManager.cs index 38d96e607b..5f889a34ca 100644 --- a/MediaBrowser.Controller/Dlna/IConnectionManager.cs +++ b/Emby.Dlna/IConnectionManager.cs @@ -1,5 +1,5 @@  -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public interface IConnectionManager : IEventManager, IUpnpService { diff --git a/MediaBrowser.Controller/Dlna/IContentDirectory.cs b/Emby.Dlna/IContentDirectory.cs similarity index 69% rename from MediaBrowser.Controller/Dlna/IContentDirectory.cs rename to Emby.Dlna/IContentDirectory.cs index 28635d9b74..d63af4ef21 100644 --- a/MediaBrowser.Controller/Dlna/IContentDirectory.cs +++ b/Emby.Dlna/IContentDirectory.cs @@ -1,5 +1,5 @@  -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public interface IContentDirectory : IEventManager, IUpnpService { diff --git a/MediaBrowser.Controller/Dlna/IEventManager.cs b/Emby.Dlna/IEventManager.cs similarity index 95% rename from MediaBrowser.Controller/Dlna/IEventManager.cs rename to Emby.Dlna/IEventManager.cs index 3af357a174..e90476fe32 100644 --- a/MediaBrowser.Controller/Dlna/IEventManager.cs +++ b/Emby.Dlna/IEventManager.cs @@ -1,5 +1,5 @@  -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public interface IEventManager { diff --git a/MediaBrowser.Controller/Dlna/IMediaReceiverRegistrar.cs b/Emby.Dlna/IMediaReceiverRegistrar.cs similarity index 70% rename from MediaBrowser.Controller/Dlna/IMediaReceiverRegistrar.cs rename to Emby.Dlna/IMediaReceiverRegistrar.cs index 6b76783a5d..cb43221e56 100644 --- a/MediaBrowser.Controller/Dlna/IMediaReceiverRegistrar.cs +++ b/Emby.Dlna/IMediaReceiverRegistrar.cs @@ -1,5 +1,5 @@  -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public interface IMediaReceiverRegistrar : IEventManager, IUpnpService { diff --git a/MediaBrowser.Controller/Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs similarity index 94% rename from MediaBrowser.Controller/Dlna/IUpnpService.cs rename to Emby.Dlna/IUpnpService.cs index 3511740ad2..caae87ba36 100644 --- a/MediaBrowser.Controller/Dlna/IUpnpService.cs +++ b/Emby.Dlna/IUpnpService.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace MediaBrowser.Controller.Dlna +namespace Emby.Dlna { public interface IUpnpService { diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 5eeab3decf..4bab30337f 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -8,13 +8,12 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.TV; using Emby.Dlna.PlayTo; using Emby.Dlna.Ssdp; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; @@ -22,13 +21,14 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Xml; using Rssdp; using Rssdp.Infrastructure; using System.Threading; namespace Emby.Dlna.Main { - public class DlnaEntryPoint : IServerEntryPoint + public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup { private readonly IServerConfigurationManager _config; private readonly ILogger _logger; @@ -48,8 +48,6 @@ namespace Emby.Dlna.Main private readonly IDeviceDiscovery _deviceDiscovery; - private bool _ssdpHandlerStarted; - private bool _dlnaServerStarted; private SsdpDevicePublisher _Publisher; private readonly ITimerFactory _timerFactory; @@ -59,6 +57,12 @@ namespace Emby.Dlna.Main private ISsdpCommunicationsServer _communicationsServer; + internal IContentDirectory ContentDirectory { get; private set; } + internal IConnectionManager ConnectionManager { get; private set; } + internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } + + public static DlnaEntryPoint Current; + public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, @@ -69,9 +73,17 @@ namespace Emby.Dlna.Main IDlnaManager dlnaManager, IImageProcessor imageProcessor, IUserDataManager userDataManager, - ILocalizationManager localization, + ILocalizationManager localizationManager, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, INetworkManager networkManager) + IDeviceDiscovery deviceDiscovery, + IMediaEncoder mediaEncoder, + ISocketFactory socketFactory, + ITimerFactory timerFactory, + IEnvironmentInfo environmentInfo, + INetworkManager networkManager, + IUserViewManager userViewManager, + IXmlReaderSettingsFactory xmlReaderSettingsFactory, + ITVSeriesManager tvSeriesManager) { _config = config; _appHost = appHost; @@ -82,7 +94,7 @@ namespace Emby.Dlna.Main _dlnaManager = dlnaManager; _imageProcessor = imageProcessor; _userDataManager = userDataManager; - _localization = localization; + _localization = localizationManager; _mediaSourceManager = mediaSourceManager; _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; @@ -91,6 +103,26 @@ namespace Emby.Dlna.Main _environmentInfo = environmentInfo; _networkManager = networkManager; _logger = logManager.GetLogger("Dlna"); + + ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager, + userDataManager, + imageProcessor, + libraryManager, + config, + userManager, + _logger, + httpClient, + localizationManager, + mediaSourceManager, + userViewManager, + mediaEncoder, + xmlReaderSettingsFactory, + tvSeriesManager); + + ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory); + + MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory); + Current = this; } public void Run() @@ -99,20 +131,9 @@ namespace Emby.Dlna.Main ReloadComponents(); - _config.ConfigurationUpdated += _config_ConfigurationUpdated; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; } - private bool _lastEnableUpnP; - void _config_ConfigurationUpdated(object sender, EventArgs e) - { - if (_lastEnableUpnP != _config.Configuration.EnableUPnP) - { - ReloadComponents(); - } - _lastEnableUpnP = _config.Configuration.EnableUPnP; - } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) @@ -125,29 +146,22 @@ namespace Emby.Dlna.Main { var options = _config.GetDlnaConfiguration(); - if (!_ssdpHandlerStarted) + StartSsdpHandler(); + + if (options.EnableServer) { - StartSsdpHandler(); + await StartDevicePublisher(options).ConfigureAwait(false); + } + else + { + DisposeDevicePublisher(); } - var isServerStarted = _dlnaServerStarted; - - if (options.EnableServer && !isServerStarted) - { - await StartDlnaServer().ConfigureAwait(false); - } - else if (!options.EnableServer && isServerStarted) - { - DisposeDlnaServer(); - } - - var isPlayToStarted = _manager != null; - - if (options.EnablePlayTo && !isPlayToStarted) + if (options.EnablePlayTo) { StartPlayToManager(); } - else if (!options.EnablePlayTo && isPlayToStarted) + else { DisposePlayToManager(); } @@ -165,12 +179,9 @@ namespace Emby.Dlna.Main { IsShared = true }; + + StartDeviceDiscovery(_communicationsServer); } - - StartPublishing(_communicationsServer); - _ssdpHandlerStarted = true; - - StartDeviceDiscovery(_communicationsServer); } catch (Exception ex) { @@ -183,12 +194,6 @@ namespace Emby.Dlna.Main _logger.Debug(msg); } - private void StartPublishing(ISsdpCommunicationsServer communicationsServer) - { - SsdpDevicePublisherBase.LogFunction = LogMessage; - _Publisher = new SsdpDevicePublisher(communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); - } - private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer) { try @@ -205,6 +210,7 @@ namespace Emby.Dlna.Main { try { + _logger.Info("Disposing DeviceDiscovery"); ((DeviceDiscovery)_deviceDiscovery).Dispose(); } catch (Exception ex) @@ -213,29 +219,27 @@ namespace Emby.Dlna.Main } } - private void DisposeSsdpHandler() + public async Task StartDevicePublisher(Configuration.DlnaOptions options) { - DisposeDeviceDiscovery(); + if (!options.BlastAliveMessages) + { + return; + } + + if (_Publisher != null) + { + return; + } try { - ((DeviceDiscovery)_deviceDiscovery).Dispose(); + _Publisher = new SsdpDevicePublisher(_communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); + _Publisher.LogFunction = LogMessage; + _Publisher.SupportPnpRootDevice = false; - _ssdpHandlerStarted = false; - } - catch (Exception ex) - { - _logger.ErrorException("Error stopping ssdp handlers", ex); - } - } - - public async Task StartDlnaServer() - { - try - { await RegisterServerEndpoints().ConfigureAwait(false); - _dlnaServerStarted = true; + _Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); } catch (Exception ex) { @@ -245,23 +249,15 @@ namespace Emby.Dlna.Main private async Task RegisterServerEndpoints() { - if (!_config.GetDlnaConfiguration().BlastAliveMessages) - { - return; - } - - var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds; - _Publisher.SupportPnpRootDevice = false; - var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList(); var udn = CreateUuid(_appHost.SystemId); foreach (var address in addresses) { - //if (IPAddress.IsLoopback(address)) + // TODO: Remove this condition on platforms that support it + //if (address.AddressFamily == IpAddressFamily.InterNetworkV6) //{ - // // Should we allow this? // continue; //} @@ -274,7 +270,7 @@ namespace Emby.Dlna.Main var device = new SsdpRootDevice { - CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info. + CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info. Location = uri, // Must point to the URL that serves your devices UPnP description document. FriendlyName = "Emby Server", Manufacturer = "Emby", @@ -286,7 +282,7 @@ namespace Emby.Dlna.Main SetProperies(device, fullService); _Publisher.AddDevice(device); - var embeddedDevices = new List + var embeddedDevices = new [] { "urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ConnectionManager:1", @@ -338,6 +334,11 @@ namespace Emby.Dlna.Main { lock (_syncLock) { + if (_manager != null) + { + return; + } + try { _manager = new PlayToManager(_logger, @@ -373,6 +374,7 @@ namespace Emby.Dlna.Main { try { + _logger.Info("Disposing PlayToManager"); _manager.Dispose(); } catch (Exception ex) @@ -386,41 +388,31 @@ namespace Emby.Dlna.Main public void Dispose() { - DisposeDlnaServer(); + DisposeDevicePublisher(); DisposePlayToManager(); - DisposeSsdpHandler(); + DisposeDeviceDiscovery(); if (_communicationsServer != null) { + _logger.Info("Disposing SsdpCommunicationsServer"); _communicationsServer.Dispose(); _communicationsServer = null; } - GC.SuppressFinalize(this); + + ContentDirectory = null; + ConnectionManager = null; + MediaReceiverRegistrar = null; + Current = null; } - public void DisposeDlnaServer() + public void DisposeDevicePublisher() { if (_Publisher != null) { - var devices = _Publisher.Devices.ToList(); - var tasks = devices.Select(i => _Publisher.RemoveDevice(i)).ToArray(); - - Task.WaitAll(tasks); - //foreach (var device in devices) - //{ - // try - // { - // _Publisher.RemoveDevice(device); - // } - // catch (Exception ex) - // { - // _logger.ErrorException("Error sending bye bye", ex); - // } - //} + _logger.Info("Disposing SsdpDevicePublisher"); _Publisher.Dispose(); + _Publisher = null; } - - _dlnaServerStarted = false; } } } diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs index 691ba3061a..bce8bfaef3 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs @@ -7,7 +7,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { public IEnumerable GetActions() { - var list = new List + return new [] { GetIsValidated(), GetIsAuthorized(), @@ -17,8 +17,6 @@ namespace Emby.Dlna.MediaReceiverRegistrar GetGetValidationRevokedUpdateID(), GetGetValidationSucceededUpdateID() }; - - return list; } private ServiceAction GetIsValidated() diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index a617117f3c..9a861b8c7f 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -106,27 +106,17 @@ namespace Emby.Dlna.PlayTo _timerFactory = timerFactory; } - private int GetPlaybackTimerIntervalMs() - { - return 1000; - } - - private int GetInactiveTimerIntervalMs() - { - return 60000; - } - public void Start() { - _timer = _timerFactory.Create(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs()); - - _timerActive = false; + _logger.Debug("Dlna Device.Start"); + _timer = _timerFactory.Create(TimerCallback, null, 1000, Timeout.Infinite); } private DateTime _lastVolumeRefresh; + private bool _volumeRefreshActive; private void RefreshVolumeIfNeeded() { - if (!_timerActive) + if (!_volumeRefreshActive) { return; } @@ -134,19 +124,19 @@ namespace Emby.Dlna.PlayTo if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) { _lastVolumeRefresh = DateTime.UtcNow; - RefreshVolume(); + RefreshVolume(CancellationToken.None); } } - private async void RefreshVolume() + private async void RefreshVolume(CancellationToken cancellationToken) { if (_disposed) return; try { - await GetVolume().ConfigureAwait(false); - await GetMute().ConfigureAwait(false); + await GetVolume(cancellationToken).ConfigureAwait(false); + await GetMute(cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -155,21 +145,17 @@ namespace Emby.Dlna.PlayTo } private readonly object _timerLock = new object(); - private bool _timerActive; - private void RestartTimer() + private void RestartTimer(bool immediate = false) { - if (_disposed) - return; - lock (_timerLock) { - if (!_timerActive) - { - _logger.Debug("RestartTimer"); - _timer.Change(10, GetPlaybackTimerIntervalMs()); - } + if (_disposed) + return; - _timerActive = true; + _volumeRefreshActive = true; + + var time = immediate ? 100 : 10000; + _timer.Change(time, Timeout.Infinite); } } @@ -178,71 +164,67 @@ namespace Emby.Dlna.PlayTo /// private void RestartTimerInactive() { - if (_disposed) - return; - lock (_timerLock) { - if (_timerActive) - { - _logger.Debug("RestartTimerInactive"); - var interval = GetInactiveTimerIntervalMs(); + if (_disposed) + return; - if (_timer != null) - { - _timer.Change(interval, interval); - } - } + _volumeRefreshActive = false; - _timerActive = false; + _timer.Change(Timeout.Infinite, Timeout.Infinite); } } + public void OnPlaybackStartedExternally() + { + RestartTimer(true); + } + #region Commanding - public Task VolumeDown() + public Task VolumeDown(CancellationToken cancellationToken) { var sendVolume = Math.Max(Volume - 5, 0); - return SetVolume(sendVolume); + return SetVolume(sendVolume, cancellationToken); } - public Task VolumeUp() + public Task VolumeUp(CancellationToken cancellationToken) { var sendVolume = Math.Min(Volume + 5, 100); - return SetVolume(sendVolume); + return SetVolume(sendVolume, cancellationToken); } - public Task ToggleMute() + public Task ToggleMute(CancellationToken cancellationToken) { if (IsMuted) { - return Unmute(); + return Unmute(cancellationToken); } - return Mute(); + return Mute(cancellationToken); } - public async Task Mute() + public async Task Mute(CancellationToken cancellationToken) { - var success = await SetMute(true).ConfigureAwait(true); + var success = await SetMute(true, cancellationToken).ConfigureAwait(true); if (!success) { - await SetVolume(0).ConfigureAwait(false); + await SetVolume(0, cancellationToken).ConfigureAwait(false); } } - public async Task Unmute() + public async Task Unmute(CancellationToken cancellationToken) { - var success = await SetMute(false).ConfigureAwait(true); + var success = await SetMute(false, cancellationToken).ConfigureAwait(true); if (!success) { var sendVolume = _muteVol <= 0 ? 20 : _muteVol; - await SetVolume(sendVolume).ConfigureAwait(false); + await SetVolume(sendVolume, cancellationToken).ConfigureAwait(false); } } @@ -262,9 +244,11 @@ namespace Emby.Dlna.PlayTo services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:AVTransport", StringComparison.OrdinalIgnoreCase)); } - private async Task SetMute(bool mute) + private async Task SetMute(bool mute, CancellationToken cancellationToken) { - var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); + var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); if (command == null) return false; @@ -278,7 +262,7 @@ namespace Emby.Dlna.PlayTo _logger.Debug("Setting mute"); var value = mute ? 1 : 0; - await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value)) + await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) .ConfigureAwait(false); IsMuted = mute; @@ -289,9 +273,11 @@ namespace Emby.Dlna.PlayTo /// /// Sets volume on a scale of 0-100 /// - public async Task SetVolume(int value) + public async Task SetVolume(int value, CancellationToken cancellationToken) { - var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); + var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); if (command == null) return; @@ -306,13 +292,15 @@ namespace Emby.Dlna.PlayTo // Remote control will perform better Volume = value; - await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value)) + await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) .ConfigureAwait(false); } - public async Task Seek(TimeSpan value) + public async Task Seek(TimeSpan value, CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); if (command == null) return; @@ -323,15 +311,21 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) + await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) .ConfigureAwait(false); + + RestartTimer(true); } - public async Task SetAvTransport(string url, string header, string metaData) + public async Task SetAvTransport(string url, string header, string metaData, CancellationToken cancellationToken) { + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + url = url.Replace("&", "&"); + _logger.Debug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header); - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); if (command == null) return; @@ -348,7 +342,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - var post = AvCommands.BuildPost(command, service.ServiceType, url, dictionary); + var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) .ConfigureAwait(false); @@ -356,7 +350,7 @@ namespace Emby.Dlna.PlayTo try { - await SetPlay().ConfigureAwait(false); + await SetPlay(avCommands, CancellationToken.None).ConfigureAwait(false); } catch { @@ -364,7 +358,7 @@ namespace Emby.Dlna.PlayTo // Others won't } - RestartTimer(); + RestartTimer(true); } private string CreateDidlMeta(string value) @@ -375,11 +369,11 @@ namespace Emby.Dlna.PlayTo return DescriptionXmlBuilder.Escape(value); } - public async Task SetPlay() + private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); if (command == null) - return; + return Task.CompletedTask; var service = GetAvTransportService(); @@ -388,52 +382,74 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1)) - .ConfigureAwait(false); + return new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)); } - public async Task SetStop() + public async Task SetPlay(CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + await SetPlay(avCommands, cancellationToken).ConfigureAwait(false); + + RestartTimer(true); + } + + public async Task SetStop(CancellationToken cancellationToken) + { + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); if (command == null) return; var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1)) + await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); + + RestartTimer(true); } - public async Task SetPause() + public async Task SetPause(CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); if (command == null) return; var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1)) + await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); TransportState = TRANSPORTSTATE.PAUSED; + + RestartTimer(true); } #endregion #region Get data - private int _successiveStopCount; private int _connectFailureCount; private async void TimerCallback(object sender) { if (_disposed) return; - const int maxSuccessiveStopReturns = 5; - try { - var transportState = await GetTransportInfo().ConfigureAwait(false); + var cancellationToken = CancellationToken.None; + + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + if (avCommands == null) + { + return; + } + + var transportState = await GetTransportInfo(avCommands, cancellationToken).ConfigureAwait(false); if (_disposed) { @@ -451,13 +467,13 @@ namespace Emby.Dlna.PlayTo } else { - var tuple = await GetPositionInfo().ConfigureAwait(false); + var tuple = await GetPositionInfo(avCommands, cancellationToken).ConfigureAwait(false); var currentObject = tuple.Item2; if (tuple.Item1 && currentObject == null) { - currentObject = await GetMediaInfo().ConfigureAwait(false); + currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false); } if (currentObject != null) @@ -474,16 +490,10 @@ namespace Emby.Dlna.PlayTo // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive if (transportState.Value == TRANSPORTSTATE.STOPPED) { - _successiveStopCount++; - - if (_successiveStopCount >= maxSuccessiveStopReturns) - { - RestartTimerInactive(); - } + RestartTimerInactive(); } else { - _successiveStopCount = 0; RestartTimer(); } } @@ -492,54 +502,39 @@ namespace Emby.Dlna.PlayTo RestartTimerInactive(); } } - catch (HttpException ex) - { - if (_disposed) - return; - - //_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); - - _successiveStopCount++; - _connectFailureCount++; - - if (_connectFailureCount >= 3) - { - if (OnDeviceUnavailable != null) - { - _logger.Debug("Disposing device due to loss of connection"); - OnDeviceUnavailable(); - return; - } - } - if (_successiveStopCount >= maxSuccessiveStopReturns) - { - RestartTimerInactive(); - } - } catch (Exception ex) { if (_disposed) return; - _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); + //_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); - _successiveStopCount++; + _connectFailureCount++; - if (_successiveStopCount >= maxSuccessiveStopReturns) + if (_connectFailureCount >= 3) { - RestartTimerInactive(); + var action = OnDeviceUnavailable; + if (action != null) + { + _logger.Debug("Disposing device due to loss of connection"); + action(); + return; + } } + RestartTimerInactive(); } } - private async Task GetVolume() + private async Task GetVolume(CancellationToken cancellationToken) { if (_disposed) { return; } - var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); + var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); if (command == null) return; @@ -550,7 +545,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), true) + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true) .ConfigureAwait(false); if (result == null || result.Document == null) @@ -570,14 +565,16 @@ namespace Emby.Dlna.PlayTo } } - private async Task GetMute() + private async Task GetMute(CancellationToken cancellationToken) { if (_disposed) { return; } - var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); + var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + + var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); if (command == null) return; @@ -588,7 +585,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), true) + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true) .ConfigureAwait(false); if (result == null || result.Document == null) @@ -600,9 +597,9 @@ namespace Emby.Dlna.PlayTo IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase); } - private async Task GetTransportInfo() + private async Task GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); if (command == null) return null; @@ -610,7 +607,7 @@ namespace Emby.Dlna.PlayTo if (service == null) return null; - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType), false) + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false) .ConfigureAwait(false); if (result == null || result.Document == null) @@ -634,9 +631,9 @@ namespace Emby.Dlna.PlayTo return null; } - private async Task GetMediaInfo() + private async Task GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); if (command == null) return null; @@ -647,7 +644,9 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), false) + var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false) .ConfigureAwait(false); if (result == null || result.Document == null) @@ -691,9 +690,9 @@ namespace Emby.Dlna.PlayTo return null; } - private async Task> GetPositionInfo() + private async Task> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) { - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); + var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); if (command == null) return new Tuple(false, null); @@ -704,7 +703,9 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), false) + var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + + var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false) .ConfigureAwait(false); if (result == null || result.Document == null) @@ -831,42 +832,67 @@ namespace Emby.Dlna.PlayTo #region From XML - private async Task GetAVProtocolAsync(CancellationToken cancellationToken) + private async Task GetAVProtocolAsync(CancellationToken cancellationToken) { + var avCommands = AvCommands; + + if (avCommands != null) + { + return avCommands; + } + if (_disposed) { - return; + throw new ObjectDisposedException(GetType().Name); } var avService = GetAvTransportService(); if (avService == null) - return; + { + return null; + } string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); var httpClient = new SsdpHttpClient(_httpClient, _config); + var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - AvCommands = TransportCommands.Create(document); + avCommands = TransportCommands.Create(document); + AvCommands = avCommands; + return avCommands; } - private async Task GetRenderingProtocolAsync(CancellationToken cancellationToken) + private async Task GetRenderingProtocolAsync(CancellationToken cancellationToken) { + var rendererCommands = RendererCommands; + + if (rendererCommands != null) + { + return rendererCommands; + } + if (_disposed) { - return; + throw new ObjectDisposedException(GetType().Name); } var avService = GetServiceRenderingControl(); if (avService == null) - return; + { + throw new ArgumentException("Device AvService is null"); + } + string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); var httpClient = new SsdpHttpClient(_httpClient, _config); + _logger.Debug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - RendererCommands = TransportCommands.Create(document); + rendererCommands = TransportCommands.Create(document); + RendererCommands = rendererCommands; + return rendererCommands; } private string NormalizeUrl(string baseUrl, string url) @@ -891,7 +917,7 @@ namespace Emby.Dlna.PlayTo set; } - internal TransportCommands RendererCommands + private TransportCommands RendererCommands { get; set; @@ -985,12 +1011,6 @@ namespace Emby.Dlna.PlayTo var device = new Device(deviceProperties, httpClient, logger, config, timerFactory); - if (device.GetAvTransportService() != null) - { - await device.GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - await device.GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - } - return device; } @@ -1010,8 +1030,8 @@ namespace Emby.Dlna.PlayTo var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth")); var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url")); - var widthValue = int.Parse(width, NumberStyles.Any, UsCulture); - var heightValue = int.Parse(height, NumberStyles.Any, UsCulture); + var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture); + var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture); return new DeviceIcon { @@ -1089,6 +1109,12 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackProgress(uBaseObject mediaInfo) { + var mediaUrl = mediaInfo.Url; + if (string.IsNullOrWhiteSpace(mediaUrl)) + { + return; + } + if (PlaybackProgress != null) { PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs @@ -1131,7 +1157,6 @@ namespace Emby.Dlna.PlayTo _disposed = true; DisposeTimer(); - GC.SuppressFinalize(this); } } diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs index d293bcea14..d453a3d829 100644 --- a/Emby.Dlna/PlayTo/DeviceInfo.cs +++ b/Emby.Dlna/PlayTo/DeviceInfo.cs @@ -8,7 +8,6 @@ namespace Emby.Dlna.PlayTo { public DeviceInfo() { - ClientType = "DLNA"; Name = "Generic Device"; } @@ -16,8 +15,6 @@ namespace Emby.Dlna.PlayTo public string Name { get; set; } - public string ClientType { get; set; } - public string ModelName { get; set; } public string ModelNumber { get; set; } diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index b253cb26e7..ce22a0af2d 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -21,6 +21,8 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Extensions; +using System.Net.Http; +using MediaBrowser.Model.Services; namespace Emby.Dlna.PlayTo { @@ -53,10 +55,6 @@ namespace Emby.Dlna.PlayTo } } - public void OnActivity() - { - } - public bool SupportsMediaControl { get { return IsSessionActive; } @@ -141,17 +139,15 @@ namespace Emby.Dlna.PlayTo try { - var streamInfo = await StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager); if (streamInfo.Item != null) { - var progress = GetProgressInfo(e.OldMediaInfo, streamInfo); - - var positionTicks = progress.PositionTicks; + var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo); ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks); } - streamInfo = await StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); if (streamInfo.Item == null) return; var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo); @@ -173,20 +169,19 @@ namespace Emby.Dlna.PlayTo try { - var streamInfo = await StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager) - .ConfigureAwait(false); + var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); if (streamInfo.Item == null) return; - var progress = GetProgressInfo(e.MediaInfo, streamInfo); - - var positionTicks = progress.PositionTicks; + var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo); ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks); - var duration = streamInfo.MediaSource == null ? + var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); + + var duration = mediaSource == null ? (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : - streamInfo.MediaSource.RunTimeTicks; + mediaSource.RunTimeTicks; var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0); @@ -241,7 +236,7 @@ namespace Emby.Dlna.PlayTo try { - var info = await StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + var info = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); if (info.Item != null) { @@ -265,7 +260,14 @@ namespace Emby.Dlna.PlayTo try { - var info = await StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + var mediaUrl = e.MediaInfo.Url; + + if (string.IsNullOrWhiteSpace(mediaUrl)) + { + return; + } + + var info = StreamParams.ParseFromUrl(mediaUrl, _libraryManager, _mediaSourceManager); if (info.Item != null) { @@ -280,7 +282,7 @@ namespace Emby.Dlna.PlayTo } } - private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info) + private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info) { var ticks = _device.Position.Ticks; @@ -289,11 +291,16 @@ namespace Emby.Dlna.PlayTo ticks += info.StartPositionTicks; } + return ticks; + } + + private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info) + { return new PlaybackStartInfo { ItemId = info.ItemId, SessionId = _session.Id, - PositionTicks = ticks, + PositionTicks = GetProgressPositionTicks(mediaInfo, info), IsMuted = _device.IsMuted, IsPaused = _device.IsPaused, MediaSourceId = info.MediaSourceId, @@ -301,7 +308,8 @@ namespace Emby.Dlna.PlayTo SubtitleStreamIndex = info.SubtitleStreamIndex, VolumeLevel = _device.Volume, - CanSeek = info.MediaSource == null ? _device.Duration.HasValue : info.MediaSource.RunTimeTicks.HasValue, + // TODO + CanSeek = true, PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode }; @@ -313,12 +321,12 @@ namespace Emby.Dlna.PlayTo { _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); - var user = String.IsNullOrEmpty(command.ControllingUserId) ? null : _userManager.GetUserById(command.ControllingUserId); + var user = command.ControllingUserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(command.ControllingUserId); var items = new List(); - foreach (string id in command.ItemIds) + foreach (var id in command.ItemIds) { - AddItemFromId(Guid.Parse(id), items); + AddItemFromId(id, items); } var startIndex = command.StartIndex ?? 0; @@ -354,31 +362,31 @@ namespace Emby.Dlna.PlayTo Playlist.AddRange(playlist); } - if (!String.IsNullOrWhiteSpace(command.ControllingUserId)) + if (!command.ControllingUserId.Equals(Guid.Empty)) { - await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId, - _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false); + _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId, + _session.DeviceName, _session.RemoteEndPoint, user); } await PlayItems(playlist).ConfigureAwait(false); } - public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) + private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) { switch (command.Command) { case PlaystateCommand.Stop: Playlist.Clear(); - return _device.SetStop(); + return _device.SetStop(CancellationToken.None); case PlaystateCommand.Pause: - return _device.SetPause(); + return _device.SetPause(CancellationToken.None); case PlaystateCommand.Unpause: - return _device.SetPlay(); + return _device.SetPlay(CancellationToken.None); case PlaystateCommand.PlayPause: - return _device.IsPaused ? _device.SetPlay() : _device.SetPause(); + return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None); case PlaystateCommand.Seek: { @@ -392,7 +400,7 @@ namespace Emby.Dlna.PlayTo return SetPlaylistIndex(_currentPlaylistIndex - 1); } - return Task.FromResult(true); + return Task.CompletedTask; } private async Task Seek(long newPosition) @@ -401,17 +409,17 @@ namespace Emby.Dlna.PlayTo if (media != null) { - var info = await StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager); if (info.Item != null && !EnableClientSideSeek(info)) { - var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null; + var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex); - await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false); + await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); return; } - await SeekAfterTransportChange(newPosition).ConfigureAwait(false); + await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); } } @@ -425,46 +433,6 @@ namespace Emby.Dlna.PlayTo return info.IsDirectStream; } - public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendRestartRequiredNotification(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendServerRestartNotification(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendServerShutdownNotification(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - #endregion #region Playlist @@ -497,13 +465,13 @@ namespace Emby.Dlna.PlayTo var hasMediaSources = item as IHasMediaSources; var mediaSources = hasMediaSources != null - ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)) + ? (_mediaSourceManager.GetStaticMediaSources(item, true, user)) : new List(); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; - playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken); + playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder) .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); @@ -528,7 +496,7 @@ namespace Emby.Dlna.PlayTo streamInfo.TargetAudioChannels, streamInfo.TargetAudioBitDepth, streamInfo.IsDirectStream, - streamInfo.RunTimeTicks, + streamInfo.RunTimeTicks ?? 0, streamInfo.TranscodeSeekInfo); } @@ -544,10 +512,10 @@ namespace Emby.Dlna.PlayTo streamInfo.TargetVideoBitrate, streamInfo.TargetTimestamp, streamInfo.IsDirectStream, - streamInfo.RunTimeTicks, + streamInfo.RunTimeTicks ?? 0, streamInfo.TargetVideoProfile, streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate, + streamInfo.TargetFramerate ?? 0, streamInfo.TargetPacketLength, streamInfo.TranscodeSeekInfo, streamInfo.IsTargetAnamorphic, @@ -582,8 +550,8 @@ namespace Emby.Dlna.PlayTo { StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildVideoItem(new VideoOptions { - ItemId = item.Id.ToString("N"), - MediaSources = mediaSources.ToArray(mediaSources.Count), + ItemId = item.Id, + MediaSources = mediaSources.ToArray(), Profile = profile, DeviceId = deviceId, MaxBitrate = profile.MaxStreamingBitrate, @@ -602,7 +570,7 @@ namespace Emby.Dlna.PlayTo { StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildAudioItem(new AudioOptions { - ItemId = item.Id.ToString("N"), + ItemId = item.Id, MediaSources = mediaSources.ToArray(mediaSources.Count), Profile = profile, DeviceId = deviceId, @@ -642,19 +610,19 @@ namespace Emby.Dlna.PlayTo if (index < 0 || index >= Playlist.Count) { Playlist.Clear(); - await _device.SetStop(); + await _device.SetStop(CancellationToken.None); return; } _currentPlaylistIndex = index; var currentitem = Playlist[index]; - await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl); + await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None); var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) { - await SeekAfterTransportChange(streamInfo.StartPositionTicks).ConfigureAwait(false); + await SeekAfterTransportChange(streamInfo.StartPositionTicks, CancellationToken.None).ConfigureAwait(false); } } @@ -676,13 +644,12 @@ namespace Emby.Dlna.PlayTo _device.OnDeviceUnavailable = null; _device.Dispose(); - GC.SuppressFinalize(this); } } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) + private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { GeneralCommandType commandType; @@ -691,15 +658,15 @@ namespace Emby.Dlna.PlayTo switch (commandType) { case GeneralCommandType.VolumeDown: - return _device.VolumeDown(); + return _device.VolumeDown(cancellationToken); case GeneralCommandType.VolumeUp: - return _device.VolumeUp(); + return _device.VolumeUp(cancellationToken); case GeneralCommandType.Mute: - return _device.Mute(); + return _device.Mute(cancellationToken); case GeneralCommandType.Unmute: - return _device.Unmute(); + return _device.Unmute(cancellationToken); case GeneralCommandType.ToggleMute: - return _device.ToggleMute(); + return _device.ToggleMute(cancellationToken); case GeneralCommandType.SetAudioStreamIndex: { string arg; @@ -708,7 +675,7 @@ namespace Emby.Dlna.PlayTo { int val; - if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out val)) + if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out val)) { return SetAudioStreamIndex(val); } @@ -726,7 +693,7 @@ namespace Emby.Dlna.PlayTo { int val; - if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out val)) + if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out val)) { return SetSubtitleStreamIndex(val); } @@ -744,9 +711,9 @@ namespace Emby.Dlna.PlayTo { int volume; - if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out volume)) + if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out volume)) { - return _device.SetVolume(volume); + return _device.SetVolume(volume, cancellationToken); } throw new ArgumentException("Unsupported volume value supplied."); @@ -755,11 +722,11 @@ namespace Emby.Dlna.PlayTo throw new ArgumentException("Volume argument cannot be null"); } default: - return Task.FromResult(true); + return Task.CompletedTask; } } - return Task.FromResult(true); + return Task.CompletedTask; } private async Task SetAudioStreamIndex(int? newIndex) @@ -768,21 +735,20 @@ namespace Emby.Dlna.PlayTo if (media != null) { - var info = await StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager); if (info.Item != null) { - var progress = GetProgressInfo(media, info); - var newPosition = progress.PositionTicks ?? 0; + var newPosition = GetProgressPositionTicks(media, info) ?? 0; - var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null; + var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex); - await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false); + await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); if (EnableClientSideSeek(newItem.StreamInfo)) { - await SeekAfterTransportChange(newPosition).ConfigureAwait(false); + await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); } } } @@ -794,27 +760,26 @@ namespace Emby.Dlna.PlayTo if (media != null) { - var info = await StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false); + var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager); if (info.Item != null) { - var progress = GetProgressInfo(media, info); - var newPosition = progress.PositionTicks ?? 0; + var newPosition = GetProgressPositionTicks(media, info) ?? 0; - var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null; + var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex); - await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false); + await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0) { - await SeekAfterTransportChange(newPosition).ConfigureAwait(false); + await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); } } } } - private async Task SeekAfterTransportChange(long positionTicks) + private async Task SeekAfterTransportChange(long positionTicks, CancellationToken cancellationToken) { const int maxWait = 15000000; const int interval = 500; @@ -825,12 +790,12 @@ namespace Emby.Dlna.PlayTo currentWait += interval; } - await _device.Seek(TimeSpan.FromTicks(positionTicks)).ConfigureAwait(false); + await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false); } private class StreamParams { - public string ItemId { get; set; } + public Guid ItemId { get; set; } public bool IsDirectStream { get; set; } @@ -847,10 +812,36 @@ namespace Emby.Dlna.PlayTo public string LiveStreamId { get; set; } public BaseItem Item { get; set; } - public MediaSourceInfo MediaSource { get; set; } + private MediaSourceInfo MediaSource; - private static string GetItemId(string url) + private IMediaSourceManager _mediaSourceManager; + + public async Task GetMediaSource(CancellationToken cancellationToken) { + if (MediaSource != null) + { + return MediaSource; + } + + var hasMediaSources = Item as IHasMediaSources; + + if (hasMediaSources == null) + { + return null; + } + + MediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false); + + return MediaSource; + } + + private static Guid GetItemId(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentNullException("url"); + } + var parts = url.Split('/'); for (var i = 0; i < parts.Length; i++) @@ -862,96 +853,108 @@ namespace Emby.Dlna.PlayTo { if (parts.Length > i + 1) { - return parts[i + 1]; + return Guid.Parse(parts[i + 1]); } } } - return null; + return Guid.Empty; } - public static async Task ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager) + public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager) { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentNullException("url"); + } + var request = new StreamParams { ItemId = GetItemId(url) }; - Guid parsedId; - - if (string.IsNullOrWhiteSpace(request.ItemId) || !Guid.TryParse(request.ItemId, out parsedId)) + if (request.ItemId.Equals(Guid.Empty)) { return request; } - const string srch = "params="; - var index = url.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - + var index = url.IndexOf('?'); if (index == -1) return request; - var vals = url.Substring(index + srch.Length).Split(';'); + var query = url.Substring(index + 1); + QueryParamCollection values = MyHttpUtility.ParseQueryString(query); - for (var i = 0; i < vals.Length; i++) - { - var val = vals[i]; + request.DeviceProfileId = values.Get("DeviceProfileId"); + request.DeviceId = values.Get("DeviceId"); + request.MediaSourceId = values.Get("MediaSourceId"); + request.LiveStreamId = values.Get("LiveStreamId"); + request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase); - if (string.IsNullOrWhiteSpace(val)) - { - continue; - } + request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); + request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); + request.StartPositionTicks = GetLongValue(values, "StartPositionTicks"); - if (i == 0) - { - request.DeviceProfileId = val; - } - else if (i == 1) - { - request.DeviceId = val; - } - else if (i == 2) - { - request.MediaSourceId = val; - } - else if (i == 3) - { - request.IsDirectStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - else if (i == 6) - { - request.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); - } - else if (i == 7) - { - request.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); - } - else if (i == 14) - { - request.StartPositionTicks = long.Parse(val, CultureInfo.InvariantCulture); - } - else if (i == 22) - { - request.LiveStreamId = val; - } - } + request.Item = libraryManager.GetItemById(request.ItemId); - request.Item = string.IsNullOrWhiteSpace(request.ItemId) - ? null - : libraryManager.GetItemById(parsedId); - - var hasMediaSources = request.Item as IHasMediaSources; - - request.MediaSource = hasMediaSources == null - ? null - : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, request.LiveStreamId, false, CancellationToken.None).ConfigureAwait(false)); + request._mediaSourceManager = mediaSourceManager; return request; } } - public Task SendMessage(string name, T data, CancellationToken cancellationToken) + private static int? GetIntValue(QueryParamCollection values, string name) { + var value = values.Get(name); + + int result; + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) + { + return result; + } + + return null; + } + + private static long GetLongValue(QueryParamCollection values, string name) + { + var value = values.Get(name); + + long result; + if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) + { + return result; + } + + return 0; + } + + public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + + if (_device == null) + { + return Task.CompletedTask; + } + + if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) + { + return SendPlayCommand(data as PlayRequest, cancellationToken); + } + if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) + { + return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); + } + if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) + { + return SendGeneralCommand(data as GeneralCommand, cancellationToken); + } + // Not supported or needed right now - return Task.FromResult(true); + return Task.CompletedTask; } } } diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 84094d9060..ed3cf311b0 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -19,6 +19,8 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.Threading; using System.Threading; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; namespace Emby.Dlna.PlayTo { @@ -42,8 +44,6 @@ namespace Emby.Dlna.PlayTo private readonly IMediaEncoder _mediaEncoder; private readonly ITimerFactory _timerFactory; - private readonly List _nonRendererUrls = new List(); - private DateTime _lastRendererClear; private bool _disposed; private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); @@ -122,8 +122,6 @@ namespace Emby.Dlna.PlayTo catch (Exception ex) { _logger.ErrorException("Error creating PlayTo device.", ex); - - _nonRendererUrls.Add(location); } finally { @@ -131,64 +129,88 @@ namespace Emby.Dlna.PlayTo } } + private string GetUuid(string usn) + { + var found = false; + var index = usn.IndexOf("uuid:", StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + usn = usn.Substring(index); + found = true; + } + index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + usn = usn.Substring(0, index); + } + + if (found) + { + return usn; + } + + return usn.GetMD5().ToString("N"); + } + private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken) { - if ((DateTime.UtcNow - _lastRendererClear).TotalMinutes >= 10) - { - _nonRendererUrls.Clear(); - _lastRendererClear = DateTime.UtcNow; - } - - if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase)) - { - return; - } - var uri = info.Location; _logger.Debug("Attempting to create PlayToController from location {0}", location); - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false); - - if (device.RendererCommands == null) - { - //_logger.Debug("Upnp device {0} does not contain a MediaRenderer device (1).", location); - _nonRendererUrls.Add(location); - return; - } _logger.Debug("Logging session activity from location {0}", location); - var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null).ConfigureAwait(false); + string uuid; + if (info.Headers.TryGetValue("USN", out uuid)) + { + uuid = GetUuid(uuid); + } + else + { + uuid = location.GetMD5().ToString("N"); + } - var controller = sessionInfo.SessionController as PlayToController; + string deviceName = null; + + var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion.ToString(), uuid, deviceName, uri.OriginalString, null); + + var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault(); if (controller == null) { + var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false); + + deviceName = device.Properties.Name; + + _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); + string serverAddress; - if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Loopback)) + if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Any)) { - serverAddress = await GetServerAddress(null, cancellationToken).ConfigureAwait(false); + serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); } else { - serverAddress = await GetServerAddress(info.LocalIpAddress, cancellationToken).ConfigureAwait(false); + serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); } string accessToken = null; - sessionInfo.SessionController = controller = new PlayToController(sessionInfo, - _sessionManager, - _libraryManager, - _logger, - _dlnaManager, - _userManager, - _imageProcessor, - serverAddress, - accessToken, - _deviceDiscovery, - _userDataManager, - _localization, - _mediaSourceManager, - _config, - _mediaEncoder); + controller = new PlayToController(sessionInfo, + _sessionManager, + _libraryManager, + _logger, + _dlnaManager, + _userManager, + _imageProcessor, + serverAddress, + accessToken, + _deviceDiscovery, + _userDataManager, + _localization, + _mediaSourceManager, + _config, + _mediaEncoder); + + sessionInfo.AddController(controller); controller.Init(device); @@ -208,31 +230,21 @@ namespace Emby.Dlna.PlayTo GeneralCommandType.ToggleMute.ToString(), GeneralCommandType.SetVolume.ToString(), GeneralCommandType.SetAudioStreamIndex.ToString(), - GeneralCommandType.SetSubtitleStreamIndex.ToString() + GeneralCommandType.SetSubtitleStreamIndex.ToString(), + GeneralCommandType.PlayMediaSource.ToString() }, - SupportsMediaControl = true, - - // xbox one creates a new uuid everytime it restarts - SupportsPersistentIdentifier = (device.Properties.ModelName ?? string.Empty).IndexOf("xbox", StringComparison.OrdinalIgnoreCase) == -1 + SupportsMediaControl = true }); _logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName); } } - private Task GetServerAddress(IpAddressInfo address, CancellationToken cancellationToken) - { - if (address == null) - { - return _appHost.GetLocalApiUrl(cancellationToken); - } - - return Task.FromResult(_appHost.GetLocalApiUrl(address)); - } - public void Dispose() { + _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; + try { _disposeCancellationTokenSource.Cancel(); @@ -243,8 +255,6 @@ namespace Emby.Dlna.PlayTo } _disposed = true; - _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; - GC.SuppressFinalize(this); } } } diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs index d31dc155e9..e2d6e43c04 100644 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs @@ -18,7 +18,7 @@ namespace Emby.Dlna.PlayTo { StreamInfo = new StreamInfo { - ItemId = item.Id.ToString("N"), + ItemId = item.Id, MediaType = DlnaProfileType.Photo, DeviceProfile = profile }, diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index d4b5943674..eaafaa65b1 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -32,7 +32,9 @@ namespace Emby.Dlna.PlayTo bool logRequest = true, string header = null) { - using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest) + var cancellationToken = CancellationToken.None; + + using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest, cancellationToken) .ConfigureAwait(false)) { using (var stream = response.Content) @@ -123,7 +125,8 @@ namespace Emby.Dlna.PlayTo string soapAction, string postData, string header, - bool logRequest) + bool logRequest, + CancellationToken cancellationToken) { if (!soapAction.StartsWith("\"")) soapAction = "\"" + soapAction + "\""; @@ -137,14 +140,16 @@ namespace Emby.Dlna.PlayTo BufferContent = false, // The periodic requests may keep some devices awake - LogRequestAsDebug = true + LogRequestAsDebug = true, + + CancellationToken = cancellationToken }; options.RequestHeaders["SOAPAction"] = soapAction; options.RequestHeaders["Pragma"] = "no-cache"; options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; - if (!string.IsNullOrWhiteSpace(header)) + if (!string.IsNullOrEmpty(header)) { options.RequestHeaders["contentFeatures.dlna.org"] = header; } diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index d7d44573c7..9e055f7921 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -91,7 +91,7 @@ namespace Emby.Dlna.PlayTo }; } - public static StateVariable FromXml(XElement container) + private static StateVariable FromXml(XElement container) { var allowedValues = new List(); var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList") @@ -108,7 +108,7 @@ namespace Emby.Dlna.PlayTo { Name = container.GetValue(uPnpNamespaces.svc + "name"), DataType = container.GetValue(uPnpNamespaces.svc + "dataType"), - AllowedValues = allowedValues + AllowedValues = allowedValues.ToArray() }; } diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index 75204b234d..4007d8870d 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -5,23 +5,21 @@ using MediaBrowser.Model.Extensions; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class DefaultProfile : DeviceProfile { public DefaultProfile() { Name = "Generic Device"; - ProtocolInfo = "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000"; - - XDlnaDoc = "DMS-1.50"; + ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*"; Manufacturer = "Emby"; - ModelDescription = "Emby"; + ModelDescription = "UPnP/AV 1.0 Compliant Media Server"; ModelName = "Emby Server"; - ModelNumber = "Emby"; - ModelUrl = "http://emby.media/"; - ManufacturerUrl = "http://emby.media/"; + ModelNumber = "01"; + ModelUrl = "https://emby.media"; + ManufacturerUrl = "https://emby.media"; AlbumArtPn = "JPEG_SM"; @@ -31,8 +29,8 @@ namespace Emby.Dlna.Profiles MaxIconWidth = 48; MaxIconHeight = 48; - MaxStreamingBitrate = 40000000; - MaxStaticBitrate = 40000000; + MaxStreamingBitrate = 140000000; + MaxStaticBitrate = 140000000; MusicStreamingTranscodingBitrate = 192000; EnableAlbumArtInDidl = false; diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs index 15da48108e..b8a44396a3 100644 --- a/Emby.Dlna/Profiles/DenonAvrProfile.cs +++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class DenonAvrProfile : DefaultProfile { public DenonAvrProfile() diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs index bb9ce903ca..4243c1c9df 100644 --- a/Emby.Dlna/Profiles/DirectTvProfile.cs +++ b/Emby.Dlna/Profiles/DirectTvProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class DirectTvProfile : DefaultProfile { public DirectTvProfile() diff --git a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs index 3f779c6795..c1d1eede25 100644 --- a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs +++ b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class DishHopperJoeyProfile : DefaultProfile { public DishHopperJoeyProfile() diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs index 915c490484..69a5d05ddd 100644 --- a/Emby.Dlna/Profiles/Foobar2000Profile.cs +++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class Foobar2000Profile : DefaultProfile { public Foobar2000Profile() diff --git a/Emby.Dlna/Profiles/LgTvProfile.cs b/Emby.Dlna/Profiles/LgTvProfile.cs index 33185365cf..79cf2ebfd9 100644 --- a/Emby.Dlna/Profiles/LgTvProfile.cs +++ b/Emby.Dlna/Profiles/LgTvProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class LgTvProfile : DefaultProfile { public LgTvProfile() @@ -53,7 +53,7 @@ namespace Emby.Dlna.Profiles { new DirectPlayProfile { - Container = "ts,mpegts,avi,mkv", + Container = "ts,mpegts,avi,mkv,m2ts", VideoCodec = "h264", AudioCodec = "aac,ac3,eac3,mp3,dca,dts", Type = DlnaProfileType.Video @@ -151,12 +151,6 @@ namespace Emby.Dlna.Profiles Value = "1080" }, new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoFramerate, - Value = "30" - }, - new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoLevel, @@ -203,6 +197,12 @@ namespace Emby.Dlna.Profiles Container = "m4v", Type = DlnaProfileType.Video, MimeType = "video/mp4" + }, + new ResponseProfile + { + Container = "ts,mpegts", + Type = DlnaProfileType.Video, + MimeType = "video/mpeg" } }; } diff --git a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs index 83236c594c..ffb735d8db 100644 --- a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs +++ b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs @@ -3,7 +3,7 @@ using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class LinksysDMA2100Profile : DefaultProfile { public LinksysDMA2100Profile() diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs index 0f9f06b888..b5b486a9c9 100644 --- a/Emby.Dlna/Profiles/MarantzProfile.cs +++ b/Emby.Dlna/Profiles/MarantzProfile.cs @@ -3,7 +3,7 @@ using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class MarantzProfile : DefaultProfile { public MarantzProfile() diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs index 1ab4bcf823..441933efad 100644 --- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs +++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class MediaMonkeyProfile : DefaultProfile { public MediaMonkeyProfile() diff --git a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs index a267158c92..53d6a62bba 100644 --- a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs +++ b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class PanasonicVieraProfile : DefaultProfile { public PanasonicVieraProfile() diff --git a/Emby.Dlna/Profiles/PopcornHourProfile.cs b/Emby.Dlna/Profiles/PopcornHourProfile.cs index 33270e72ab..b91089b1bd 100644 --- a/Emby.Dlna/Profiles/PopcornHourProfile.cs +++ b/Emby.Dlna/Profiles/PopcornHourProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class PopcornHourProfile : DefaultProfile { public PopcornHourProfile() diff --git a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs index cd90566322..b55146ffdf 100644 --- a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SamsungSmartTvProfile : DefaultProfile { public SamsungSmartTvProfile() @@ -42,7 +42,7 @@ namespace Emby.Dlna.Profiles }, new TranscodingProfile { - Container = "ts,mpegts", + Container = "ts", AudioCodec = "ac3", VideoCodec = "h264", Type = DlnaProfileType.Video, diff --git a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs index b49ad01977..d27a0e7824 100644 --- a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs +++ b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs @@ -3,7 +3,7 @@ using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SharpSmartTvProfile : DefaultProfile { public SharpSmartTvProfile() diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs index ac7f56b46f..dd2ca7a7d0 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBlurayPlayer2013 : DefaultProfile { public SonyBlurayPlayer2013() diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs index 961ff30f28..69a2f2b618 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBlurayPlayer2014 : DefaultProfile { public SonyBlurayPlayer2014() diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs index 2573121b17..ef443e56ed 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBlurayPlayer2015 : DefaultProfile { public SonyBlurayPlayer2015() diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs index 1ffed3d62c..92e5cb086f 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBlurayPlayer2016 : DefaultProfile { public SonyBlurayPlayer2016() diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs index ddda638eda..2bf65351e1 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBlurayPlayerProfile : DefaultProfile { public SonyBlurayPlayerProfile() diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs index 0986ebdcbd..75382067ff 100644 --- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBravia2010Profile : DefaultProfile { public SonyBravia2010Profile() diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs index ff8316d9bb..75fefa9a86 100644 --- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBravia2011Profile : DefaultProfile { public SonyBravia2011Profile() diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs index c592fae5cb..1d55d91f9e 100644 --- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBravia2012Profile : DefaultProfile { public SonyBravia2012Profile() diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs index 8580c744d1..555ce419b2 100644 --- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBravia2013Profile : DefaultProfile { public SonyBravia2013Profile() diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs index 8f871dee7a..b6e5bbea51 100644 --- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyBravia2014Profile : DefaultProfile { public SonyBravia2014Profile() diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs index 69b0f81ee8..fc69d2dd34 100644 --- a/Emby.Dlna/Profiles/SonyPs3Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs3Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyPs3Profile : DefaultProfile { public SonyPs3Profile() @@ -35,7 +35,6 @@ namespace Emby.Dlna.Profiles AlbumArtPn = "JPEG_TN"; SonyAggregationFlags = "10"; - XDlnaDoc = "DMS-1.50"; EnableSingleAlbumArtLimit = true; DirectPlayProfiles = new[] diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs index 4c4c1f6764..9b444ec243 100644 --- a/Emby.Dlna/Profiles/SonyPs4Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs4Profile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class SonyPs4Profile : DefaultProfile { public SonyPs4Profile() @@ -35,7 +35,6 @@ namespace Emby.Dlna.Profiles AlbumArtPn = "JPEG_TN"; SonyAggregationFlags = "10"; - XDlnaDoc = "DMS-1.50"; EnableSingleAlbumArtLimit = true; DirectPlayProfiles = new[] diff --git a/Emby.Dlna/Profiles/WdtvLiveProfile.cs b/Emby.Dlna/Profiles/WdtvLiveProfile.cs index b4ca5a3d92..bab5576745 100644 --- a/Emby.Dlna/Profiles/WdtvLiveProfile.cs +++ b/Emby.Dlna/Profiles/WdtvLiveProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class WdtvLiveProfile : DefaultProfile { public WdtvLiveProfile() diff --git a/Emby.Dlna/Profiles/XboxOneProfile.cs b/Emby.Dlna/Profiles/XboxOneProfile.cs index d497ee161f..46740cb311 100644 --- a/Emby.Dlna/Profiles/XboxOneProfile.cs +++ b/Emby.Dlna/Profiles/XboxOneProfile.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace Emby.Dlna.Profiles { - [XmlRoot("Profile")] + [System.Xml.Serialization.XmlRoot("Profile")] public class XboxOneProfile : DefaultProfile { public XboxOneProfile() diff --git a/Emby.Dlna/Profiles/Xml/Default.xml b/Emby.Dlna/Profiles/Xml/Default.xml index 77392d9ce1..cdbf3bbe5b 100644 --- a/Emby.Dlna/Profiles/Xml/Default.xml +++ b/Emby.Dlna/Profiles/Xml/Default.xml @@ -2,11 +2,11 @@ Generic Device Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -16,12 +16,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Denon AVR.xml b/Emby.Dlna/Profiles/Xml/Denon AVR.xml index 3ab20c3307..14e89d0063 100644 --- a/Emby.Dlna/Profiles/Xml/Denon AVR.xml +++ b/Emby.Dlna/Profiles/Xml/Denon AVR.xml @@ -7,11 +7,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -21,12 +21,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false @@ -42,7 +41,14 @@ - + + + + + + + + diff --git a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml index f022191a3e..cfd2edfae9 100644 --- a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml +++ b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 10 true true diff --git a/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml b/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml index fa8fd0e744..d95b8dc96b 100644 --- a/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml +++ b/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml @@ -9,11 +9,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -23,11 +23,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:* 0 false diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml index 845ff04153..1c579321b1 100644 --- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 10 false false @@ -35,7 +34,7 @@ false - + @@ -66,7 +65,6 @@ - @@ -82,6 +80,9 @@ + + + diff --git a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml index 410a794328..d98f2bb07f 100644 --- a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml +++ b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml @@ -6,11 +6,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -20,12 +20,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Marantz.xml b/Emby.Dlna/Profiles/Xml/Marantz.xml index 7d2d53ee40..81bea492f6 100644 --- a/Emby.Dlna/Profiles/Xml/Marantz.xml +++ b/Emby.Dlna/Profiles/Xml/Marantz.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml index 944b80f715..a144b34754 100644 --- a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml +++ b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml index 0b842fe2a6..47a1304448 100644 --- a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml +++ b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml @@ -9,11 +9,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -23,12 +23,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 10 false false diff --git a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml index c49184a4c8..a695de8fde 100644 --- a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml +++ b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml @@ -2,11 +2,11 @@ Popcorn Hour Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -16,12 +16,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml index 361d7660bb..6f7a59efcc 100644 --- a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media true true false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false @@ -51,7 +50,7 @@ - + diff --git a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml index 9043330ec0..73c4d2ff57 100644 --- a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml +++ b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 true true diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml index 2734aec9d1..9219569b8f 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml @@ -12,11 +12,11 @@ Microsoft Corporation - http://emby.media/ + https://emby.media Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 - http://emby.media/ + https://emby.media false false false @@ -26,11 +26,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 0 false diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml index 8c5e0a90ec..d7a53832df 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml @@ -12,11 +12,11 @@ Microsoft Corporation - http://emby.media/ + https://emby.media Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 - http://emby.media/ + https://emby.media false false false @@ -26,11 +26,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 0 false diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml index 6d55ef980d..316aa6de2b 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml @@ -10,11 +10,11 @@ Microsoft Corporation - http://emby.media/ + https://emby.media Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 - http://emby.media/ + https://emby.media false false false @@ -24,11 +24,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 0 false diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml index 58312c1c68..f6e93a1914 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml @@ -10,11 +10,11 @@ Microsoft Corporation - http://emby.media/ + https://emby.media Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 - http://emby.media/ + https://emby.media false false false @@ -24,11 +24,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 0 false diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml index 011ba08ea3..f9212cccb1 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml @@ -10,11 +10,11 @@ Microsoft Corporation - http://emby.media/ + https://emby.media Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 - http://emby.media/ + https://emby.media false false false @@ -24,11 +24,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 0 false diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml index c99e21a340..d845317581 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml @@ -11,7 +11,7 @@ Microsoft Corporation http://www.microsoft.com/ Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 http://www.microsoft.com/ true @@ -23,11 +23,10 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 0 diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml index 6defa3c224..8e376fbf9a 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml @@ -11,7 +11,7 @@ Microsoft Corporation http://www.microsoft.com/ Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 http://www.microsoft.com/ true @@ -23,13 +23,12 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml index d90f02a55b..92a04dc7a5 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml @@ -11,7 +11,7 @@ Microsoft Corporation http://www.microsoft.com/ Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 http://www.microsoft.com/ true @@ -23,13 +23,12 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml index ad5d1a6762..953ac0a43a 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml @@ -11,7 +11,7 @@ Microsoft Corporation http://www.microsoft.com/ Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 http://www.microsoft.com/ true @@ -23,13 +23,12 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml index f654982cb2..34afe7e6e1 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml @@ -11,7 +11,7 @@ Microsoft Corporation http://www.microsoft.com/ Windows Media Player Sharing - Emby + UPnP/AV 1.0 Compliant Media Server 3.0 http://www.microsoft.com/ true @@ -23,13 +23,12 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml index 8da880951b..d603010d0a 100644 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml @@ -9,11 +9,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false true false @@ -23,13 +23,12 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml index c192d082e4..9540debff0 100644 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml +++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml @@ -9,11 +9,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false true false @@ -23,13 +23,12 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 10 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Profiles/Xml/WDTV Live.xml b/Emby.Dlna/Profiles/Xml/WDTV Live.xml index 0fc8b99733..79d9ba2ce0 100644 --- a/Emby.Dlna/Profiles/Xml/WDTV Live.xml +++ b/Emby.Dlna/Profiles/Xml/WDTV Live.xml @@ -9,11 +9,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -23,12 +23,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 5 false false diff --git a/Emby.Dlna/Profiles/Xml/Xbox One.xml b/Emby.Dlna/Profiles/Xml/Xbox One.xml index 0b095b2d0c..b05b21455e 100644 --- a/Emby.Dlna/Profiles/Xml/Xbox One.xml +++ b/Emby.Dlna/Profiles/Xml/Xbox One.xml @@ -9,11 +9,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -23,12 +23,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 40 false false diff --git a/Emby.Dlna/Profiles/Xml/foobar2000.xml b/Emby.Dlna/Profiles/Xml/foobar2000.xml index 82ffc916c6..56c04e1e70 100644 --- a/Emby.Dlna/Profiles/Xml/foobar2000.xml +++ b/Emby.Dlna/Profiles/Xml/foobar2000.xml @@ -8,11 +8,11 @@ Emby - http://emby.media/ + https://emby.media Emby Server - Emby - Emby - http://emby.media/ + UPnP/AV 1.0 Compliant Media Server + 01 + https://emby.media false false false @@ -22,12 +22,11 @@ 480 48 48 - 40000000 - 40000000 + 140000000 + 140000000 192000 - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 + http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:* 0 false false diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index bba4adc5f4..988d45e075 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -22,12 +22,12 @@ namespace Emby.Dlna.Server public DescriptionXmlBuilder(DeviceProfile profile, string serverUdn, string serverAddress, string serverName, string serverId) { - if (string.IsNullOrWhiteSpace(serverUdn)) + if (string.IsNullOrEmpty(serverUdn)) { throw new ArgumentNullException("serverUdn"); } - if (string.IsNullOrWhiteSpace(serverAddress)) + if (string.IsNullOrEmpty(serverAddress)) { throw new ArgumentNullException("serverAddress"); } @@ -77,6 +77,11 @@ namespace Emby.Dlna.Server builder.Append("0"); builder.Append(""); + if (!EnableAbsoluteUrls) + { + builder.Append("" + Escape(_serverAddress) + ""); + } + AppendDeviceInfo(builder); builder.Append(""); @@ -90,6 +95,9 @@ namespace Emby.Dlna.Server AppendDeviceProperties(builder); AppendIconList(builder); + + builder.Append("" + Escape(_serverAddress) + "/web/index.html"); + AppendServiceList(builder); builder.Append(""); } @@ -169,12 +177,12 @@ namespace Emby.Dlna.Server private void AppendDeviceProperties(StringBuilder builder) { - builder.Append("urn:schemas-upnp-org:device:MediaServer:1"); - - builder.Append("" + Escape(_profile.XDlnaCap ?? string.Empty) + ""); + builder.Append(""); + builder.Append("DMS-1.50"); builder.Append("M-DMS-1.50"); - builder.Append("" + Escape(_profile.XDlnaDoc ?? string.Empty) + ""); + + builder.Append("urn:schemas-upnp-org:device:MediaServer:1"); builder.Append("" + Escape(GetFriendlyName()) + ""); builder.Append("" + Escape(_profile.Manufacturer ?? string.Empty) + ""); @@ -186,7 +194,7 @@ namespace Emby.Dlna.Server builder.Append("" + Escape(_profile.ModelNumber ?? string.Empty) + ""); builder.Append("" + Escape(_profile.ModelUrl ?? string.Empty) + ""); - if (string.IsNullOrWhiteSpace(_profile.SerialNumber)) + if (string.IsNullOrEmpty(_profile.SerialNumber)) { builder.Append("" + Escape(_serverId) + ""); } @@ -195,15 +203,11 @@ namespace Emby.Dlna.Server builder.Append("" + Escape(_profile.SerialNumber) + ""); } + builder.Append(""); + builder.Append("uuid:" + Escape(_serverUdn) + ""); - builder.Append("" + Escape(_serverAddress) + ""); - if (!EnableAbsoluteUrls) - { - //builder.Append("" + Escape(_serverAddress) + ""); - } - - if (!string.IsNullOrWhiteSpace(_profile.SonyAggregationFlags)) + if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags)) { builder.Append("" + Escape(_profile.SonyAggregationFlags) + ""); } @@ -211,7 +215,7 @@ namespace Emby.Dlna.Server private string GetFriendlyName() { - if (string.IsNullOrWhiteSpace(_profile.FriendlyName)) + if (string.IsNullOrEmpty(_profile.FriendlyName)) { return "Emby - " + _serverName; } @@ -226,7 +230,7 @@ namespace Emby.Dlna.Server } } - var characters = characterList.ToArray(characterList.Count); + var characters = characterList.ToArray(); var serverName = new string(characters); @@ -277,7 +281,7 @@ namespace Emby.Dlna.Server private string BuildUrl(string url) { - if (string.IsNullOrWhiteSpace(url)) + if (string.IsNullOrEmpty(url)) { return string.Empty; } diff --git a/Emby.Dlna/Server/UpnpDevice.cs b/Emby.Dlna/Server/UpnpDevice.cs deleted file mode 100644 index 46f3d1c833..0000000000 --- a/Emby.Dlna/Server/UpnpDevice.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Net; -using MediaBrowser.Model.Net; - -namespace Emby.Dlna.Server -{ - public sealed class UpnpDevice - { - public readonly Uri Descriptor; - public readonly string Type; - public readonly string USN; - public readonly string Uuid; - public readonly IpAddressInfo Address; - - public UpnpDevice(string aUuid, string aType, Uri aDescriptor, IpAddressInfo address) - { - Uuid = aUuid; - Type = aType; - Descriptor = aDescriptor; - - Address = address; - - USN = CreateUSN(aUuid, aType); - } - - private static string CreateUSN(string aUuid, string aType) - { - if (aType.StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) - { - return aType; - } - else - { - return String.Format("uuid:{0}::{1}", aUuid, aType); - } - } - } -} diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 7cd10bd019..b2b742b102 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -255,7 +255,7 @@ namespace Emby.Dlna.Service } var originalHeaders = response.Headers; - var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray(originalHeaders.Count)); + var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray()); //builder.Append(response.Xml); Logger.Debug("Control response. Headers: {0}", headers); diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs index 08eb804033..c41f1b3bed 100644 --- a/Emby.Dlna/Service/ServiceXmlBuilder.cs +++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs @@ -72,7 +72,7 @@ namespace Emby.Dlna.Service builder.Append("" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + ""); builder.Append("" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + ""); - if (item.AllowedValues.Count > 0) + if (item.AllowedValues.Length > 0) { builder.Append(""); foreach (var allowedValue in item.AllowedValues) diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index bd5ad31c24..a75e065c30 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -26,13 +26,38 @@ namespace Emby.Dlna.Ssdp private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - public event EventHandler> DeviceDiscovered; + private event EventHandler> DeviceDiscoveredInternal; + + private int _listenerCount; + private object _syncLock = new object(); + public event EventHandler> DeviceDiscovered + { + add + { + lock (_syncLock) + { + _listenerCount++; + DeviceDiscoveredInternal += value; + } + StartInternal(); + } + remove + { + lock (_syncLock) + { + _listenerCount--; + DeviceDiscoveredInternal -= value; + } + } + } + public event EventHandler> DeviceLeft; private SsdpDeviceLocator _deviceLocator; private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; + private ISsdpCommunicationsServer _commsServer; public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, ISocketFactory socketFactory, ITimerFactory timerFactory) { @@ -45,21 +70,34 @@ namespace Emby.Dlna.Ssdp // Call this method from somewhere in your code to start the search. public void Start(ISsdpCommunicationsServer communicationsServer) { - _deviceLocator = new SsdpDeviceLocator(communicationsServer, _timerFactory); + _commsServer = communicationsServer; - // (Optional) Set the filter so we only see notifications for devices we care about - // (can be any search target value i.e device type, uuid value etc - any value that appears in the - // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). - //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; + StartInternal(); + } - // Connect our event handler so we process devices as they are found - _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; - _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; + private void StartInternal() + { + lock (_syncLock) + { + if (_listenerCount > 0 && _deviceLocator == null) + { + _deviceLocator = new SsdpDeviceLocator(_commsServer, _timerFactory); - var dueTime = TimeSpan.FromSeconds(5); - var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds); + // (Optional) Set the filter so we only see notifications for devices we care about + // (can be any search target value i.e device type, uuid value etc - any value that appears in the + // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). + //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; - _deviceLocator.RestartBroadcastTimer(dueTime, interval); + // Connect our event handler so we process devices as they are found + _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; + _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; + + var dueTime = TimeSpan.FromSeconds(5); + var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds); + + _deviceLocator.RestartBroadcastTimer(dueTime, interval); + } + } } // Process each found device in the event handler @@ -81,7 +119,7 @@ namespace Emby.Dlna.Ssdp } }; - EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger); + EventHelper.FireEventIfNotNull(DeviceDiscoveredInternal, this, args, _logger); } private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e) diff --git a/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj b/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj index c4cef629ad..8f3a70f5bf 100644 --- a/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj +++ b/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj @@ -1,82 +1,22 @@ - - - + + - Debug - AnyCPU - {6CFEE013-6E7C-432B-AC37-CABF0880C69A} - Library - Properties - Emby.Drawing.ImageMagick - Emby.Drawing.ImageMagick - v4.5.2 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + netcoreapp2.1 + false + - - ..\packages\ImageMagickSharp.1.0.0.19\lib\net45\ImageMagickSharp.dll - - - - - - - - - + + - - - - - - - + + + + - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - - - - - - \ No newline at end of file + + diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs index 3b3f7cf0ab..9a313d9d3f 100644 --- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs +++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs @@ -138,7 +138,7 @@ namespace Emby.Drawing.ImageMagick // Even if the caller specified 100, don't use it because it takes forever quality = Math.Min(quality, 99); - if (string.IsNullOrWhiteSpace(options.BackgroundColor) || !HasTransparency(inputPath)) + if (string.IsNullOrEmpty(options.BackgroundColor) || !HasTransparency(inputPath)) { using (var originalImage = new MagickWand(inputPath)) { @@ -216,7 +216,7 @@ namespace Emby.Drawing.ImageMagick private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options) { - if (string.IsNullOrWhiteSpace(options.ForegroundLayer)) + if (string.IsNullOrEmpty(options.ForegroundLayer)) { return; } @@ -328,7 +328,6 @@ namespace Emby.Drawing.ImageMagick { _disposed = true; Wand.CloseEnvironment(); - GC.SuppressFinalize(this); } private void CheckDisposed() diff --git a/Emby.Drawing.ImageMagick/packages.config b/Emby.Drawing.ImageMagick/packages.config deleted file mode 100644 index aaf7ab7bac..0000000000 --- a/Emby.Drawing.ImageMagick/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Emby.Drawing.Net/Emby.Drawing.Net.csproj b/Emby.Drawing.Net/Emby.Drawing.Net.csproj index 16e72a085b..dc9d35dcaf 100644 --- a/Emby.Drawing.Net/Emby.Drawing.Net.csproj +++ b/Emby.Drawing.Net/Emby.Drawing.Net.csproj @@ -1,78 +1,17 @@ - - - + + + + + + + + - Debug - AnyCPU - {C97A239E-A96C-4D64-A844-CCF8CC30AECB} - Library - Properties - Emby.Drawing.Net - Emby.Drawing.Net - v4.5.2 - 512 + netstandard2.0 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - + - - - \ No newline at end of file + + diff --git a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj index 2b61561cbc..55dbf876a2 100644 --- a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj +++ b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj @@ -1,78 +1,22 @@ - - - + + - 11.0 - Debug - AnyCPU - {2312DA6D-FF86-4597-9777-BCEEC32D96DD} - Library - Properties - Emby.Drawing.Skia - Emby.Drawing.Skia - en-US - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + netcoreapp2.1 + false + - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - + + - - Properties\SharedVersion.cs - - - - - - - + + + + - - ..\packages\SkiaSharp.1.58.1\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll - + - - - - - - \ No newline at end of file + + diff --git a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs index 2417043d60..a4d8a29383 100644 --- a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs +++ b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -2,14 +2,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Model.Drawing; -using System; -using System.IO; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; -using System.Reflection; -using MediaBrowser.Common.Progress; namespace Emby.Drawing.Skia { @@ -28,7 +21,7 @@ namespace Emby.Drawing.Skia _fileSystem = fileSystem; } - public async Task DrawPlayedIndicator(SKCanvas canvas, ImageSize imageSize) + public void DrawPlayedIndicator(SKCanvas canvas, ImageSize imageSize) { var x = imageSize.Width - OffsetFromTopRightCorner; @@ -61,35 +54,5 @@ namespace Emby.Drawing.Skia canvas.DrawText(text, (float)x-20, OffsetFromTopRightCorner + 12, paint); } } - - internal static async Task DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient, IFileSystem fileSystem) - { - var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); - - if (fileSystem.FileExists(filePath)) - { - return filePath; - } - - var tempPath = await httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - Progress = new SimpleProgress() - - }).ConfigureAwait(false); - - fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath)); - - try - { - fileSystem.CopyFile(tempPath, filePath, false); - } - catch (IOException) - { - - } - - return tempPath; - } } } diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs index 9b4f1fc58d..7ccb75ec47 100644 --- a/Emby.Drawing.Skia/SkiaEncoder.cs +++ b/Emby.Drawing.Skia/SkiaEncoder.cs @@ -57,9 +57,13 @@ namespace Emby.Drawing.Skia "pkm", "wbmp", + // TODO + // Are all of these supported? https://github.com/google/skia/blob/master/infra/bots/recipes/test.py#L454 + // working on windows at least "cr2", - "nef" + "nef", + "arw" }; } } @@ -68,7 +72,7 @@ namespace Emby.Drawing.Skia { get { - return new[] { ImageFormat.Webp, ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png, ImageFormat.Bmp }; + return new[] { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png }; } } @@ -224,13 +228,42 @@ namespace Emby.Drawing.Skia var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path) ?? string.Empty); + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath)); fileSystem.CopyFile(path, tempPath, true); return tempPath; } + private static SKCodecOrigin GetSKCodecOrigin(ImageOrientation? orientation) + { + if (!orientation.HasValue) + { + return SKCodecOrigin.TopLeft; + } + + switch (orientation.Value) + { + case ImageOrientation.TopRight: + return SKCodecOrigin.TopRight; + case ImageOrientation.RightTop: + return SKCodecOrigin.RightTop; + case ImageOrientation.RightBottom: + return SKCodecOrigin.RightBottom; + case ImageOrientation.LeftTop: + return SKCodecOrigin.LeftTop; + case ImageOrientation.LeftBottom: + return SKCodecOrigin.LeftBottom; + case ImageOrientation.BottomRight: + return SKCodecOrigin.BottomRight; + case ImageOrientation.BottomLeft: + return SKCodecOrigin.BottomLeft; + default: + return SKCodecOrigin.TopLeft; + } + } + private static string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" }; - internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, out SKCodecOrigin origin) + internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, ImageOrientation? orientation, out SKCodecOrigin origin) { if (!fileSystem.FileExists(path)) { @@ -247,7 +280,7 @@ namespace Emby.Drawing.Skia { if (codec == null) { - origin = SKCodecOrigin.TopLeft; + origin = GetSKCodecOrigin(orientation); return null; } @@ -258,12 +291,12 @@ namespace Emby.Drawing.Skia { // decode codec.GetPixels(bitmap.Info, bitmap.GetPixels()); - + origin = codec.Origin; } else { - origin = SKCodecOrigin.TopLeft; + origin = GetSKCodecOrigin(orientation); } return bitmap; @@ -275,7 +308,7 @@ namespace Emby.Drawing.Skia if (resultBitmap == null) { - return Decode(path, true, fileSystem, out origin); + return Decode(path, true, fileSystem, orientation, out origin); } // If we have to resize these they often end up distorted @@ -283,7 +316,7 @@ namespace Emby.Drawing.Skia { using (resultBitmap) { - return Decode(path, true, fileSystem, out origin); + return Decode(path, true, fileSystem, orientation, out origin); } } @@ -291,26 +324,26 @@ namespace Emby.Drawing.Skia return resultBitmap; } - private SKBitmap GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, out SKCodecOrigin origin) + private SKBitmap GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKCodecOrigin origin) { if (cropWhitespace) { - using (var bitmap = Decode(path, forceAnalyzeBitmap, _fileSystem, out origin)) + using (var bitmap = Decode(path, forceAnalyzeBitmap, _fileSystem, orientation, out origin)) { return CropWhiteSpace(bitmap); } } - return Decode(path, forceAnalyzeBitmap, _fileSystem, out origin); + return Decode(path, forceAnalyzeBitmap, _fileSystem, orientation, out origin); } - private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient) + private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation) { SKCodecOrigin origin; if (autoOrient) { - var bitmap = GetBitmap(path, cropWhitespace, true, out origin); + var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out origin); if (bitmap != null) { @@ -326,7 +359,7 @@ namespace Emby.Drawing.Skia return bitmap; } - return GetBitmap(path, cropWhitespace, false, out origin); + return GetBitmap(path, cropWhitespace, false, orientation, out origin); } private SKBitmap OrientImage(SKBitmap bitmap, SKCodecOrigin origin) @@ -496,7 +529,7 @@ namespace Emby.Drawing.Skia var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); - using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient)) + using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) { if (bitmap == null) { @@ -621,8 +654,7 @@ namespace Emby.Drawing.Skia if (options.AddPlayedIndicator) { - var task = new PlayedIndicatorDrawer(_appPaths, _httpClientFactory(), _fileSystem).DrawPlayedIndicator(canvas, currentImageSize); - Task.WaitAll(task); + new PlayedIndicatorDrawer(_appPaths, _httpClientFactory(), _fileSystem).DrawPlayedIndicator(canvas, currentImageSize); } else if (options.UnplayedCount.HasValue) { diff --git a/Emby.Drawing.Skia/StripCollageBuilder.cs b/Emby.Drawing.Skia/StripCollageBuilder.cs index d562bb4be3..85eeaa9f59 100644 --- a/Emby.Drawing.Skia/StripCollageBuilder.cs +++ b/Emby.Drawing.Skia/StripCollageBuilder.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Configuration; using System; using System.IO; using MediaBrowser.Model.IO; +using System.Collections.Generic; namespace Emby.Drawing.Skia { @@ -82,9 +83,17 @@ namespace Emby.Drawing.Skia for (int i = 0; i < 4; i++) { - SKCodecOrigin origin; - using (var currentBitmap = SkiaEncoder.Decode(paths[imageIndex], false, _fileSystem, out origin)) + int newIndex; + + using (var currentBitmap = GetNextValidImage(paths, imageIndex, out newIndex)) { + imageIndex = newIndex; + + if (currentBitmap == null) + { + continue; + } + // resize to the same aspect as the original int iWidth = (int)Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) @@ -98,7 +107,12 @@ namespace Emby.Drawing.Skia using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight))) { // draw image onto canvas - canvas.DrawImage(subset, (horizontalImagePadding * (i + 1)) + (iSlice * i), verticalSpacing); + canvas.DrawImage(subset ?? image, (horizontalImagePadding * (i + 1)) + (iSlice * i), verticalSpacing); + + if (subset == null) + { + continue; + } using (var croppedBitmap = SKBitmap.FromImage(subset)) { @@ -140,17 +154,41 @@ namespace Emby.Drawing.Skia } } } - - imageIndex++; - - if (imageIndex >= paths.Length) - imageIndex = 0; } } return bitmap; } + private SKBitmap GetNextValidImage(string[] paths, int currentIndex, out int newIndex) + { + Dictionary imagesTested = new Dictionary(); + SKBitmap bitmap = null; + + while (imagesTested.Count < paths.Length) + { + if (currentIndex >= paths.Length) + { + currentIndex = 0; + } + + SKCodecOrigin origin; + bitmap = SkiaEncoder.Decode(paths[currentIndex], false, _fileSystem, null, out origin); + + imagesTested[currentIndex] = 0; + + currentIndex++; + + if (bitmap != null) + { + break; + } + } + + newIndex = currentIndex; + return bitmap; + } + private SKBitmap BuildSquareCollageBitmap(string[] paths, int width, int height) { var bitmap = new SKBitmap(width, height); @@ -165,8 +203,17 @@ namespace Emby.Drawing.Skia for (var y = 0; y < 2; y++) { SKCodecOrigin origin; - using (var currentBitmap = SkiaEncoder.Decode(paths[imageIndex], false, _fileSystem, out origin)) + int newIndex; + + using (var currentBitmap = GetNextValidImage(paths, imageIndex, out newIndex)) { + imageIndex = newIndex; + + if (currentBitmap == null) + { + continue; + } + using (var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) { // scale image @@ -178,10 +225,6 @@ namespace Emby.Drawing.Skia canvas.DrawBitmap(resizedBitmap, xPos, yPos); } } - imageIndex++; - - if (imageIndex >= paths.Length) - imageIndex = 0; } } } diff --git a/Emby.Drawing.Skia/packages.config b/Emby.Drawing.Skia/packages.config deleted file mode 100644 index 2b9b0aee43..0000000000 --- a/Emby.Drawing.Skia/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Emby.Drawing/Common/ImageHeader.cs b/Emby.Drawing/Common/ImageHeader.cs index 4f56498a50..121e20debd 100644 --- a/Emby.Drawing/Common/ImageHeader.cs +++ b/Emby.Drawing/Common/ImageHeader.cs @@ -27,10 +27,10 @@ namespace Emby.Drawing.Common /// The image format decoders /// private static readonly KeyValuePair>[] ImageFormatDecoders = new Dictionary> - { - { new byte[] { 0x42, 0x4D }, DecodeBitmap }, - { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, - { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, + { + { new byte[] { 0x42, 0x4D }, DecodeBitmap }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, { new byte[] { 0xff, 0xd8 }, DecodeJfif } @@ -38,6 +38,8 @@ namespace Emby.Drawing.Common private static readonly int MaxMagicBytesLength = ImageFormatDecoders.Select(i => i.Key.Length).OrderByDescending(i => i).First(); + private static string[] SupportedExtensions = new string[] { ".jpg", ".jpeg", ".png", ".gif" }; + /// /// Gets the dimensions of an image. /// @@ -48,6 +50,17 @@ namespace Emby.Drawing.Common /// The image was of an unrecognised format. public static ImageSize GetDimensions(string path, ILogger logger, IFileSystem fileSystem) { + var extension = Path.GetExtension(path); + + if (string.IsNullOrEmpty(extension)) + { + throw new ArgumentException("ImageHeader doesn't support image file"); + } + if (!SupportedExtensions.Contains(extension)) + { + throw new ArgumentException("ImageHeader doesn't support " + extension); + } + using (var fs = fileSystem.OpenRead(path)) { using (var binaryReader = new BinaryReader(fs)) diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index c5dd671a85..e4c9a0c351 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -1,67 +1,17 @@ - - - + + + + + + + + + + + - Debug - AnyCPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716} - Library - Properties - Emby.Drawing - Emby.Drawing - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - 512 - ..\ + netcoreapp2.1 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\SharedVersion.cs - - - - - - - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - - - - \ No newline at end of file + + diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index f91cb4675c..eeb5b8f9f6 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -169,7 +169,7 @@ namespace Emby.Drawing return _imageEncoder.SupportedOutputFormats; } - private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp" }; + private readonly string[] TransparentImageTypes = new string[] { ".png", ".webp", ".gif" }; public bool SupportsTransparency(string path) { return TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); @@ -183,7 +183,7 @@ namespace Emby.Drawing } var originalImage = options.Image; - IHasMetadata item = options.Item; + var item = options.Item; if (!originalImage.IsLocalFile) { @@ -196,6 +196,7 @@ namespace Emby.Drawing var originalImagePath = originalImage.Path; var dateModified = originalImage.DateModified; + var originalImageSize = originalImage.Width > 0 && originalImage.Height > 0 ? new ImageSize(originalImage.Width, originalImage.Height) : (ImageSize?)null; if (!_imageEncoder.SupportsImageEncoding) { @@ -207,7 +208,7 @@ namespace Emby.Drawing dateModified = supportedImageInfo.Item2; var requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath) ?? string.Empty); - if (options.Enhancers.Count > 0) + if (options.Enhancers.Length > 0) { if (item == null) { @@ -225,18 +226,32 @@ namespace Emby.Drawing originalImagePath = tuple.Item1; dateModified = tuple.Item2; requiresTransparency = tuple.Item3; + // TODO: Get this info + originalImageSize = null; } var photo = item as Photo; var autoOrient = false; ImageOrientation? orientation = null; - if (photo != null && photo.Orientation.HasValue && photo.Orientation.Value != ImageOrientation.TopLeft) + if (photo != null) { - autoOrient = true; - orientation = photo.Orientation; + if (photo.Orientation.HasValue) + { + if (photo.Orientation.Value != ImageOrientation.TopLeft) + { + autoOrient = true; + orientation = photo.Orientation; + } + } + else + { + // Orientation unknown, so do it + autoOrient = true; + orientation = photo.Orientation; + } } - if (options.HasDefaultOptions(originalImagePath) && (!autoOrient || !options.RequiresAutoOrientation)) + if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation)) { // Just spit out the original file if all the options are default return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); @@ -247,7 +262,7 @@ namespace Emby.Drawing //{ // // Just spit out the original file if all the options are default // _logger.Info("Returning original image {0}", originalImagePath); - // return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + // return new ValueTuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); //} var newSize = ImageHelper.GetNewImageSize(options, null); @@ -317,7 +332,7 @@ namespace Emby.Drawing } // If transparency is needed and webp isn't supported, than png is the only option - if (requiresTransparency) + if (requiresTransparency && clientSupportedFormats.Contains(ImageFormat.Png)) { return ImageFormat.Png; } @@ -346,21 +361,6 @@ namespace Emby.Drawing } } - //private static int[][] OPERATIONS = new int[][] { - // TopLeft - //new int[] { 0, NONE}, - // TopRight - //new int[] { 0, HORIZONTAL}, - //new int[] {180, NONE}, - // LeftTop - //new int[] { 0, VERTICAL}, - //new int[] { 90, HORIZONTAL}, - // RightTop - //new int[] { 90, NONE}, - //new int[] {-90, HORIZONTAL}, - //new int[] {-90, NONE}, - //}; - private string GetMimeType(ImageFormat format, string path) { if (format == ImageFormat.Bmp) @@ -494,55 +494,19 @@ namespace Emby.Drawing throw new ArgumentNullException("path"); } - return GetImageSizeInternal(path, allowSlowMethod); - } - - /// - /// Gets the image size internal. - /// - /// The path. - /// if set to true [allow slow method]. - /// ImageSize. - private ImageSize GetImageSizeInternal(string path, bool allowSlowMethod) - { - //try - //{ - // using (var fileStream = _fileSystem.OpenRead(path)) - // { - // using (var file = TagLib.File.Create(new StreamFileAbstraction(Path.GetFileName(path), fileStream, null))) - // { - // var image = file as TagLib.Image.File; - - // if (image != null) - // { - // var properties = image.Properties; - - // return new ImageSize - // { - // Height = properties.PhotoHeight, - // Width = properties.PhotoWidth - // }; - // } - // } - // } - //} - //catch - //{ - //} - try { return ImageHeader.GetDimensions(path, _logger, _fileSystem); } catch { - if (allowSlowMethod) + if (!allowSlowMethod) { - return _imageEncoder.GetImageSize(path); + throw; } - - throw; } + + return _imageEncoder.GetImageSize(path); } /// @@ -552,23 +516,30 @@ namespace Emby.Drawing /// The image. /// Guid. /// item - public string GetImageCacheTag(IHasMetadata item, ItemImageInfo image) + public string GetImageCacheTag(BaseItem item, ItemImageInfo image) { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (image == null) - { - throw new ArgumentNullException("image"); - } - var supportedEnhancers = GetSupportedEnhancers(item, image.Type); return GetImageCacheTag(item, image, supportedEnhancers); } + public string GetImageCacheTag(BaseItem item, ChapterInfo chapter) + { + try + { + return GetImageCacheTag(item, new ItemImageInfo + { + Path = chapter.ImagePath, + Type = ImageType.Chapter, + DateModified = chapter.ImageDateModified + }); + } + catch + { + return null; + } + } + /// /// Gets the image cache tag. /// @@ -577,29 +548,14 @@ namespace Emby.Drawing /// The image enhancers. /// Guid. /// item - public string GetImageCacheTag(IHasMetadata item, ItemImageInfo image, List imageEnhancers) + public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers) { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (imageEnhancers == null) - { - throw new ArgumentNullException("imageEnhancers"); - } - - if (image == null) - { - throw new ArgumentNullException("image"); - } - var originalImagePath = image.Path; var dateModified = image.DateModified; var imageType = image.Type; // Optimization - if (imageEnhancers.Count == 0) + if (imageEnhancers.Length == 0) { return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N"); } @@ -611,7 +567,7 @@ namespace Emby.Drawing return string.Join("|", cacheKeys.ToArray(cacheKeys.Count)).GetMD5().ToString("N"); } - private async Task> GetSupportedImage(string originalImagePath, DateTime dateModified) + private async Task> GetSupportedImage(string originalImagePath, DateTime dateModified) { var inputFormat = (Path.GetExtension(originalImagePath) ?? string.Empty) .TrimStart('.') @@ -620,7 +576,7 @@ namespace Emby.Drawing // These are just jpg files renamed as tbn if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) { - return new Tuple(originalImagePath, dateModified); + return new ValueTuple(originalImagePath, dateModified); } if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase)) @@ -651,7 +607,7 @@ namespace Emby.Drawing } } - return new Tuple(originalImagePath, dateModified); + return new ValueTuple(originalImagePath, dateModified); } /// @@ -661,7 +617,7 @@ namespace Emby.Drawing /// Type of the image. /// Index of the image. /// Task{System.String}. - public async Task GetEnhancedImage(IHasMetadata item, ImageType imageType, int imageIndex) + public async Task GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex) { var enhancers = GetSupportedEnhancers(item, imageType); @@ -674,11 +630,11 @@ namespace Emby.Drawing return result.Item1; } - private async Task> GetEnhancedImage(ItemImageInfo image, + private async Task> GetEnhancedImage(ItemImageInfo image, bool inputImageSupportsTransparency, - IHasMetadata item, + BaseItem item, int imageIndex, - List enhancers, + IImageEnhancer[] enhancers, CancellationToken cancellationToken) { var originalImagePath = image.Path; @@ -699,7 +655,7 @@ namespace Emby.Drawing { var treatmentRequiresTransparency = ehnancedImageInfo.Item2; - return new Tuple(ehnancedImagePath, _fileSystem.GetLastWriteTimeUtc(ehnancedImagePath), treatmentRequiresTransparency); + return new ValueTuple(ehnancedImagePath, _fileSystem.GetLastWriteTimeUtc(ehnancedImagePath), treatmentRequiresTransparency); } } catch (Exception ex) @@ -707,7 +663,7 @@ namespace Emby.Drawing _logger.ErrorException("Error enhancing image", ex); } - return new Tuple(originalImagePath, dateModified, inputImageSupportsTransparency); + return new ValueTuple(originalImagePath, dateModified, inputImageSupportsTransparency); } /// @@ -725,11 +681,11 @@ namespace Emby.Drawing /// or /// item /// - private async Task> GetEnhancedImageInternal(string originalImagePath, - IHasMetadata item, + private async Task> GetEnhancedImageInternal(string originalImagePath, + BaseItem item, ImageType imageType, int imageIndex, - List supportedEnhancers, + IImageEnhancer[] supportedEnhancers, string cacheGuid, CancellationToken cancellationToken) { @@ -753,8 +709,8 @@ namespace Emby.Drawing } // All enhanced images are saved as png to allow transparency - var cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ? - ".webp" : + var cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ? + ".webp" : (treatmentRequiresTransparency ? ".png" : ".jpg"); var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension); @@ -768,14 +724,14 @@ namespace Emby.Drawing // Check again in case of contention if (_fileSystem.FileExists(enhancedImagePath)) { - return new Tuple(enhancedImagePath, treatmentRequiresTransparency); + return new ValueTuple(enhancedImagePath, treatmentRequiresTransparency); } _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath)); await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false); - return new Tuple(enhancedImagePath, treatmentRequiresTransparency); + return new ValueTuple(enhancedImagePath, treatmentRequiresTransparency); } finally { @@ -793,7 +749,7 @@ namespace Emby.Drawing /// Type of the image. /// Index of the image. /// Task{EnhancedImage}. - private async Task ExecuteImageEnhancers(IEnumerable imageEnhancers, string inputPath, string outputPath, IHasMetadata item, ImageType imageType, int imageIndex) + private async Task ExecuteImageEnhancers(IEnumerable imageEnhancers, string inputPath, string outputPath, BaseItem item, ImageType imageType, int imageIndex) { // Run the enhancers sequentially in order of priority foreach (var enhancer in imageEnhancers) @@ -878,9 +834,9 @@ namespace Emby.Drawing _logger.Info("Completed creation of image collage and saved to {0}", options.OutputPath); } - public List GetSupportedEnhancers(IHasMetadata item, ImageType imageType) + public IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType) { - var list = new List(); + List list = null; foreach (var i in ImageEnhancers) { @@ -888,6 +844,10 @@ namespace Emby.Drawing { if (i.Supports(item, imageType)) { + if (list == null) + { + list = new List(); + } list.Add(i); } } @@ -896,7 +856,8 @@ namespace Emby.Drawing _logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name); } } - return list; + + return list == null ? Array.Empty() : list.ToArray(); } private Dictionary _locks = new Dictionary(); @@ -948,8 +909,6 @@ namespace Emby.Drawing { disposable.Dispose(); } - - GC.SuppressFinalize(this); } private void CheckDisposed() diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs new file mode 100644 index 0000000000..c88631a0c8 --- /dev/null +++ b/Emby.Naming/Audio/AlbumParser.cs @@ -0,0 +1,65 @@ +using Emby.Naming.Common; +using Emby.Naming.Video; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Audio +{ + public class AlbumParser + { + private readonly NamingOptions _options; + + public AlbumParser(NamingOptions options) + { + _options = options; + } + + public MultiPartResult ParseMultiPart(string path) + { + var result = new MultiPartResult(); + + var filename = Path.GetFileName(path); + + if (string.IsNullOrEmpty(filename)) + { + return result; + } + + // TODO: Move this logic into options object + // Even better, remove the prefixes and come up with regexes + // But Kodi documentation seems to be weak for audio + + // Normalize + // Remove whitespace + filename = filename.Replace("-", " "); + filename = filename.Replace(".", " "); + filename = filename.Replace("(", " "); + filename = filename.Replace(")", " "); + filename = Regex.Replace(filename, @"\s+", " "); + + filename = filename.TrimStart(); + + foreach (var prefix in _options.AlbumStackingPrefixes) + { + if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) == 0) + { + var tmp = filename.Substring(prefix.Length); + + tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty; + + int val; + if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + { + result.IsMultiPart = true; + break; + } + } + } + + return result; + } + } +} diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs new file mode 100644 index 0000000000..20016915ae --- /dev/null +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; +using System.Linq; +using Emby.Naming.Common; + +namespace Emby.Naming.Audio +{ + public class AudioFileParser + { + private readonly NamingOptions _options; + + public AudioFileParser(NamingOptions options) + { + _options = options; + } + + public bool IsAudioFile(string path) + { + var extension = Path.GetExtension(path) ?? string.Empty; + return _options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/Emby.Naming/Audio/MultiPartResult.cs b/Emby.Naming/Audio/MultiPartResult.cs new file mode 100644 index 0000000000..fae0ae4d81 --- /dev/null +++ b/Emby.Naming/Audio/MultiPartResult.cs @@ -0,0 +1,22 @@ + +namespace Emby.Naming.Audio +{ + public class MultiPartResult + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the part. + /// + /// The part. + public string Part { get; set; } + /// + /// Gets or sets a value indicating whether this instance is multi part. + /// + /// true if this instance is multi part; otherwise, false. + public bool IsMultiPart { get; set; } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs new file mode 100644 index 0000000000..88a98d0f1e --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -0,0 +1,49 @@ + +using System; +using Emby.Naming.Video; + +namespace Emby.Naming.AudioBook +{ + /// + /// Represents a single video file + /// + public class AudioBookFileInfo : IComparable + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets the container. + /// + /// The container. + public string Container { get; set; } + /// + /// Gets or sets the part number. + /// + /// The part number. + public int? PartNumber { get; set; } + /// + /// Gets or sets the chapter number. + /// + /// The chapter number. + public int? ChapterNumber { get; set; } + /// + /// Gets or sets the type. + /// + /// The type. + public bool IsDirectory { get; set; } + + public int CompareTo(AudioBookFileInfo other) + { + if (ReferenceEquals(this, other)) return 0; + if (ReferenceEquals(null, other)) return 1; + var chapterNumberComparison = Nullable.Compare(ChapterNumber, other.ChapterNumber); + if (chapterNumberComparison != 0) return chapterNumberComparison; + var partNumberComparison = Nullable.Compare(PartNumber, other.PartNumber); + if (partNumberComparison != 0) return partNumberComparison; + return string.Compare(Path, other.Path, StringComparison.Ordinal); + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs new file mode 100644 index 0000000000..b4805edd21 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Emby.Naming.Common; +using Emby.Naming.TV; + +namespace Emby.Naming.AudioBook +{ + public class AudioBookFilePathParser + { + private readonly NamingOptions _options; + + public AudioBookFilePathParser(NamingOptions options) + { + _options = options; + } + + public AudioBookFilePathParserResult Parse(string path, bool IsDirectory) + { + AudioBookFilePathParserResult result = Parse(path); + return !result.Success ? new AudioBookFilePathParserResult() : result; + } + + private AudioBookFilePathParserResult Parse(string path) + { + var result = new AudioBookFilePathParserResult(); + var fileName = Path.GetFileNameWithoutExtension(path); + foreach (var expression in _options.AudioBookPartsExpressions) + { + var match = new Regex(expression, RegexOptions.IgnoreCase).Match(fileName); + if (match.Success) + { + if (!result.ChapterNumber.HasValue) + { + var value = match.Groups["chapter"]; + if (value.Success) + { + int intValue; + if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue)) + { + result.ChapterNumber = intValue; + } + } + } + if (!result.PartNumber.HasValue) + { + var value = match.Groups["part"]; + if (value.Success) + { + int intValue; + if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue)) + { + result.ChapterNumber = intValue; + } + } + } + } + } + + /*var matches = _iRegexProvider.GetRegex("\\d+", RegexOptions.IgnoreCase).Matches(fileName); + if (matches.Count > 0) + { + if (!result.ChapterNumber.HasValue) + { + result.ChapterNumber = int.Parse(matches[0].Groups[0].Value); + } + if (matches.Count > 1) + { + result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value); + } + }*/ + result.Success = result.PartNumber.HasValue || result.ChapterNumber.HasValue; + + return result; + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs new file mode 100644 index 0000000000..759e7b8ad2 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Emby.Naming.AudioBook +{ + public class AudioBookFilePathParserResult + { + public int? PartNumber { get; set; } + public int? ChapterNumber { get; set; } + public bool Success { get; set; } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs new file mode 100644 index 0000000000..e039e5359a --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Emby.Naming.Video; + +namespace Emby.Naming.AudioBook +{ + /// + /// Represents a complete video, including all parts and subtitles + /// + public class AudioBookInfo + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + public int? Year { get; set; } + /// + /// Gets or sets the files. + /// + /// The files. + public List Files { get; set; } + /// + /// Gets or sets the extras. + /// + /// The extras. + public List Extras { get; set; } + /// + /// Gets or sets the alternate versions. + /// + /// The alternate versions. + public List AlternateVersions { get; set; } + + public AudioBookInfo() + { + Files = new List(); + Extras = new List(); + AlternateVersions = new List(); + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs new file mode 100644 index 0000000000..8cf6a03bd8 --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Emby.Naming.Common; +using Emby.Naming.Video; +using MediaBrowser.Model.IO; + +namespace Emby.Naming.AudioBook +{ + public class AudioBookListResolver + { + private readonly NamingOptions _options; + + public AudioBookListResolver(NamingOptions options) + { + _options = options; + } + + public IEnumerable Resolve(List files) + { + var audioBookResolver = new AudioBookResolver(_options); + + var audiobookFileInfos = files + .Select(i => audioBookResolver.Resolve(i.FullName, i.IsDirectory)) + .Where(i => i != null) + .ToList(); + + // Filter out all extras, otherwise they could cause stacks to not be resolved + // See the unit test TestStackedWithTrailer + var metadata = audiobookFileInfos + .Select(i => new FileSystemMetadata + { + FullName = i.Path, + IsDirectory = i.IsDirectory + }); + + var stackResult = new StackResolver(_options) + .ResolveAudioBooks(metadata); + + var list = new List(); + + foreach (var stack in stackResult.Stacks) + { + var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList(); + stackFiles.Sort(); + var info = new AudioBookInfo + { + Files = stackFiles, + Name = stack.Name + }; + list.Add(info); + } + + // Whatever files are left, just add them + /*list.AddRange(remainingFiles.Select(i => new AudioBookInfo + { + Files = new List { i }, + Name = i., + Year = i.Year + }));*/ + + var orderedList = list.OrderBy(i => i.Name); + + return orderedList; + } + } +} diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs new file mode 100644 index 0000000000..a206ee30bf --- /dev/null +++ b/Emby.Naming/AudioBook/AudioBookResolver.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; +using System.Linq; +using Emby.Naming.Common; +using Emby.Naming.TV; +using Emby.Naming.Video; + +namespace Emby.Naming.AudioBook +{ + public class AudioBookResolver + { + private readonly NamingOptions _options; + + public AudioBookResolver(NamingOptions options) + { + _options = options; + } + + public AudioBookFileInfo ParseFile(string path) + { + return Resolve(path, false); + } + + public AudioBookFileInfo ParseDirectory(string path) + { + return Resolve(path, true); + } + + public AudioBookFileInfo Resolve(string path, bool IsDirectory = false) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + if (IsDirectory) + return null; + + var extension = Path.GetExtension(path) ?? string.Empty; + // Check supported extensions + if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + return null; + } + + var container = extension.TrimStart('.'); + + var parsingResult = new AudioBookFilePathParser(_options) + .Parse(path, IsDirectory); + + return new AudioBookFileInfo + { + Path = path, + Container = container, + PartNumber = parsingResult.PartNumber, + ChapterNumber = parsingResult.ChapterNumber, + IsDirectory = IsDirectory + }; + } + } +} diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs new file mode 100644 index 0000000000..3b9687c60b --- /dev/null +++ b/Emby.Naming/Common/EpisodeExpression.cs @@ -0,0 +1,45 @@ +using System.Text.RegularExpressions; +using System; + +namespace Emby.Naming.Common +{ + public class EpisodeExpression + { + private string _expression; + public string Expression { get { return _expression; } set { _expression = value; _regex = null; } } + + public bool IsByDate { get; set; } + public bool IsOptimistic { get; set; } + public bool IsNamed { get; set; } + public bool SupportsAbsoluteEpisodeNumbers { get; set; } + + public string[] DateTimeFormats { get; set; } + + private Regex _regex; + public Regex Regex + { + get + { + return _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + } + + public EpisodeExpression(string expression, bool byDate) + { + Expression = expression; + IsByDate = byDate; + DateTimeFormats = Array.Empty(); + SupportsAbsoluteEpisodeNumbers = true; + } + + public EpisodeExpression(string expression) + : this(expression, false) + { + } + + public EpisodeExpression() + : this(null) + { + } + } +} diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs new file mode 100644 index 0000000000..2a3d433cf8 --- /dev/null +++ b/Emby.Naming/Common/MediaType.cs @@ -0,0 +1,19 @@ + +namespace Emby.Naming.Common +{ + public enum MediaType + { + /// + /// The audio + /// + Audio = 0, + /// + /// The photo + /// + Photo = 1, + /// + /// The video + /// + Video = 2 + } +} diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs new file mode 100644 index 0000000000..9e65440d08 --- /dev/null +++ b/Emby.Naming/Common/NamingOptions.cs @@ -0,0 +1,687 @@ +using Emby.Naming.Video; +using System.Collections.Generic; +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Common +{ + public class NamingOptions + { + public string[] AudioFileExtensions { get; set; } + public string[] AlbumStackingPrefixes { get; set; } + + public string[] SubtitleFileExtensions { get; set; } + public char[] SubtitleFlagDelimiters { get; set; } + + public string[] SubtitleForcedFlags { get; set; } + public string[] SubtitleDefaultFlags { get; set; } + + public EpisodeExpression[] EpisodeExpressions { get; set; } + public string[] EpisodeWithoutSeasonExpressions { get; set; } + public string[] EpisodeMultiPartExpressions { get; set; } + + public string[] VideoFileExtensions { get; set; } + public string[] StubFileExtensions { get; set; } + + public string[] AudioBookPartsExpressions { get; set; } + + public StubTypeRule[] StubTypes { get; set; } + + public char[] VideoFlagDelimiters { get; set; } + public Format3DRule[] Format3DRules { get; set; } + + public string[] VideoFileStackingExpressions { get; set; } + public string[] CleanDateTimes { get; set; } + public string[] CleanStrings { get; set; } + + + public EpisodeExpression[] MultipleEpisodeExpressions { get; set; } + + public ExtraRule[] VideoExtraRules { get; set; } + + public NamingOptions() + { + VideoFileExtensions = new string[] + { + ".m4v", + ".3gp", + ".nsv", + ".ts", + ".ty", + ".strm", + ".rm", + ".rmvb", + ".ifo", + ".mov", + ".qt", + ".divx", + ".xvid", + ".bivx", + ".vob", + ".nrg", + ".img", + ".iso", + ".pva", + ".wmv", + ".asf", + ".asx", + ".ogm", + ".m2v", + ".avi", + ".bin", + ".dvr-ms", + ".mpg", + ".mpeg", + ".mp4", + ".mkv", + ".avc", + ".vp3", + ".svq3", + ".nuv", + ".viv", + ".dv", + ".fli", + ".flv", + ".001", + ".tp" + }; + + VideoFlagDelimiters = new[] + { + '(', + ')', + '-', + '.', + '_', + '[', + ']' + }; + + StubFileExtensions = new[] + { + ".disc" + }; + + StubTypes = new[] + { + new StubTypeRule + { + StubType = "dvd", + Token = "dvd" + }, + new StubTypeRule + { + StubType = "hddvd", + Token = "hddvd" + }, + new StubTypeRule + { + StubType = "bluray", + Token = "bluray" + }, + new StubTypeRule + { + StubType = "bluray", + Token = "brrip" + }, + new StubTypeRule + { + StubType = "bluray", + Token = "bd25" + }, + new StubTypeRule + { + StubType = "bluray", + Token = "bd50" + }, + new StubTypeRule + { + StubType = "vhs", + Token = "vhs" + }, + new StubTypeRule + { + StubType = "tv", + Token = "HDTV" + }, + new StubTypeRule + { + StubType = "tv", + Token = "PDTV" + }, + new StubTypeRule + { + StubType = "tv", + Token = "DSR" + } + }; + + VideoFileStackingExpressions = new[] + { + "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(.*?)(\\.[^.]+)$", + "(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(.*?)(\\.[^.]+)$", + "(.*?)([ ._-]*[a-d])(.*?)(\\.[^.]+)$" + }; + + CleanDateTimes = new[] + { + @"(.+[^ _\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9][0-9]|20[0-1][0-9])([ _\,\.\(\)\[\]\-][^0-9]|$)" + }; + + CleanStrings = new[] + { + @"[ _\,\.\(\)\[\]\-](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"(\[.*\])" + }; + + SubtitleFileExtensions = new[] + { + ".srt", + ".ssa", + ".ass", + ".sub" + }; + + SubtitleFlagDelimiters = new[] + { + '.' + }; + + SubtitleForcedFlags = new[] + { + "foreign", + "forced" + }; + + SubtitleDefaultFlags = new[] + { + "default" + }; + + AlbumStackingPrefixes = new[] + { + "disc", + "cd", + "disk", + "vol", + "volume" + }; + + AudioFileExtensions = new[] + { + ".nsv", + ".m4a", + ".flac", + ".aac", + ".strm", + ".pls", + ".rm", + ".mpa", + ".wav", + ".wma", + ".ogg", + ".opus", + ".mp3", + ".mp2", + ".mod", + ".amf", + ".669", + ".dmf", + ".dsm", + ".far", + ".gdm", + ".imf", + ".it", + ".m15", + ".med", + ".okt", + ".s3m", + ".stm", + ".sfx", + ".ult", + ".uni", + ".xm", + ".sid", + ".ac3", + ".dts", + ".cue", + ".aif", + ".aiff", + ".ape", + ".mac", + ".mpc", + ".mp+", + ".mpp", + ".shn", + ".wv", + ".nsf", + ".spc", + ".gym", + ".adplug", + ".adx", + ".dsp", + ".adp", + ".ymf", + ".ast", + ".afc", + ".hps", + ".xsp", + ".acc", + ".m4b", + ".oga", + ".dsf", + ".mka" + }; + + EpisodeExpressions = new[] + { + // *** Begin Kodi Standard Naming + // + new EpisodeExpression(@".*(\\|\/)(?((?![Ss]([0-9]+)[][ ._-]*[Ee]([0-9]+))[^\\\/])*)?[Ss](?[0-9]+)[][ ._-]*[Ee](?[0-9]+)([^\\/]*)$") + { + IsNamed = true + }, + // + new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"), + new EpisodeExpression("([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})", true) + { + DateTimeFormats = new [] + { + "yyyy.MM.dd", + "yyyy-MM-dd", + "yyyy_MM_dd" + } + }, + new EpisodeExpression("([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})", true) + { + DateTimeFormats = new [] + { + "dd.MM.yyyy", + "dd-MM-yyyy", + "dd_MM_yyyy" + } + }, + + new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$") + { + SupportsAbsoluteEpisodeNumbers = true + }, + new EpisodeExpression(@"[\\\\/\\._ -](?(?![0-9]+[0-9][0-9])([^\\\/])*)[\\\\/\\._ -](?[0-9]+)(?[0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$") + { + IsOptimistic = true, + IsNamed = true, + SupportsAbsoluteEpisodeNumbers = false + }, + new EpisodeExpression("[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$") + { + SupportsAbsoluteEpisodeNumbers = true + }, + + // *** End Kodi Standard Naming + + new EpisodeExpression(@".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)[sS](?\d{1,4})[x,X]?[eE](?\d{1,3})[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))[^\\\/]*$") + { + IsNamed = true + }, + + new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})[^\\\/]*$") + { + IsNamed = true + }, + + // "01.avi" + new EpisodeExpression(@".*[\\\/](?\d{1,3})(-(?\d{2,3}))*\.\w+$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "1-12 episode title" + new EpisodeExpression(@"([0-9]+)-([0-9]+)") + { + }, + + // "01 - blah.avi", "01-blah.avi" + new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\s?-\s?[^\\\/]*$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "01.blah.avi" + new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\.[^\\\/]+$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" + new EpisodeExpression(@".*[\\\/][^\\\/]* - (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$") + { + IsOptimistic = true, + IsNamed = true + }, + + // "01 episode title.avi" + new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?\d{1,3})([^\\\/]*)$") + { + IsOptimistic = true, + IsNamed = true + }, + // "Episode 16", "Episode 16 - Title" + new EpisodeExpression(@".*[\\\/][^\\\/]* (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$") + { + IsOptimistic = true, + IsNamed = true + } + }; + + EpisodeWithoutSeasonExpressions = new[] + { + @"[/\._ \-]()([0-9]+)(-[0-9]+)?" + }; + + EpisodeMultiPartExpressions = new[] + { + @"^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)" + }; + + VideoExtraRules = new[] + { + new ExtraRule + { + ExtraType = "trailer", + RuleType = ExtraRuleType.Filename, + Token = "trailer", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "trailer", + RuleType = ExtraRuleType.Suffix, + Token = "-trailer", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "trailer", + RuleType = ExtraRuleType.Suffix, + Token = ".trailer", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "trailer", + RuleType = ExtraRuleType.Suffix, + Token = "_trailer", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "trailer", + RuleType = ExtraRuleType.Suffix, + Token = " trailer", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "sample", + RuleType = ExtraRuleType.Filename, + Token = "sample", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "sample", + RuleType = ExtraRuleType.Suffix, + Token = "-sample", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "sample", + RuleType = ExtraRuleType.Suffix, + Token = ".sample", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "sample", + RuleType = ExtraRuleType.Suffix, + Token = "_sample", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "sample", + RuleType = ExtraRuleType.Suffix, + Token = " sample", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "themesong", + RuleType = ExtraRuleType.Filename, + Token = "theme", + MediaType = MediaType.Audio + }, + + new ExtraRule + { + ExtraType = "scene", + RuleType = ExtraRuleType.Suffix, + Token = "-scene", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "clip", + RuleType = ExtraRuleType.Suffix, + Token = "-clip", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "interview", + RuleType = ExtraRuleType.Suffix, + Token = "-interview", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "behindthescenes", + RuleType = ExtraRuleType.Suffix, + Token = "-behindthescenes", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "deletedscene", + RuleType = ExtraRuleType.Suffix, + Token = "-deleted", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "featurette", + RuleType = ExtraRuleType.Suffix, + Token = "-featurette", + MediaType = MediaType.Video + }, + new ExtraRule + { + ExtraType = "short", + RuleType = ExtraRuleType.Suffix, + Token = "-short", + MediaType = MediaType.Video + } + + }; + Format3DRules = new[] + { + // Kodi rules: + new Format3DRule + { + PreceedingToken = "3d", + Token = "hsbs" + }, + new Format3DRule + { + PreceedingToken = "3d", + Token = "sbs" + }, + new Format3DRule + { + PreceedingToken = "3d", + Token = "htab" + }, + new Format3DRule + { + PreceedingToken = "3d", + Token = "tab" + }, + // Media Browser rules: + new Format3DRule + { + Token = "fsbs" + }, + new Format3DRule + { + Token = "hsbs" + }, + new Format3DRule + { + Token = "sbs" + }, + new Format3DRule + { + Token = "ftab" + }, + new Format3DRule + { + Token = "htab" + }, + new Format3DRule + { + Token = "tab" + }, + new Format3DRule + { + Token = "sbs3d" + }, + new Format3DRule + { + Token = "mvc" + } + }; + AudioBookPartsExpressions = new[] + { + // Detect specified chapters, like CH 01 + @"ch(?:apter)?[\s_-]?(?\d+)", + // Detect specified parts, like Part 02 + @"p(?:ar)?t[\s_-]?(?\d+)", + // Chapter is often beginning of filename + @"^(?\d+)", + // Part if often ending of filename + @"(?\d+)$", + // Sometimes named as 0001_005 (chapter_part) + @"(?\d+)_(?\d+)", + // Some audiobooks are ripped from cd's, and will be named by disk number. + @"dis(?:c|k)[\s_-]?(?\d+)" + }; + + var extensions = VideoFileExtensions.ToList(); + + extensions.AddRange(new[] + { + ".mkv", + ".m2t", + ".m2ts", + ".img", + ".iso", + ".mk3d", + ".ts", + ".rmvb", + ".mov", + ".avi", + ".mpg", + ".mpeg", + ".wmv", + ".mp4", + ".divx", + ".dvr-ms", + ".wtv", + ".ogm", + ".ogv", + ".asf", + ".m4v", + ".flv", + ".f4v", + ".3gp", + ".webm", + ".mts", + ".m2v", + ".rec", + ".mxf" + }); + + MultipleEpisodeExpressions = new string[] + { + @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[eExX](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})(-[xE]?[eE]?(?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xXeE](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$", + @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$" + + }.Select(i => new EpisodeExpression(i) + { + IsNamed = true + + }).ToArray(); + + VideoFileExtensions = extensions + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + Compile(); + } + + public Regex[] VideoFileStackingRegexes { get; private set; } + public Regex[] CleanDateTimeRegexes { get; private set; } + public Regex[] CleanStringRegexes { get; private set; } + + public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } + public Regex[] EpisodeMultiPartRegexes { get; private set; } + + public void Compile() + { + VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray(); + CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray(); + CleanStringRegexes = CleanStrings.Select(Compile).ToArray(); + EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray(); + EpisodeMultiPartRegexes = EpisodeMultiPartExpressions.Select(Compile).ToArray(); + } + + private Regex Compile(string exp) + { + return new Regex(exp, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + } +} diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj new file mode 100644 index 0000000000..89562296d4 --- /dev/null +++ b/Emby.Naming/Emby.Naming.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.1 + false + + + + + + + \ No newline at end of file diff --git a/Emby.Naming/Extensions/StringExtensions.cs b/Emby.Naming/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..e773dbfe8e --- /dev/null +++ b/Emby.Naming/Extensions/StringExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Text; + +namespace Emby.Naming.Extensions +{ + public static class StringExtensions + { + public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison) + { + var sb = new StringBuilder(); + + var previousIndex = 0; + var index = str.IndexOf(oldValue, comparison); + + while (index != -1) + { + sb.Append(str.Substring(previousIndex, index - previousIndex)); + sb.Append(newValue); + index += oldValue.Length; + + previousIndex = index; + index = str.IndexOf(oldValue, index, comparison); + } + + sb.Append(str.Substring(previousIndex)); + + return sb.ToString(); + } + } +} diff --git a/Emby.Naming/StringExtensions.cs b/Emby.Naming/StringExtensions.cs new file mode 100644 index 0000000000..5598ddb4c2 --- /dev/null +++ b/Emby.Naming/StringExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Emby.Naming +{ + internal static class StringExtensions + { + public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison) + { + var sb = new StringBuilder(); + + var previousIndex = 0; + var index = str.IndexOf(oldValue, comparison); + + while (index != -1) + { + sb.Append(str.Substring(previousIndex, index - previousIndex)); + sb.Append(newValue); + index += oldValue.Length; + + previousIndex = index; + index = str.IndexOf(oldValue, index, comparison); + } + + sb.Append(str.Substring(previousIndex)); + + return sb.ToString(); + } + } +} diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs new file mode 100644 index 0000000000..3ece0679ea --- /dev/null +++ b/Emby.Naming/Subtitles/SubtitleInfo.cs @@ -0,0 +1,27 @@ + +namespace Emby.Naming.Subtitles +{ + public class SubtitleInfo + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets the language. + /// + /// The language. + public string Language { get; set; } + /// + /// Gets or sets a value indicating whether this instance is default. + /// + /// true if this instance is default; otherwise, false. + public bool IsDefault { get; set; } + /// + /// Gets or sets a value indicating whether this instance is forced. + /// + /// true if this instance is forced; otherwise, false. + public bool IsForced { get; set; } + } +} diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs new file mode 100644 index 0000000000..fbf0f60cba --- /dev/null +++ b/Emby.Naming/Subtitles/SubtitleParser.cs @@ -0,0 +1,65 @@ +using Emby.Naming.Common; +using System; +using System.IO; +using System.Linq; + +namespace Emby.Naming.Subtitles +{ + public class SubtitleParser + { + private readonly NamingOptions _options; + + public SubtitleParser(NamingOptions options) + { + _options = options; + } + + public SubtitleInfo ParseFile(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + var extension = Path.GetExtension(path); + if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + return null; + } + + var flags = GetFlags(path); + + var info = new SubtitleInfo + { + Path = path, + IsDefault = _options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)), + IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)) + }; + + var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase)) + .ToList(); + + // Should have a name, language and file extension + if (parts.Count >= 3) + { + info.Language = parts[parts.Count - 2]; + } + + return info; + } + + private string[] GetFlags(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _. + + var file = Path.GetFileName(path); + + return file.Split(_options.SubtitleFlagDelimiters, StringSplitOptions.RemoveEmptyEntries); + } + } +} diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs new file mode 100644 index 0000000000..41397480ec --- /dev/null +++ b/Emby.Naming/TV/EpisodeInfo.cs @@ -0,0 +1,51 @@ + +namespace Emby.Naming.TV +{ + public class EpisodeInfo + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets the container. + /// + /// The container. + public string Container { get; set; } + /// + /// Gets or sets the name of the series. + /// + /// The name of the series. + public string SeriesName { get; set; } + /// + /// Gets or sets the format3 d. + /// + /// The format3 d. + public string Format3D { get; set; } + /// + /// Gets or sets a value indicating whether [is3 d]. + /// + /// true if [is3 d]; otherwise, false. + public bool Is3D { get; set; } + /// + /// Gets or sets a value indicating whether this instance is stub. + /// + /// true if this instance is stub; otherwise, false. + public bool IsStub { get; set; } + /// + /// Gets or sets the type of the stub. + /// + /// The type of the stub. + public string StubType { get; set; } + + public int? SeasonNumber { get; set; } + public int? EpisodeNumber { get; set; } + public int? EndingEpsiodeNumber { get; set; } + + public int? Year { get; set; } + public int? Month { get; set; } + public int? Day { get; set; } + public bool IsByDate { get; set; } + } +} diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs new file mode 100644 index 0000000000..7f8a6a70ec --- /dev/null +++ b/Emby.Naming/TV/EpisodePathParser.cs @@ -0,0 +1,223 @@ +using Emby.Naming.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Emby.Naming.TV +{ + public class EpisodePathParser + { + private readonly NamingOptions _options; + + public EpisodePathParser(NamingOptions options) + { + _options = options; + } + + public EpisodePathParserResult Parse(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) + { + // Added to be able to use regex patterns which require a file extension. + // There were no failed tests without this block, but to be safe, we can keep it until + // the regex which require file extensions are modified so that they don't need them. + if (IsDirectory) + path += ".mp4"; + + EpisodePathParserResult result = null; + + foreach (var expression in _options.EpisodeExpressions) + { + if (supportsAbsoluteNumbers.HasValue) + { + if (expression.SupportsAbsoluteEpisodeNumbers != supportsAbsoluteNumbers.Value) + { + continue; + } + } + if (isNamed.HasValue) + { + if (expression.IsNamed != isNamed.Value) + { + continue; + } + } + if (isOptimistic.HasValue) + { + if (expression.IsOptimistic != isOptimistic.Value) + { + continue; + } + } + + var currentResult = Parse(path, expression); + if (currentResult.Success) + { + result = currentResult; + break; + } + } + + if (result != null && fillExtendedInfo) + { + FillAdditional(path, result); + + if (!string.IsNullOrEmpty(result.SeriesName)) + { + result.SeriesName = result.SeriesName + .Trim() + .Trim(new[] { '_', '.', '-' }) + .Trim(); + } + } + + return result ?? new EpisodePathParserResult(); + } + + private EpisodePathParserResult Parse(string name, EpisodeExpression expression) + { + var result = new EpisodePathParserResult(); + + // This is a hack to handle wmc naming + if (expression.IsByDate) + { + name = name.Replace('_', '-'); + } + + var match = expression.Regex.Match(name); + + // (Full)(Season)(Episode)(Extension) + if (match.Success && match.Groups.Count >= 3) + { + if (expression.IsByDate) + { + DateTime date; + if (expression.DateTimeFormats.Length > 0) + { + if (DateTime.TryParseExact(match.Groups[0].Value, + expression.DateTimeFormats, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out date)) + { + result.Year = date.Year; + result.Month = date.Month; + result.Day = date.Day; + result.Success = true; + } + } + else + { + if (DateTime.TryParse(match.Groups[0].Value, out date)) + { + result.Year = date.Year; + result.Month = date.Month; + result.Day = date.Day; + result.Success = true; + } + } + + // TODO: Only consider success if date successfully parsed? + result.Success = true; + } + else if (expression.IsNamed) + { + int num; + if (int.TryParse(match.Groups["seasonnumber"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.SeasonNumber = num; + } + + if (int.TryParse(match.Groups["epnumber"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.EpisodeNumber = num; + } + + Group endingNumberGroup = match.Groups["endingepnumber"]; + if (endingNumberGroup.Success) + { + // Will only set EndingEpsiodeNumber if the captured number is not followed by additional numbers + // or a 'p' or 'i' as what you would get with a pixel resolution specification. + // It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108 + int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length; + if (nextIndex >= name.Length || "0123456789iIpP".IndexOf(name[nextIndex]) == -1) + { + if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.EndingEpsiodeNumber = num; + } + } + } + + result.SeriesName = match.Groups["seriesname"].Value; + result.Success = result.EpisodeNumber.HasValue; + } + else + { + int num; + if (int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.SeasonNumber = num; + } + if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num)) + { + result.EpisodeNumber = num; + } + + result.Success = result.EpisodeNumber.HasValue; + } + + // Invalidate match when the season is 200 through 1927 or above 2500 + // because it is an error unless the TV show is intentionally using false season numbers. + // It avoids erroneous parsing of something like "Series Special (1920x1080).mkv" as being season 1920 episode 1080. + if (result.SeasonNumber >= 200 && result.SeasonNumber < 1928 || result.SeasonNumber > 2500) + result.Success = false; + + result.IsByDate = expression.IsByDate; + } + + return result; + } + + private void FillAdditional(string path, EpisodePathParserResult info) + { + var expressions = _options.MultipleEpisodeExpressions.ToList(); + + if (string.IsNullOrEmpty(info.SeriesName)) + { + expressions.InsertRange(0, _options.EpisodeExpressions.Where(i => i.IsNamed)); + } + + FillAdditional(path, info, expressions); + } + + private void FillAdditional(string path, EpisodePathParserResult info, IEnumerable expressions) + { + var results = expressions + .Where(i => i.IsNamed) + .Select(i => Parse(path, i)) + .Where(i => i.Success); + + foreach (var result in results) + { + if (string.IsNullOrEmpty(info.SeriesName)) + { + info.SeriesName = result.SeriesName; + } + + if (!info.EndingEpsiodeNumber.HasValue && info.EpisodeNumber.HasValue) + { + info.EndingEpsiodeNumber = result.EndingEpsiodeNumber; + } + + if (!string.IsNullOrEmpty(info.SeriesName)) + { + if (!info.EpisodeNumber.HasValue || info.EndingEpsiodeNumber.HasValue) + { + break; + } + } + } + } + } +} diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs new file mode 100644 index 0000000000..9890c78a45 --- /dev/null +++ b/Emby.Naming/TV/EpisodePathParserResult.cs @@ -0,0 +1,17 @@ + +namespace Emby.Naming.TV +{ + public class EpisodePathParserResult + { + public int? SeasonNumber { get; set; } + public int? EpisodeNumber { get; set; } + public int? EndingEpsiodeNumber { get; set; } + public string SeriesName { get; set; } + public bool Success { get; set; } + + public bool IsByDate { get; set; } + public int? Year { get; set; } + public int? Month { get; set; } + public int? Day { get; set; } + } +} diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs new file mode 100644 index 0000000000..2007d13077 --- /dev/null +++ b/Emby.Naming/TV/EpisodeResolver.cs @@ -0,0 +1,76 @@ +using Emby.Naming.Common; +using Emby.Naming.Video; +using System; +using System.IO; +using System.Linq; + +namespace Emby.Naming.TV +{ + public class EpisodeResolver + { + private readonly NamingOptions _options; + + public EpisodeResolver(NamingOptions options) + { + _options = options; + } + + public EpisodeInfo Resolve(string path, bool IsDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + var isStub = false; + string container = null; + string stubType = null; + + if (!IsDirectory) + { + var extension = Path.GetExtension(path) ?? string.Empty; + // Check supported extensions + if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + var stubResult = new StubResolver(_options).ResolveFile(path); + + isStub = stubResult.IsStub; + + // It's not supported. Check stub extensions + if (!isStub) + { + return null; + } + + stubType = stubResult.StubType; + } + + container = extension.TrimStart('.'); + } + + var flags = new FlagParser(_options).GetFlags(path); + var format3DResult = new Format3DParser(_options).Parse(flags); + + var parsingResult = new EpisodePathParser(_options) + .Parse(path, IsDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); + + return new EpisodeInfo + { + Path = path, + Container = container, + IsStub = isStub, + EndingEpsiodeNumber = parsingResult.EndingEpsiodeNumber, + EpisodeNumber = parsingResult.EpisodeNumber, + SeasonNumber = parsingResult.SeasonNumber, + SeriesName = parsingResult.SeriesName, + StubType = stubType, + Is3D = format3DResult.Is3D, + Format3D = format3DResult.Format3D, + IsByDate = parsingResult.IsByDate, + Day = parsingResult.Day, + Month = parsingResult.Month, + Year = parsingResult.Year + }; + } + } +} diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs new file mode 100644 index 0000000000..72277e6b9e --- /dev/null +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -0,0 +1,186 @@ +using Emby.Naming.Common; +using System; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace Emby.Naming.TV +{ + public class SeasonPathParser + { + private readonly NamingOptions _options; + + public SeasonPathParser(NamingOptions options) + { + _options = options; + } + + public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders) + { + var result = new SeasonPathParserResult(); + + var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders); + + result.SeasonNumber = seasonNumberInfo.Item1; + + if (result.SeasonNumber.HasValue) + { + result.Success = true; + result.IsSeasonFolder = seasonNumberInfo.Item2; + } + + return result; + } + + /// + /// A season folder must contain one of these somewhere in the name + /// + private static readonly string[] SeasonFolderNames = + { + "season", + "sæson", + "temporada", + "saison", + "staffel", + "series", + "сезон", + "stagione" + }; + + /// + /// Gets the season number from path. + /// + /// The path. + /// if set to true [support special aliases]. + /// if set to true [support numeric season folders]. + /// System.Nullable{System.Int32}. + private Tuple GetSeasonNumberFromPath(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders) + { + var filename = Path.GetFileName(path); + + if (supportSpecialAliases) + { + if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase)) + { + return new Tuple(0, true); + } + if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase)) + { + return new Tuple(0, true); + } + } + + if (supportNumericSeasonFolders) + { + int val; + if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + { + return new Tuple(val, true); + } + } + + if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase)) + { + var testFilename = filename.Substring(1); + + int val; + if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + { + return new Tuple(val, true); + } + } + + // Look for one of the season folder names + foreach (var name in SeasonFolderNames) + { + var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase); + + if (index != -1) + { + var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase)); + if (result.Item1.HasValue) + { + return result; + } + + break; + } + } + + var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries); + var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue); + return new Tuple(resultNumber, true); + } + + private int? GetSeasonNumberFromPart(string part) + { + if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + part = part.Substring(1); + + int value; + if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) + { + return value; + } + + return null; + } + + /// + /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel") + /// + /// The path. + /// System.Nullable{System.Int32}. + private Tuple GetSeasonNumberFromPathSubstring(string path) + { + var numericStart = -1; + var length = 0; + + var hasOpenParenth = false; + var isSeasonFolder = true; + + // Find out where the numbers start, and then keep going until they end + for (var i = 0; i < path.Length; i++) + { + if (char.IsNumber(path, i)) + { + if (!hasOpenParenth) + { + if (numericStart == -1) + { + numericStart = i; + } + length++; + } + } + else if (numericStart != -1) + { + // There's other stuff after the season number, e.g. episode number + isSeasonFolder = false; + break; + } + + var currentChar = path[i]; + if (currentChar.Equals('(')) + { + hasOpenParenth = true; + } + else if (currentChar.Equals(')')) + { + hasOpenParenth = false; + } + } + + if (numericStart == -1) + { + return new Tuple(null, isSeasonFolder); + } + + return new Tuple(int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder); + } + } +} diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs new file mode 100644 index 0000000000..9a18d0a036 --- /dev/null +++ b/Emby.Naming/TV/SeasonPathParserResult.cs @@ -0,0 +1,18 @@ + +namespace Emby.Naming.TV +{ + public class SeasonPathParserResult + { + /// + /// Gets or sets the season number. + /// + /// The season number. + public int? SeasonNumber { get; set; } + /// + /// Gets or sets a value indicating whether this is success. + /// + /// true if success; otherwise, false. + public bool Success { get; set; } + public bool IsSeasonFolder { get; set; } + } +} diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs new file mode 100644 index 0000000000..572dd1c600 --- /dev/null +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -0,0 +1,87 @@ +using System; +using Emby.Naming.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video +{ + /// + /// http://kodi.wiki/view/Advancedsettings.xml#video + /// + public class CleanDateTimeParser + { + private readonly NamingOptions _options; + + public CleanDateTimeParser(NamingOptions options) + { + _options = options; + } + + public CleanDateTimeResult Clean(string name) + { + var originalName = name; + + try + { + var extension = Path.GetExtension(name) ?? string.Empty; + // Check supported extensions + if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase) && + !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + // Dummy up a file extension because the expressions will fail without one + // This is tricky because we can't just check Path.GetExtension for empty + // If the input is "St. Vincent (2014)", it will produce ". Vincent (2014)" as the extension + name += ".mkv"; + } + } + catch (ArgumentException) + { + + } + + var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i)) + .FirstOrDefault(i => i.HasChanged) ?? + new CleanDateTimeResult { Name = originalName }; + + if (result.HasChanged) + { + return result; + } + + // Make a second pass, running clean string first + var cleanStringResult = new CleanStringParser().Clean(name, _options.CleanStringRegexes); + + if (!cleanStringResult.HasChanged) + { + return result; + } + + return _options.CleanDateTimeRegexes.Select(i => Clean(cleanStringResult.Name, i)) + .FirstOrDefault(i => i.HasChanged) ?? + result; + } + + private CleanDateTimeResult Clean(string name, Regex expression) + { + var result = new CleanDateTimeResult(); + + var match = expression.Match(name); + + if (match.Success && match.Groups.Count == 4) + { + int year; + if (match.Groups[1].Success && match.Groups[2].Success && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out year)) + { + name = match.Groups[1].Value; + result.Year = year; + result.HasChanged = true; + } + } + + result.Name = name; + return result; + } + } +} diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs new file mode 100644 index 0000000000..946fd953c1 --- /dev/null +++ b/Emby.Naming/Video/CleanDateTimeResult.cs @@ -0,0 +1,22 @@ + +namespace Emby.Naming.Video +{ + public class CleanDateTimeResult + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the year. + /// + /// The year. + public int? Year { get; set; } + /// + /// Gets or sets a value indicating whether this instance has changed. + /// + /// true if this instance has changed; otherwise, false. + public bool HasChanged { get; set; } + } +} diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs new file mode 100644 index 0000000000..bddf9589b6 --- /dev/null +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video +{ + /// + /// http://kodi.wiki/view/Advancedsettings.xml#video + /// + public class CleanStringParser + { + public CleanStringResult Clean(string name, IEnumerable expressions) + { + var hasChanged = false; + + foreach (var exp in expressions) + { + var result = Clean(name, exp); + + if (!string.IsNullOrEmpty(result.Name)) + { + name = result.Name; + hasChanged = hasChanged || result.HasChanged; + } + } + + return new CleanStringResult + { + Name = name, + HasChanged = hasChanged + }; + } + + private CleanStringResult Clean(string name, Regex expression) + { + var result = new CleanStringResult(); + + var match = expression.Match(name); + + if (match.Success) + { + result.HasChanged = true; + name = name.Substring(0, match.Index); + } + + result.Name = name; + return result; + } + } +} diff --git a/Emby.Naming/Video/CleanStringResult.cs b/Emby.Naming/Video/CleanStringResult.cs new file mode 100644 index 0000000000..0282863e06 --- /dev/null +++ b/Emby.Naming/Video/CleanStringResult.cs @@ -0,0 +1,17 @@ + +namespace Emby.Naming.Video +{ + public class CleanStringResult + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets a value indicating whether this instance has changed. + /// + /// true if this instance has changed; otherwise, false. + public bool HasChanged { get; set; } + } +} diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs new file mode 100644 index 0000000000..bde1a47656 --- /dev/null +++ b/Emby.Naming/Video/ExtraResolver.cs @@ -0,0 +1,87 @@ +using Emby.Naming.Audio; +using Emby.Naming.Common; +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video +{ + public class ExtraResolver + { + private readonly NamingOptions _options; + + public ExtraResolver(NamingOptions options) + { + _options = options; + } + + public ExtraResult GetExtraInfo(string path) + { + return _options.VideoExtraRules + .Select(i => GetExtraInfo(path, i)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i.ExtraType)) ?? new ExtraResult(); + } + + private ExtraResult GetExtraInfo(string path, ExtraRule rule) + { + var result = new ExtraResult(); + + if (rule.MediaType == MediaType.Audio) + { + if (!new AudioFileParser(_options).IsAudioFile(path)) + { + return result; + } + } + else if (rule.MediaType == MediaType.Video) + { + if (!new VideoResolver(_options).IsVideoFile(path)) + { + return result; + } + } + else + { + return result; + } + + if (rule.RuleType == ExtraRuleType.Filename) + { + var filename = Path.GetFileNameWithoutExtension(path); + + if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase)) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + + else if (rule.RuleType == ExtraRuleType.Suffix) + { + var filename = Path.GetFileNameWithoutExtension(path); + + if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + + else if (rule.RuleType == ExtraRuleType.Regex) + { + var filename = Path.GetFileName(path); + + var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); + + if (regex.IsMatch(filename)) + { + result.ExtraType = rule.ExtraType; + result.Rule = rule; + } + } + + return result; + } + } +} diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs new file mode 100644 index 0000000000..ca79af9da7 --- /dev/null +++ b/Emby.Naming/Video/ExtraResult.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Emby.Naming.Video +{ + public class ExtraResult + { + /// + /// Gets or sets the type of the extra. + /// + /// The type of the extra. + public string ExtraType { get; set; } + /// + /// Gets or sets the rule. + /// + /// The rule. + public ExtraRule Rule { get; set; } + } +} diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs new file mode 100644 index 0000000000..ef83b3cd6c --- /dev/null +++ b/Emby.Naming/Video/ExtraRule.cs @@ -0,0 +1,28 @@ +using Emby.Naming.Common; + +namespace Emby.Naming.Video +{ + public class ExtraRule + { + /// + /// Gets or sets the token. + /// + /// The token. + public string Token { get; set; } + /// + /// Gets or sets the type of the extra. + /// + /// The type of the extra. + public string ExtraType { get; set; } + /// + /// Gets or sets the type of the rule. + /// + /// The type of the rule. + public ExtraRuleType RuleType { get; set; } + /// + /// Gets or sets the type of the media. + /// + /// The type of the media. + public MediaType MediaType { get; set; } + } +} diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs new file mode 100644 index 0000000000..323c7cef60 --- /dev/null +++ b/Emby.Naming/Video/ExtraRuleType.cs @@ -0,0 +1,19 @@ + +namespace Emby.Naming.Video +{ + public enum ExtraRuleType + { + /// + /// The suffix + /// + Suffix = 0, + /// + /// The filename + /// + Filename = 1, + /// + /// The regex + /// + Regex = 2 + } +} diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs new file mode 100644 index 0000000000..2feea4cb3a --- /dev/null +++ b/Emby.Naming/Video/FileStack.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Emby.Naming.Video +{ + public class FileStack + { + public string Name { get; set; } + public List Files { get; set; } + public bool IsDirectoryStack { get; set; } + + public FileStack() + { + Files = new List(); + } + + public bool ContainsFile(string file, bool IsDirectory) + { + if (IsDirectoryStack == IsDirectory) + { + return Files.Contains(file, StringComparer.OrdinalIgnoreCase); + } + + return false; + } + } +} diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs new file mode 100644 index 0000000000..a2c541eeb2 --- /dev/null +++ b/Emby.Naming/Video/FlagParser.cs @@ -0,0 +1,35 @@ +using Emby.Naming.Common; +using System; +using System.IO; + +namespace Emby.Naming.Video +{ + public class FlagParser + { + private readonly NamingOptions _options; + + public FlagParser(NamingOptions options) + { + _options = options; + } + + public string[] GetFlags(string path) + { + return GetFlags(path, _options.VideoFlagDelimiters); + } + + public string[] GetFlags(string path, char[] delimeters) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + // Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _. + + var file = Path.GetFileName(path); + + return file.Split(delimeters, StringSplitOptions.RemoveEmptyEntries); + } + } +} diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs new file mode 100644 index 0000000000..42737b4834 --- /dev/null +++ b/Emby.Naming/Video/Format3DParser.cs @@ -0,0 +1,81 @@ +using Emby.Naming.Common; +using System; +using System.Linq; + +namespace Emby.Naming.Video +{ + public class Format3DParser + { + private readonly NamingOptions _options; + + public Format3DParser(NamingOptions options) + { + _options = options; + } + + public Format3DResult Parse(string path) + { + var delimeters = _options.VideoFlagDelimiters.ToList(); + delimeters.Add(' '); + + return Parse(new FlagParser(_options).GetFlags(path, delimeters.ToArray())); + } + + internal Format3DResult Parse(string[] videoFlags) + { + foreach (var rule in _options.Format3DRules) + { + var result = Parse(videoFlags, rule); + + if (result.Is3D) + { + return result; + } + } + + return new Format3DResult(); + } + + private Format3DResult Parse(string[] videoFlags, Format3DRule rule) + { + var result = new Format3DResult(); + + if (string.IsNullOrEmpty(rule.PreceedingToken)) + { + result.Format3D = new[] { rule.Token }.FirstOrDefault(i => videoFlags.Contains(i, StringComparer.OrdinalIgnoreCase)); + result.Is3D = !string.IsNullOrEmpty(result.Format3D); + + if (result.Is3D) + { + result.Tokens.Add(rule.Token); + } + } + else + { + var foundPrefix = false; + string format = null; + + foreach (var flag in videoFlags) + { + if (foundPrefix) + { + result.Tokens.Add(rule.PreceedingToken); + + if (string.Equals(rule.Token, flag, StringComparison.OrdinalIgnoreCase)) + { + format = flag; + result.Tokens.Add(rule.Token); + } + break; + } + foundPrefix = string.Equals(flag, rule.PreceedingToken, StringComparison.OrdinalIgnoreCase); + } + + result.Is3D = foundPrefix && !string.IsNullOrEmpty(format); + result.Format3D = format; + } + + return result; + } + } +} diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs new file mode 100644 index 0000000000..147ccfc057 --- /dev/null +++ b/Emby.Naming/Video/Format3DResult.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Emby.Naming.Video +{ + public class Format3DResult + { + /// + /// Gets or sets a value indicating whether [is3 d]. + /// + /// true if [is3 d]; otherwise, false. + public bool Is3D { get; set; } + /// + /// Gets or sets the format3 d. + /// + /// The format3 d. + public string Format3D { get; set; } + /// + /// Gets or sets the tokens. + /// + /// The tokens. + public List Tokens { get; set; } + + public Format3DResult() + { + Tokens = new List(); + } + } +} diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs new file mode 100644 index 0000000000..3c173efbc7 --- /dev/null +++ b/Emby.Naming/Video/Format3DRule.cs @@ -0,0 +1,17 @@ + +namespace Emby.Naming.Video +{ + public class Format3DRule + { + /// + /// Gets or sets the token. + /// + /// The token. + public string Token { get; set; } + /// + /// Gets or sets the preceeding token. + /// + /// The preceeding token. + public string PreceedingToken { get; set; } + } +} diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs new file mode 100644 index 0000000000..2a71255368 --- /dev/null +++ b/Emby.Naming/Video/StackResolver.cs @@ -0,0 +1,216 @@ +using Emby.Naming.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using MediaBrowser.Model.IO; + +namespace Emby.Naming.Video +{ + public class StackResolver + { + private readonly NamingOptions _options; + + public StackResolver(NamingOptions options) + { + _options = options; + } + + public StackResult ResolveDirectories(IEnumerable files) + { + return Resolve(files.Select(i => new FileSystemMetadata + { + FullName = i, + IsDirectory = true + })); + } + + public StackResult ResolveFiles(IEnumerable files) + { + return Resolve(files.Select(i => new FileSystemMetadata + { + FullName = i, + IsDirectory = false + })); + } + + public StackResult ResolveAudioBooks(IEnumerable files) + { + var result = new StackResult(); + foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName))) + { + var stack = new FileStack(); + stack.Name = Path.GetFileName(directory.Key); + stack.IsDirectoryStack = false; + foreach (var file in directory) + { + if (file.IsDirectory) + continue; + stack.Files.Add(file.FullName); + } + result.Stacks.Add(stack); + } + return result; + } + + public StackResult Resolve(IEnumerable files) + { + var result = new StackResult(); + + var resolver = new VideoResolver(_options); + + var list = files + .Where(i => i.IsDirectory || (resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName))) + .OrderBy(i => i.FullName) + .ToList(); + + var expressions = _options.VideoFileStackingRegexes; + + for (var i = 0; i < list.Count; i++) + { + var offset = 0; + + var file1 = list[i]; + + var expressionIndex = 0; + while (expressionIndex < expressions.Length) + { + var exp = expressions[expressionIndex]; + var stack = new FileStack(); + + // (Title)(Volume)(Ignore)(Extension) + var match1 = FindMatch(file1, exp, offset); + + if (match1.Success) + { + var title1 = match1.Groups[1].Value; + var volume1 = match1.Groups[2].Value; + var ignore1 = match1.Groups[3].Value; + var extension1 = match1.Groups[4].Value; + + var j = i + 1; + while (j < list.Count) + { + var file2 = list[j]; + + if (file1.IsDirectory != file2.IsDirectory) + { + j++; + continue; + } + + // (Title)(Volume)(Ignore)(Extension) + var match2 = FindMatch(file2, exp, offset); + + if (match2.Success) + { + var title2 = match2.Groups[1].Value; + var volume2 = match2.Groups[2].Value; + var ignore2 = match2.Groups[3].Value; + var extension2 = match2.Groups[4].Value; + + if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase)) + { + if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase) && + string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase)) + { + if (stack.Files.Count == 0) + { + stack.Name = title1 + ignore1; + stack.IsDirectoryStack = file1.IsDirectory; + //stack.Name = title1 + ignore1 + extension1; + stack.Files.Add(file1.FullName); + } + stack.Files.Add(file2.FullName); + } + else + { + // Sequel + offset = 0; + expressionIndex++; + break; + } + } + else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)) + { + // False positive, try again with offset + offset = match1.Groups[3].Index; + break; + } + else + { + // Extension mismatch + offset = 0; + expressionIndex++; + break; + } + } + else + { + // Title mismatch + offset = 0; + expressionIndex++; + break; + } + } + else + { + // No match 2, next expression + offset = 0; + expressionIndex++; + break; + } + + j++; + } + + if (j == list.Count) + { + expressionIndex = expressions.Length; + } + } + else + { + // No match 1 + offset = 0; + expressionIndex++; + } + + if (stack.Files.Count > 1) + { + result.Stacks.Add(stack); + i += stack.Files.Count - 1; + break; + } + } + } + + return result; + } + + private string GetRegexInput(FileSystemMetadata file) + { + // For directories, dummy up an extension otherwise the expressions will fail + var input = !file.IsDirectory + ? file.FullName + : file.FullName + ".mkv"; + + return Path.GetFileName(input); + } + + private Match FindMatch(FileSystemMetadata input, Regex regex, int offset) + { + var regexInput = GetRegexInput(input); + + if (offset < 0 || offset >= regexInput.Length) + { + return Match.Empty; + } + + return regex.Match(regexInput, offset); + } + } +} diff --git a/Emby.Naming/Video/StackResult.cs b/Emby.Naming/Video/StackResult.cs new file mode 100644 index 0000000000..920a7dea73 --- /dev/null +++ b/Emby.Naming/Video/StackResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Emby.Naming.Video +{ + public class StackResult + { + public List Stacks { get; set; } + + public StackResult() + { + Stacks = new List(); + } + } +} diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs new file mode 100644 index 0000000000..69f1f50fa0 --- /dev/null +++ b/Emby.Naming/Video/StubResolver.cs @@ -0,0 +1,44 @@ +using Emby.Naming.Common; +using System; +using System.IO; +using System.Linq; + +namespace Emby.Naming.Video +{ + public class StubResolver + { + private readonly NamingOptions _options; + + public StubResolver(NamingOptions options) + { + _options = options; + } + + public StubResult ResolveFile(string path) + { + var result = new StubResult(); + var extension = Path.GetExtension(path) ?? string.Empty; + + if (_options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + result.IsStub = true; + + path = Path.GetFileNameWithoutExtension(path); + + var token = (Path.GetExtension(path) ?? string.Empty).TrimStart('.'); + + foreach (var rule in _options.StubTypes) + { + if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase)) + { + result.StubType = rule.StubType; + result.Tokens.Add(token); + break; + } + } + } + + return result; + } + } +} diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs new file mode 100644 index 0000000000..c9d06c9a7f --- /dev/null +++ b/Emby.Naming/Video/StubResult.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Emby.Naming.Video +{ + public class StubResult + { + /// + /// Gets or sets a value indicating whether this instance is stub. + /// + /// true if this instance is stub; otherwise, false. + public bool IsStub { get; set; } + /// + /// Gets or sets the type of the stub. + /// + /// The type of the stub. + public string StubType { get; set; } + /// + /// Gets or sets the tokens. + /// + /// The tokens. + public List Tokens { get; set; } + + public StubResult() + { + Tokens = new List(); + } + } +} diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs new file mode 100644 index 0000000000..66ebfc3a26 --- /dev/null +++ b/Emby.Naming/Video/StubTypeRule.cs @@ -0,0 +1,17 @@ + +namespace Emby.Naming.Video +{ + public class StubTypeRule + { + /// + /// Gets or sets the token. + /// + /// The token. + public string Token { get; set; } + /// + /// Gets or sets the type of the stub. + /// + /// The type of the stub. + public string StubType { get; set; } + } +} diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs new file mode 100644 index 0000000000..96839c31ef --- /dev/null +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -0,0 +1,79 @@ + +namespace Emby.Naming.Video +{ + /// + /// Represents a single video file + /// + public class VideoFileInfo + { + /// + /// Gets or sets the path. + /// + /// The path. + public string Path { get; set; } + /// + /// Gets or sets the container. + /// + /// The container. + public string Container { get; set; } + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the year. + /// + /// The year. + public int? Year { get; set; } + /// + /// Gets or sets the type of the extra, e.g. trailer, theme song, behing the scenes, etc. + /// + /// The type of the extra. + public string ExtraType { get; set; } + /// + /// Gets or sets the extra rule. + /// + /// The extra rule. + public ExtraRule ExtraRule { get; set; } + /// + /// Gets or sets the format3 d. + /// + /// The format3 d. + public string Format3D { get; set; } + /// + /// Gets or sets a value indicating whether [is3 d]. + /// + /// true if [is3 d]; otherwise, false. + public bool Is3D { get; set; } + /// + /// Gets or sets a value indicating whether this instance is stub. + /// + /// true if this instance is stub; otherwise, false. + public bool IsStub { get; set; } + /// + /// Gets or sets the type of the stub. + /// + /// The type of the stub. + public string StubType { get; set; } + /// + /// Gets or sets the type. + /// + /// The type. + public bool IsDirectory { get; set; } + /// + /// Gets the file name without extension. + /// + /// The file name without extension. + public string FileNameWithoutExtension + { + get { return !IsDirectory ? System.IO.Path.GetFileNameWithoutExtension(Path) : System.IO.Path.GetFileName(Path); } + } + + public override string ToString() + { + // Makes debugging easier + return Name ?? base.ToString(); + } + } +} diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs new file mode 100644 index 0000000000..f4d311b975 --- /dev/null +++ b/Emby.Naming/Video/VideoInfo.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace Emby.Naming.Video +{ + /// + /// Represents a complete video, including all parts and subtitles + /// + public class VideoInfo + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the year. + /// + /// The year. + public int? Year { get; set; } + /// + /// Gets or sets the files. + /// + /// The files. + public List Files { get; set; } + /// + /// Gets or sets the extras. + /// + /// The extras. + public List Extras { get; set; } + /// + /// Gets or sets the alternate versions. + /// + /// The alternate versions. + public List AlternateVersions { get; set; } + + public VideoInfo() + { + Files = new List(); + Extras = new List(); + AlternateVersions = new List(); + } + } +} diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs new file mode 100644 index 0000000000..47be28104d --- /dev/null +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -0,0 +1,259 @@ +using Emby.Naming.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Model.IO; +using System.Text.RegularExpressions; + +namespace Emby.Naming.Video +{ + public class VideoListResolver + { + private readonly NamingOptions _options; + + public VideoListResolver(NamingOptions options) + { + _options = options; + } + + public IEnumerable Resolve(List files, bool supportMultiVersion = true) + { + var videoResolver = new VideoResolver(_options); + + var videoInfos = files + .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory)) + .Where(i => i != null) + .ToList(); + + // Filter out all extras, otherwise they could cause stacks to not be resolved + // See the unit test TestStackedWithTrailer + var nonExtras = videoInfos + .Where(i => string.IsNullOrEmpty(i.ExtraType)) + .Select(i => new FileSystemMetadata + { + FullName = i.Path, + IsDirectory = i.IsDirectory + }); + + var stackResult = new StackResolver(_options) + .Resolve(nonExtras); + + var remainingFiles = videoInfos + .Where(i => !stackResult.Stacks.Any(s => s.ContainsFile(i.Path, i.IsDirectory))) + .ToList(); + + var list = new List(); + + foreach (var stack in stackResult.Stacks) + { + var info = new VideoInfo + { + Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList(), + Name = stack.Name + }; + + info.Year = info.Files.First().Year; + + var extraBaseNames = new List + { + stack.Name, + Path.GetFileNameWithoutExtension(stack.Files[0]) + }; + + var extras = GetExtras(remainingFiles, extraBaseNames); + + if (extras.Count > 0) + { + remainingFiles = remainingFiles + .Except(extras) + .ToList(); + + info.Extras = extras; + } + + list.Add(info); + } + + var standaloneMedia = remainingFiles + .Where(i => string.IsNullOrEmpty(i.ExtraType)) + .ToList(); + + foreach (var media in standaloneMedia) + { + var info = new VideoInfo + { + Files = new List { media }, + Name = media.Name + }; + + info.Year = info.Files.First().Year; + + var extras = GetExtras(remainingFiles, new List { media.FileNameWithoutExtension }); + + remainingFiles = remainingFiles + .Except(extras.Concat(new[] { media })) + .ToList(); + + info.Extras = extras; + + list.Add(info); + } + + if (supportMultiVersion) + { + list = GetVideosGroupedByVersion(list) + .ToList(); + } + + // If there's only one resolved video, use the folder name as well to find extras + if (list.Count == 1) + { + var info = list[0]; + var videoPath = list[0].Files[0].Path; + var parentPath = Path.GetDirectoryName(videoPath); + + if (!string.IsNullOrEmpty(parentPath)) + { + var folderName = Path.GetFileName(Path.GetDirectoryName(videoPath)); + if (!string.IsNullOrEmpty(folderName)) + { + var extras = GetExtras(remainingFiles, new List { folderName }); + + remainingFiles = remainingFiles + .Except(extras) + .ToList(); + + info.Extras.AddRange(extras); + } + } + + // Add the extras that are just based on file name as well + var extrasByFileName = remainingFiles + .Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename) + .ToList(); + + remainingFiles = remainingFiles + .Except(extrasByFileName) + .ToList(); + + info.Extras.AddRange(extrasByFileName); + } + + // If there's only one video, accept all trailers + // Be lenient because people use all kinds of mish mash conventions with trailers + if (list.Count == 1) + { + var trailers = remainingFiles + .Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + list[0].Extras.AddRange(trailers); + + remainingFiles = remainingFiles + .Except(trailers) + .ToList(); + } + + // Whatever files are left, just add them + list.AddRange(remainingFiles.Select(i => new VideoInfo + { + Files = new List { i }, + Name = i.Name, + Year = i.Year + })); + + var orderedList = list.OrderBy(i => i.Name); + + return orderedList; + } + + private IEnumerable GetVideosGroupedByVersion(List videos) + { + if (videos.Count == 0) + { + return videos; + } + + var list = new List(); + + var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path)); + + if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1) + { + if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path))) + { + // Enforce the multi-version limit + if (videos.Count <= 8 && HaveSameYear(videos)) + { + var ordered = videos.OrderBy(i => i.Name).ToList(); + + list.Add(ordered[0]); + + list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList(); + list[0].Name = folderName; + list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras)); + + return list; + } + } + } + + return videos; + //foreach (var video in videos.OrderBy(i => i.Name)) + //{ + // var match = list + // .FirstOrDefault(i => string.Equals(i.Name, video.Name, StringComparison.OrdinalIgnoreCase)); + + // if (match != null && video.Files.Count == 1 && match.Files.Count == 1) + // { + // match.AlternateVersions.Add(video.Files[0]); + // match.Extras.AddRange(video.Extras); + // } + // else + // { + // list.Add(video); + // } + //} + + //return list; + } + + private bool HaveSameYear(List videos) + { + return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2; + } + + private bool IsEligibleForMultiVersion(string folderName, string testFilename) + { + testFilename = Path.GetFileNameWithoutExtension(testFilename); + + if (string.Equals(folderName, testFilename, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) + { + testFilename = testFilename.Substring(folderName.Length).Trim(); + return testFilename.StartsWith("-", StringComparison.OrdinalIgnoreCase)||Regex.Replace(testFilename, @"\[([^]]*)\]", "").Trim() == String.Empty; + } + + return false; + } + + private List GetExtras(IEnumerable remainingFiles, List baseNames) + { + foreach (var name in baseNames.ToList()) + { + var trimmedName = name.TrimEnd().TrimEnd(_options.VideoFlagDelimiters).TrimEnd(); + baseNames.Add(trimmedName); + } + + return remainingFiles + .Where(i => !string.IsNullOrEmpty(i.ExtraType)) + .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) + .ToList(); + } + } +} diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs new file mode 100644 index 0000000000..c4951c728e --- /dev/null +++ b/Emby.Naming/Video/VideoResolver.cs @@ -0,0 +1,139 @@ +using Emby.Naming.Common; +using System; +using System.IO; +using System.Linq; + +namespace Emby.Naming.Video +{ + public class VideoResolver + { + private readonly NamingOptions _options; + + public VideoResolver(NamingOptions options) + { + _options = options; + } + + /// + /// Resolves the directory. + /// + /// The path. + /// VideoFileInfo. + public VideoFileInfo ResolveDirectory(string path) + { + return Resolve(path, true); + } + + /// + /// Resolves the file. + /// + /// The path. + /// VideoFileInfo. + public VideoFileInfo ResolveFile(string path) + { + return Resolve(path, false); + } + + /// + /// Resolves the specified path. + /// + /// The path. + /// if set to true [is folder]. + /// VideoFileInfo. + /// path + public VideoFileInfo Resolve(string path, bool IsDirectory, bool parseName = true) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + var isStub = false; + string container = null; + string stubType = null; + + if (!IsDirectory) + { + var extension = Path.GetExtension(path) ?? string.Empty; + // Check supported extensions + if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + var stubResult = new StubResolver(_options).ResolveFile(path); + + isStub = stubResult.IsStub; + + // It's not supported. Check stub extensions + if (!isStub) + { + return null; + } + + stubType = stubResult.StubType; + } + + container = extension.TrimStart('.'); + } + + var flags = new FlagParser(_options).GetFlags(path); + var format3DResult = new Format3DParser(_options).Parse(flags); + + var extraResult = new ExtraResolver(_options).GetExtraInfo(path); + + var name = !IsDirectory + ? Path.GetFileNameWithoutExtension(path) + : Path.GetFileName(path); + + int? year = null; + + if (parseName) + { + var cleanDateTimeResult = CleanDateTime(name); + + if (string.IsNullOrEmpty(extraResult.ExtraType)) + { + name = cleanDateTimeResult.Name; + name = CleanString(name).Name; + } + + year = cleanDateTimeResult.Year; + } + + return new VideoFileInfo + { + Path = path, + Container = container, + IsStub = isStub, + Name = name, + Year = year, + StubType = stubType, + Is3D = format3DResult.Is3D, + Format3D = format3DResult.Format3D, + ExtraType = extraResult.ExtraType, + IsDirectory = IsDirectory, + ExtraRule = extraResult.Rule + }; + } + + public bool IsVideoFile(string path) + { + var extension = Path.GetExtension(path) ?? string.Empty; + return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } + + public bool IsStubFile(string path) + { + var extension = Path.GetExtension(path) ?? string.Empty; + return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } + + public CleanStringResult CleanString(string name) + { + return new CleanStringParser().Clean(name, _options.CleanStringRegexes); + } + + public CleanDateTimeResult CleanDateTime(string name) + { + return new CleanDateTimeParser(_options).Clean(name); + } + } +} diff --git a/MediaBrowser.Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs similarity index 72% rename from MediaBrowser.Api/NotificationsService.cs rename to Emby.Notifications/Api/NotificationsService.cs index 4876351fc0..d09552ebc0 100644 --- a/MediaBrowser.Api/NotificationsService.cs +++ b/Emby.Notifications/Api/NotificationsService.cs @@ -8,8 +8,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Dto; -namespace MediaBrowser.Api +namespace Emby.Notifications.Api { [Route("/Notifications/{UserId}", "GET", Summary = "Gets notifications")] public class GetNotifications : IReturn @@ -27,6 +28,37 @@ namespace MediaBrowser.Api public int? Limit { get; set; } } + public class Notification + { + public string Id { get; set; } + + public string UserId { get; set; } + + public DateTime Date { get; set; } + + public bool IsRead { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Url { get; set; } + + public NotificationLevel Level { get; set; } + } + + public class NotificationResult + { + public Notification[] Notifications { get; set; } + public int TotalRecordCount { get; set; } + } + + public class NotificationsSummary + { + public int UnreadCount { get; set; } + public NotificationLevel MaxUnreadNotificationLevel { get; set; } + } + [Route("/Notifications/{UserId}/Summary", "GET", Summary = "Gets a notification summary for a user")] public class GetNotificationsSummary : IReturn { @@ -40,7 +72,7 @@ namespace MediaBrowser.Api } [Route("/Notifications/Services", "GET", Summary = "Gets notification types")] - public class GetNotificationServices : IReturn> + public class GetNotificationServices : IReturn> { } @@ -84,49 +116,42 @@ namespace MediaBrowser.Api } [Authenticated] - public class NotificationsService : BaseApiService + public class NotificationsService : IService { - private readonly INotificationsRepository _notificationsRepo; private readonly INotificationManager _notificationManager; private readonly IUserManager _userManager; - public NotificationsService(INotificationsRepository notificationsRepo, INotificationManager notificationManager, IUserManager userManager) + public NotificationsService(INotificationManager notificationManager, IUserManager userManager) { - _notificationsRepo = notificationsRepo; _notificationManager = notificationManager; _userManager = userManager; } public object Get(GetNotificationTypes request) { - var result = _notificationManager.GetNotificationTypes(); - - return ToOptimizedResult(result); + return _notificationManager.GetNotificationTypes(); } public object Get(GetNotificationServices request) { - var result = _notificationManager.GetNotificationServices().ToList(); - - return ToOptimizedResult(result); + return _notificationManager.GetNotificationServices().ToList(); } public object Get(GetNotificationsSummary request) { - var result = _notificationsRepo.GetNotificationsSummary(request.UserId); + return new NotificationsSummary + { - return ToOptimizedResult(result); + }; } - public void Post(AddAdminNotification request) + public Task Post(AddAdminNotification request) { // This endpoint really just exists as post of a real with sickbeard - var task = AddNotification(request); - - Task.WaitAll(task); + return AddNotification(request); } - private async Task AddNotification(AddAdminNotification request) + private Task AddNotification(AddAdminNotification request) { var notification = new NotificationRequest { @@ -135,49 +160,23 @@ namespace MediaBrowser.Api Level = request.Level, Name = request.Name, Url = request.Url, - UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList() + UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray() }; - await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false); + return _notificationManager.SendNotification(notification, CancellationToken.None); } public void Post(MarkRead request) { - var task = MarkRead(request.Ids, request.UserId, true); - - Task.WaitAll(task); } public void Post(MarkUnread request) { - var task = MarkRead(request.Ids, request.UserId, false); - - Task.WaitAll(task); - } - - private Task MarkRead(string idList, string userId, bool read) - { - var ids = (idList ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (ids.Length == 0) - { - return _notificationsRepo.MarkAllRead(userId, read, CancellationToken.None); - } - - return _notificationsRepo.MarkRead(ids, userId, read, CancellationToken.None); } public object Get(GetNotifications request) { - var result = _notificationsRepo.GetNotifications(new NotificationQuery - { - IsRead = request.IsRead, - Limit = request.Limit, - StartIndex = request.StartIndex, - UserId = request.UserId - }); - - return ToOptimizedResult(result); + return new NotificationResult(); } } } diff --git a/Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs similarity index 61% rename from Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs rename to Emby.Notifications/CoreNotificationTypes.cs index b00b5d43b9..b45a75b1e8 100644 --- a/Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs +++ b/Emby.Notifications/CoreNotificationTypes.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.Globalization; -namespace Emby.Server.Implementations.Notifications +namespace Emby.Notifications { public class CoreNotificationTypes : INotificationTypeFactory { @@ -25,124 +25,87 @@ namespace Emby.Server.Implementations.Notifications { new NotificationTypeInfo { - Type = NotificationType.ApplicationUpdateInstalled.ToString(), - DefaultDescription = "{ReleaseNotes}", - DefaultTitle = "A new version of Emby Server has been installed.", - Variables = new string[]{"Version"} + Type = NotificationType.ApplicationUpdateInstalled.ToString() }, new NotificationTypeInfo { - Type = NotificationType.InstallationFailed.ToString(), - DefaultTitle = "{Name} installation failed.", - Variables = new string[]{"Name", "Version"} + Type = NotificationType.InstallationFailed.ToString() }, new NotificationTypeInfo { - Type = NotificationType.PluginInstalled.ToString(), - DefaultTitle = "{Name} was installed.", - Variables = new string[]{"Name", "Version"} + Type = NotificationType.PluginInstalled.ToString() }, new NotificationTypeInfo { - Type = NotificationType.PluginError.ToString(), - DefaultTitle = "{Name} has encountered an error.", - DefaultDescription = "{ErrorMessage}", - Variables = new string[]{"Name", "ErrorMessage"} + Type = NotificationType.PluginError.ToString() }, new NotificationTypeInfo { - Type = NotificationType.PluginUninstalled.ToString(), - DefaultTitle = "{Name} was uninstalled.", - Variables = new string[]{"Name", "Version"} + Type = NotificationType.PluginUninstalled.ToString() }, new NotificationTypeInfo { - Type = NotificationType.PluginUpdateInstalled.ToString(), - DefaultTitle = "{Name} was updated.", - DefaultDescription = "{ReleaseNotes}", - Variables = new string[]{"Name", "ReleaseNotes", "Version"} + Type = NotificationType.PluginUpdateInstalled.ToString() }, new NotificationTypeInfo { - Type = NotificationType.ServerRestartRequired.ToString(), - DefaultTitle = "Please restart Emby Server to finish updating." + Type = NotificationType.ServerRestartRequired.ToString() }, new NotificationTypeInfo { - Type = NotificationType.TaskFailed.ToString(), - DefaultTitle = "{Name} failed.", - DefaultDescription = "{ErrorMessage}", - Variables = new string[]{"Name", "ErrorMessage"} + Type = NotificationType.TaskFailed.ToString() }, new NotificationTypeInfo { - Type = NotificationType.NewLibraryContent.ToString(), - DefaultTitle = "{Name} has been added to your media library.", - Variables = new string[]{"Name"} + Type = NotificationType.NewLibraryContent.ToString() }, new NotificationTypeInfo { - Type = NotificationType.AudioPlayback.ToString(), - DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", - Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"} + Type = NotificationType.AudioPlayback.ToString() }, new NotificationTypeInfo { - Type = NotificationType.GamePlayback.ToString(), - DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", - Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"} + Type = NotificationType.GamePlayback.ToString() }, new NotificationTypeInfo { - Type = NotificationType.VideoPlayback.ToString(), - DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", - Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"} + Type = NotificationType.VideoPlayback.ToString() }, new NotificationTypeInfo { - Type = NotificationType.AudioPlaybackStopped.ToString(), - DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", - Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"} + Type = NotificationType.AudioPlaybackStopped.ToString() }, new NotificationTypeInfo { - Type = NotificationType.GamePlaybackStopped.ToString(), - DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", - Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"} + Type = NotificationType.GamePlaybackStopped.ToString() }, new NotificationTypeInfo { - Type = NotificationType.VideoPlaybackStopped.ToString(), - DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.", - Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"} + Type = NotificationType.VideoPlaybackStopped.ToString() }, new NotificationTypeInfo { - Type = NotificationType.CameraImageUploaded.ToString(), - DefaultTitle = "A new camera image has been uploaded from {DeviceName}.", - Variables = new string[]{"DeviceName"} + Type = NotificationType.CameraImageUploaded.ToString() }, new NotificationTypeInfo { - Type = NotificationType.UserLockedOut.ToString(), - DefaultTitle = "{UserName} has been locked out.", - Variables = new string[]{"UserName"} + Type = NotificationType.UserLockedOut.ToString() } }; @@ -150,8 +113,7 @@ namespace Emby.Server.Implementations.Notifications { knownTypes.Add(new NotificationTypeInfo { - Type = NotificationType.ApplicationUpdateAvailable.ToString(), - DefaultTitle = "A new version of Emby Server is available for download." + Type = NotificationType.ApplicationUpdateAvailable.ToString() }); } diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj new file mode 100644 index 0000000000..0a07c419ba --- /dev/null +++ b/Emby.Notifications/Emby.Notifications.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp2.1 + false + + + + + + + + + diff --git a/Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs b/Emby.Notifications/NotificationConfigurationFactory.cs similarity index 83% rename from Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs rename to Emby.Notifications/NotificationConfigurationFactory.cs index a7c5b12337..2d464910e9 100644 --- a/Emby.Server.Implementations/Notifications/NotificationConfigurationFactory.cs +++ b/Emby.Notifications/NotificationConfigurationFactory.cs @@ -2,13 +2,13 @@ using MediaBrowser.Model.Notifications; using System.Collections.Generic; -namespace Emby.Server.Implementations.Notifications +namespace Emby.Notifications { public class NotificationConfigurationFactory : IConfigurationFactory { public IEnumerable GetConfigurations() { - return new List + return new ConfigurationStore[] { new ConfigurationStore { diff --git a/Emby.Server.Implementations/Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs similarity index 63% rename from Emby.Server.Implementations/Notifications/NotificationManager.cs rename to Emby.Notifications/NotificationManager.cs index e11f2790e8..bb348d2677 100644 --- a/Emby.Server.Implementations/Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -12,8 +12,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Dto; -namespace Emby.Server.Implementations.Notifications +namespace Emby.Notifications { public class NotificationManager : INotificationManager { @@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.Notifications { var notificationType = request.NotificationType; - var options = string.IsNullOrWhiteSpace(notificationType) ? + var options = string.IsNullOrEmpty(notificationType) ? null : GetConfiguration().GetOptions(notificationType); @@ -54,8 +55,8 @@ namespace Emby.Server.Implementations.Notifications .Where(i => relatedItem == null || relatedItem.IsVisibleStandalone(i)) .ToArray(); - var title = GetTitle(request, options); - var description = GetDescription(request, options); + var title = request.Name; + var description = request.Description; var tasks = _services.Where(i => IsEnabled(i, notificationType)) .Select(i => SendNotification(request, i, users, title, description, cancellationToken)); @@ -78,7 +79,7 @@ namespace Emby.Server.Implementations.Notifications return Task.WhenAll(tasks); } - private IEnumerable GetUserIds(NotificationRequest request, NotificationOption options) + private IEnumerable GetUserIds(NotificationRequest request, NotificationOption options) { if (request.SendToUserMode.HasValue) { @@ -86,9 +87,9 @@ namespace Emby.Server.Implementations.Notifications { case SendToUserType.Admins: return _userManager.Users.Where(i => i.Policy.IsAdministrator) - .Select(i => i.Id.ToString("N")); + .Select(i => i.Id); case SendToUserType.All: - return _userManager.Users.Select(i => i.Id.ToString("N")); + return _userManager.Users.Select(i => i.Id); case SendToUserType.Custom: return request.UserIds; default: @@ -96,13 +97,13 @@ namespace Emby.Server.Implementations.Notifications } } - if (options != null && !string.IsNullOrWhiteSpace(request.NotificationType)) + if (options != null && !string.IsNullOrEmpty(request.NotificationType)) { var config = GetConfiguration(); return _userManager.Users .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) - .Select(i => i.Id.ToString("N")); + .Select(i => i.Id); } return request.UserIds; @@ -137,90 +138,6 @@ namespace Emby.Server.Implementations.Notifications } } - private string GetTitle(NotificationRequest request, NotificationOption options) - { - var title = request.Name; - - // If empty, grab from options - if (string.IsNullOrEmpty(title)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - if (options != null) - { - title = options.Title; - } - } - } - - // If still empty, grab default - if (string.IsNullOrEmpty(title)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase)); - - if (info != null) - { - title = info.DefaultTitle; - } - } - } - - title = title ?? string.Empty; - - foreach (var pair in request.Variables) - { - var token = "{" + pair.Key + "}"; - - title = title.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase); - } - - return title; - } - - private string GetDescription(NotificationRequest request, NotificationOption options) - { - var text = request.Description; - - // If empty, grab from options - if (string.IsNullOrEmpty(text)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - if (options != null) - { - text = options.Description; - } - } - } - - // If still empty, grab default - if (string.IsNullOrEmpty(text)) - { - if (!string.IsNullOrEmpty(request.NotificationType)) - { - var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase)); - - if (info != null) - { - text = info.DefaultDescription; - } - } - } - - text = text ?? string.Empty; - - foreach (var pair in request.Variables) - { - var token = "{" + pair.Key + "}"; - - text = text.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase); - } - - return text; - } - private bool IsEnabledForUser(INotificationService service, User user) { try @@ -241,13 +158,6 @@ namespace Emby.Server.Implementations.Notifications return true; } - var configurable = service as IConfigurableNotificationService; - - if (configurable != null) - { - return configurable.IsEnabled(notificationType); - } - return GetConfiguration().IsServiceEnabled(service.Name, notificationType); } @@ -283,15 +193,9 @@ namespace Emby.Server.Implementations.Notifications return list; } - public IEnumerable GetNotificationServices() + public IEnumerable GetNotificationServices() { - return _services.Where(i => - { - var configurable = i as IConfigurableNotificationService; - - return configurable == null || !configurable.IsHidden; - - }).Select(i => new NotificationServiceInfo + return _services.Select(i => new NameIdPair { Name = i.Name, Id = i.Name.GetMD5().ToString("N") diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/Notifications.cs new file mode 100644 index 0000000000..64863eb39e --- /dev/null +++ b/Emby.Notifications/Notifications.cs @@ -0,0 +1,299 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Tasks; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; + +namespace Emby.Notifications +{ + /// + /// Creates notifications for various system events + /// + public class Notifications : IServerEntryPoint + { + private readonly IInstallationManager _installationManager; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + + private readonly ITaskManager _taskManager; + private readonly INotificationManager _notificationManager; + + private readonly ILibraryManager _libraryManager; + private readonly ISessionManager _sessionManager; + private readonly IServerApplicationHost _appHost; + private readonly ITimerFactory _timerFactory; + + private ITimer LibraryUpdateTimer { get; set; } + private readonly object _libraryChangedSyncLock = new object(); + + private readonly IConfigurationManager _config; + private readonly IDeviceManager _deviceManager; + private readonly ILocalizationManager _localization; + private readonly IActivityManager _activityManager; + + private string[] _coreNotificationTypes; + + public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager, ITimerFactory timerFactory) + { + _installationManager = installationManager; + _userManager = userManager; + _logger = logger; + _taskManager = taskManager; + _notificationManager = notificationManager; + _libraryManager = libraryManager; + _sessionManager = sessionManager; + _appHost = appHost; + _config = config; + _deviceManager = deviceManager; + _timerFactory = timerFactory; + _localization = localization; + _activityManager = activityManager; + + _coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray(); + } + + public void Run() + { + _libraryManager.ItemAdded += _libraryManager_ItemAdded; + _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; + _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; + _activityManager.EntryCreated += _activityManager_EntryCreated; + } + + private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e) + { + var type = NotificationType.ServerRestartRequired.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type, + Name = string.Format(_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"), _appHost.Name) + }; + + await SendNotification(notification, null).ConfigureAwait(false); + } + + private async void _activityManager_EntryCreated(object sender, GenericEventArgs e) + { + var entry = e.Argument; + + var type = entry.Type; + + if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + var userId = e.Argument.UserId; + + if (!userId.Equals(Guid.Empty) && !GetOptions().IsEnabledToMonitorUser(type, userId)) + { + return; + } + + var notification = new NotificationRequest + { + NotificationType = type, + Name = entry.Name, + Description = entry.Overview + }; + + await SendNotification(notification, null).ConfigureAwait(false); + } + + private NotificationOptions GetOptions() + { + return _config.GetConfiguration("notifications"); + } + + async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) + { + // This notification is for users who can't auto-update (aka running as service) + if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate) + { + return; + } + + var type = NotificationType.ApplicationUpdateAvailable.ToString(); + + var notification = new NotificationRequest + { + Description = "Please see emby.media for details.", + NotificationType = type, + Name = _localization.GetLocalizedString("NewVersionIsAvailable") + }; + + await SendNotification(notification, null).ConfigureAwait(false); + } + + private readonly List _itemsAdded = new List(); + void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (!FilterItem(e.Item)) + { + return; + } + + lock (_libraryChangedSyncLock) + { + if (LibraryUpdateTimer == null) + { + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000, + Timeout.Infinite); + } + else + { + LibraryUpdateTimer.Change(5000, Timeout.Infinite); + } + + _itemsAdded.Add(e.Item); + } + } + + private bool FilterItem(BaseItem item) + { + if (item.IsFolder) + { + return false; + } + + if (!item.HasPathProtocol) + { + return false; + } + + if (item is IItemByName) + { + return false; + } + + return item.SourceType == SourceType.Library; + } + + private async void LibraryUpdateTimerCallback(object state) + { + List items; + + lock (_libraryChangedSyncLock) + { + items = _itemsAdded.ToList(); + _itemsAdded.Clear(); + DisposeLibraryUpdateTimer(); + } + + items = items.Take(10).ToList(); + + foreach (var item in items) + { + var notification = new NotificationRequest + { + NotificationType = NotificationType.NewLibraryContent.ToString(), + Name = string.Format(_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"), GetItemName(item)), + Description = item.Overview + }; + + await SendNotification(notification, item).ConfigureAwait(false); + } + } + + public static string GetItemName(BaseItem item) + { + var name = item.Name; + var episode = item as Episode; + if (episode != null) + { + if (episode.IndexNumber.HasValue) + { + name = string.Format("Ep{0} - {1}", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); + } + if (episode.ParentIndexNumber.HasValue) + { + name = string.Format("S{0}, {1}", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); + } + } + + var hasSeries = item as IHasSeriesName; + + if (hasSeries != null) + { + name = hasSeries.SeriesName + " - " + name; + } + + var hasAlbumArtist = item as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + var artists = hasAlbumArtist.AlbumArtists; + + if (artists.Length > 0) + { + name = artists[0] + " - " + name; + } + } + else + { + var hasArtist = item as IHasArtist; + if (hasArtist != null) + { + var artists = hasArtist.Artists; + + if (artists.Length > 0) + { + name = artists[0] + " - " + name; + } + } + } + + return name; + } + + private async Task SendNotification(NotificationRequest notification, BaseItem relatedItem) + { + try + { + await _notificationManager.SendNotification(notification, relatedItem, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending notification", ex); + } + } + + public void Dispose() + { + DisposeLibraryUpdateTimer(); + + _libraryManager.ItemAdded -= _libraryManager_ItemAdded; + _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; + _appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged; + _activityManager.EntryCreated -= _activityManager_EntryCreated; + } + + private void DisposeLibraryUpdateTimer() + { + if (LibraryUpdateTimer != null) + { + LibraryUpdateTimer.Dispose(); + LibraryUpdateTimer = null; + } + } + } +} diff --git a/MediaBrowser.ServerApplication/Properties/AssemblyInfo.cs b/Emby.Notifications/Properties/AssemblyInfo.cs similarity index 61% rename from MediaBrowser.ServerApplication/Properties/AssemblyInfo.cs rename to Emby.Notifications/Properties/AssemblyInfo.cs index 95113e0edd..d35c8b2895 100644 --- a/MediaBrowser.ServerApplication/Properties/AssemblyInfo.cs +++ b/Emby.Notifications/Properties/AssemblyInfo.cs @@ -1,30 +1,36 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Emby Server")] +[assembly: AssemblyTitle("Emby.Notifications")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Emby Server")] -[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyProduct("Emby.Notifications")] +[assembly: AssemblyCopyright("Copyright © 2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a5a1e61f-da85-4fd6-9d6d-bcf2bf09c1f8")] +[assembly: Guid("4d1d313b-60bb-4e11-acf9-cda6745266ef")] // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision -// \ No newline at end of file +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj index 21631aeb54..f16ce6b9c9 100644 --- a/Emby.Photos/Emby.Photos.csproj +++ b/Emby.Photos/Emby.Photos.csproj @@ -1,68 +1,20 @@ - - - - - Debug - AnyCPU - {89AB4548-770D-41FD-A891-8DAFF44F452C} - Library - Properties - Emby.Photos - Emby.Photos - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - + + + + ..\ThirdParty\taglib\TagLib.Portable.dll + - - Properties\SharedVersion.cs - - - + - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - - - \ No newline at end of file + + + netcoreapp2.1 + false + + + diff --git a/Emby.Photos/Emby.Photos.nuget.targets b/Emby.Photos/Emby.Photos.nuget.targets deleted file mode 100644 index e69ce0e64f..0000000000 --- a/Emby.Photos/Emby.Photos.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 11a7db47d3..f1a05607f8 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -14,10 +14,11 @@ using TagLib; using TagLib.IFD; using TagLib.IFD.Entries; using TagLib.IFD.Tags; +using MediaBrowser.Model.MediaInfo; namespace Emby.Photos { - public class PhotoProvider : ICustomMetadataProvider, IForcedProvider + public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor { private readonly ILogger _logger; private readonly IFileSystem _fileSystem; @@ -30,8 +31,22 @@ namespace Emby.Photos _imageProcessor = imageProcessor; } + public bool HasChanged(BaseItem item, IDirectoryService directoryService) + { + if (item.IsFileProtocol) + { + var file = directoryService.GetFile(item.Path); + if (file != null && file.LastWriteTimeUtc != item.DateModified) + { + return true; + } + } + + return false; + } + // These are causing taglib to hang - private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff" }; + private string[] _includextensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2" }; public Task FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken) { @@ -108,7 +123,10 @@ namespace Emby.Photos if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)) { - item.Name = image.ImageTag.Title; + if (!item.LockedFields.Contains(MetadataFields.Name)) + { + item.Name = image.ImageTag.Title; + } } var dateTaken = image.ImageTag.DateTime; @@ -119,7 +137,7 @@ namespace Emby.Photos item.ProductionYear = dateTaken.Value.Year; } - item.Genres = image.ImageTag.Genres.ToList(); + item.Genres = image.ImageTag.Genres; item.Tags = image.ImageTag.Keywords; item.Software = image.ImageTag.Software; @@ -161,15 +179,23 @@ namespace Emby.Photos } } - if (!item.Width.HasValue || !item.Height.HasValue) + if (item.Width <= 0 || item.Height <= 0) { var img = item.GetImageInfo(ImageType.Primary, 0); - var size = _imageProcessor.GetImageSize(item, img, false, false); - if (size.Width > 0 && size.Height > 0) + try { - item.Width = Convert.ToInt32(size.Width); - item.Height = Convert.ToInt32(size.Height); + var size = _imageProcessor.GetImageSize(item, img, false, false); + + if (size.Width > 0 && size.Height > 0) + { + item.Width = Convert.ToInt32(size.Width); + item.Height = Convert.ToInt32(size.Height); + } + } + catch (ArgumentException) + { + // format not supported } } diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 4e448ac64c..079d0af0ad 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -19,6 +19,11 @@ using System.Linq; using System.Text; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Dto; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Authentication; namespace Emby.Server.Implementations.Activity { @@ -26,7 +31,6 @@ namespace Emby.Server.Implementations.Activity { private readonly IInstallationManager _installationManager; - //private readonly ILogManager _logManager; //private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; @@ -38,10 +42,10 @@ namespace Emby.Server.Implementations.Activity private readonly IUserManager _userManager; private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; + private readonly IDeviceManager _deviceManager; - public ActivityLogEntryPoint(ISessionManager sessionManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) + public ActivityLogEntryPoint(ISessionManager sessionManager, IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) { - //_logger = _logManager.GetLogger("ActivityLogEntryPoint"); _sessionManager = sessionManager; _taskManager = taskManager; _activityManager = activityManager; @@ -51,21 +55,18 @@ namespace Emby.Server.Implementations.Activity _subManager = subManager; _userManager = userManager; _config = config; - //_logManager = logManager; _appHost = appHost; + _deviceManager = deviceManager; } public void Run() { - //_taskManager.TaskExecuting += _taskManager_TaskExecuting; - //_taskManager.TaskCompleted += _taskManager_TaskCompleted; + _taskManager.TaskCompleted += _taskManager_TaskCompleted; - //_installationManager.PluginInstalled += _installationManager_PluginInstalled; - //_installationManager.PluginUninstalled += _installationManager_PluginUninstalled; - //_installationManager.PluginUpdated += _installationManager_PluginUpdated; - - //_libraryManager.ItemAdded += _libraryManager_ItemAdded; - //_libraryManager.ItemRemoved += _libraryManager_ItemRemoved; + _installationManager.PluginInstalled += _installationManager_PluginInstalled; + _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; + _installationManager.PluginUpdated += _installationManager_PluginUpdated; + _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; _sessionManager.SessionStarted += _sessionManager_SessionStarted; _sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed; @@ -81,24 +82,33 @@ namespace Emby.Server.Implementations.Activity _userManager.UserCreated += _userManager_UserCreated; _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; _userManager.UserDeleted += _userManager_UserDeleted; - _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; _userManager.UserLockedOut += _userManager_UserLockedOut; //_config.ConfigurationUpdated += _config_ConfigurationUpdated; //_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; - //_logManager.LoggerLoaded += _logManager_LoggerLoaded; + _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; } + void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name), + Type = NotificationType.CameraImageUploaded.ToString() + }); + } + void _userManager_UserLockedOut(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name), - Type = "UserLockedOut", - UserId = e.Argument.Id.ToString("N") + Type = NotificationType.UserLockedOut.ToString(), + UserId = e.Argument.Id }); } @@ -106,11 +116,10 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureForItem"), Notifications.Notifications.GetItemName(e.Item)), + Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)), Type = "SubtitleDownloadFailure", ItemId = e.Item.Id.ToString("N"), - ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider), - Overview = LogHelper.GetLogMessage(e.Exception).ToString() + ShortOverview = e.Exception.Message }); } @@ -139,10 +148,9 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, Notifications.Notifications.GetItemName(item)), - Type = "PlaybackStopped", - ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), e.ClientName, e.DeviceName), - UserId = user.Id.ToString("N") + Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), + Type = GetPlaybackStoppedNotificationType(item.MediaType), + UserId = user.Id }); } @@ -171,19 +179,71 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, Notifications.Notifications.GetItemName(item)), - Type = "PlaybackStart", - ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), e.ClientName, e.DeviceName), - UserId = user.Id.ToString("N") + Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), + Type = GetPlaybackNotificationType(item.MediaType), + UserId = user.Id }); } + private static string GetItemName(BaseItemDto item) + { + var name = item.Name; + + if (!string.IsNullOrEmpty(item.SeriesName)) + { + name = item.SeriesName + " - " + name; + } + + if (item.Artists != null && item.Artists.Length > 0) + { + name = item.Artists[0] + " - " + name; + } + + return name; + } + + private string GetPlaybackNotificationType(string mediaType) + { + if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.AudioPlayback.ToString(); + } + if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.GamePlayback.ToString(); + } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.VideoPlayback.ToString(); + } + + return null; + } + + private string GetPlaybackStoppedNotificationType(string mediaType) + { + if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.AudioPlaybackStopped.ToString(); + } + if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.GamePlaybackStopped.ToString(); + } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + return NotificationType.VideoPlaybackStopped.ToString(); + } + + return null; + } + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; - if (string.IsNullOrWhiteSpace(session.UserName)) + if (string.IsNullOrEmpty(session.UserName)) { name = string.Format(_localization.GetLocalizedString("DeviceOfflineWithName"), session.DeviceName); @@ -200,17 +260,20 @@ namespace Emby.Server.Implementations.Activity Name = name, Type = "SessionEnded", ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint), - UserId = session.UserId.HasValue ? session.UserId.Value.ToString("N") : null + UserId = session.UserId }); } - void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs e) + void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs e) { + var user = e.Argument.User; + CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), e.Argument.Username), + Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name), Type = "AuthenticationSucceeded", - ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.RemoteEndPoint) + ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.SessionInfo.RemoteEndPoint), + UserId = user.Id }); } @@ -229,9 +292,8 @@ namespace Emby.Server.Implementations.Activity { CreateLogEntry(new ActivityLogEntry { - Name = _localization.GetLocalizedString("MessageApplicationUpdated"), - Type = "ApplicationUpdated", - ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr), + Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr), + Type = NotificationType.ApplicationUpdateInstalled.ToString(), Overview = e.Argument.description }); } @@ -254,13 +316,13 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs e) + void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserConfigurationUpdatedWithName"), e.Argument.Name), - Type = "UserConfigurationUpdated", - UserId = e.Argument.Id.ToString("N") + Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name), + Type = "UserPolicyUpdated", + UserId = e.Argument.Id }); } @@ -279,7 +341,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), Type = "UserPasswordChanged", - UserId = e.Argument.Id.ToString("N") + UserId = e.Argument.Id }); } @@ -289,7 +351,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), Type = "UserCreated", - UserId = e.Argument.Id.ToString("N") + UserId = e.Argument.Id }); } @@ -309,7 +371,7 @@ namespace Emby.Server.Implementations.Activity string name; var session = e.SessionInfo; - if (string.IsNullOrWhiteSpace(session.UserName)) + if (string.IsNullOrEmpty(session.UserName)) { name = string.Format(_localization.GetLocalizedString("DeviceOnlineWithName"), session.DeviceName); @@ -326,36 +388,7 @@ namespace Emby.Server.Implementations.Activity Name = name, Type = "SessionStarted", ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint), - UserId = session.UserId.HasValue ? session.UserId.Value.ToString("N") : null - }); - } - - void _libraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) - { - if (e.Item.SourceType != SourceType.Library) - { - return; - } - - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("ItemRemovedWithName"), Notifications.Notifications.GetItemName(e.Item)), - Type = "ItemRemoved" - }); - } - - void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (e.Item.SourceType != SourceType.Library) - { - return; - } - - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("ItemAddedWithName"), Notifications.Notifications.GetItemName(e.Item)), - Type = "ItemAdded", - ItemId = e.Item.Id.ToString("N") + UserId = session.UserId }); } @@ -364,7 +397,7 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), - Type = "PluginUpdated", + Type = NotificationType.PluginUpdateInstalled.ToString(), ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.Item2.versionStr), Overview = e.Argument.Item2.description }); @@ -375,7 +408,7 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), - Type = "PluginUninstalled" + Type = NotificationType.PluginUninstalled.ToString() }); } @@ -384,25 +417,21 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), - Type = "PluginInstalled", + Type = NotificationType.PluginInstalled.ToString(), ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr) }); } - void _taskManager_TaskExecuting(object sender, GenericEventArgs e) + void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) { - var task = e.Argument; - - var activityTask = task.ScheduledTask as IConfigurableScheduledTask; - if (activityTask != null && !activityTask.IsLogged) - { - return; - } + var installationInfo = e.InstallationInfo; CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("ScheduledTaskStartedWithName"), task.Name), - Type = "ScheduledTaskStarted" + Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name), + Type = NotificationType.InstallationFailed.ToString(), + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), installationInfo.Version), + Overview = e.Exception.Message }); } @@ -424,11 +453,11 @@ namespace Emby.Server.Implementations.Activity { var vals = new List(); - if (!string.IsNullOrWhiteSpace(e.Result.ErrorMessage)) + if (!string.IsNullOrEmpty(e.Result.ErrorMessage)) { vals.Add(e.Result.ErrorMessage); } - if (!string.IsNullOrWhiteSpace(e.Result.LongErrorMessage)) + if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) { vals.Add(e.Result.LongErrorMessage); } @@ -436,7 +465,7 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), - Type = "ScheduledTaskFailed", + Type = NotificationType.TaskFailed.ToString(), Overview = string.Join(Environment.NewLine, vals.ToArray(vals.Count)), ShortOverview = runningTime, Severity = LogSeverity.Error @@ -458,15 +487,12 @@ namespace Emby.Server.Implementations.Activity public void Dispose() { - _taskManager.TaskExecuting -= _taskManager_TaskExecuting; _taskManager.TaskCompleted -= _taskManager_TaskCompleted; _installationManager.PluginInstalled -= _installationManager_PluginInstalled; _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; _installationManager.PluginUpdated -= _installationManager_PluginUpdated; - - _libraryManager.ItemAdded -= _libraryManager_ItemAdded; - _libraryManager.ItemRemoved -= _libraryManager_ItemRemoved; + _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; _sessionManager.SessionStarted -= _sessionManager_SessionStarted; _sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed; @@ -482,16 +508,15 @@ namespace Emby.Server.Implementations.Activity _userManager.UserCreated -= _userManager_UserCreated; _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; _userManager.UserDeleted -= _userManager_UserDeleted; - _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; + _userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated; _userManager.UserLockedOut -= _userManager_UserLockedOut; _config.ConfigurationUpdated -= _config_ConfigurationUpdated; _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; - //_logManager.LoggerLoaded -= _logManager_LoggerLoaded; + _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; - GC.SuppressFinalize(this); } /// diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index 9a3f1ae472..047bebf231 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -6,7 +6,6 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using System; using System.Linq; -using System.Threading.Tasks; namespace Emby.Server.Implementations.Activity { @@ -27,7 +26,6 @@ namespace Emby.Server.Implementations.Activity public void Create(ActivityLogEntry entry) { - entry.Id = Guid.NewGuid().ToString("N"); entry.Date = DateTime.UtcNow; _repo.Create(entry); @@ -35,11 +33,11 @@ namespace Emby.Server.Implementations.Activity EventHelper.FireEventIfNotNull(EntryCreated, this, new GenericEventArgs(entry), _logger); } - public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) + public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) { - var result = _repo.GetActivityLogEntries(minDate, startIndex, limit); + var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit); - foreach (var item in result.Items.Where(i => !string.IsNullOrWhiteSpace(i.UserId))) + foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty))) { var user = _userManager.GetUserById(item.UserId); @@ -52,5 +50,10 @@ namespace Emby.Server.Implementations.Activity return result; } + + public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) + { + return GetActivityLogEntries(minDate, null, startIndex, limit); + } } } diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 6293cc69fa..ce9f460ffb 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -48,20 +48,76 @@ namespace Emby.Server.Implementations.Activity { RunDefaultInitialization(connection); - string[] queries = { - "create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY NOT NULL, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)", - "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)" - }; + connection.RunQueries(new[] + { + "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)", + "drop index if exists idx_ActivityLogEntries" + }); - connection.RunQueries(queries); + TryMigrate(connection); } } - private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries"; + private void TryMigrate(ManagedConnection connection) + { + try + { + if (TableExists(connection, "ActivityLogEntries")) + { + connection.RunQueries(new[] + { + "INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries", + "drop table if exists ActivityLogEntries" + }); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error migrating activity log database", ex); + } + } + + private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog"; public void Create(ActivityLogEntry entry) { - Update(entry); + if (entry == null) + { + throw new ArgumentNullException("entry"); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)")) + { + statement.TryBind("@Name", entry.Name); + + statement.TryBind("@Overview", entry.Overview); + statement.TryBind("@ShortOverview", entry.ShortOverview); + statement.TryBind("@Type", entry.Type); + statement.TryBind("@ItemId", entry.ItemId); + + if (entry.UserId.Equals(Guid.Empty)) + { + statement.TryBindNull("@UserId"); + } + else + { + statement.TryBind("@UserId", entry.UserId.ToString("N")); + } + + statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); + statement.TryBind("@LogSeverity", entry.Severity.ToString()); + + statement.MoveNext(); + } + }, TransactionMode); + } + } } public void Update(ActivityLogEntry entry) @@ -77,16 +133,25 @@ namespace Emby.Server.Implementations.Activity { connection.RunInTransaction(db => { - using (var statement = db.PrepareStatement("replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)")) + using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id")) { - statement.TryBind("@Id", entry.Id.ToGuidBlob()); - statement.TryBind("@Name", entry.Name); + statement.TryBind("@Id", entry.Id); + statement.TryBind("@Name", entry.Name); statement.TryBind("@Overview", entry.Overview); statement.TryBind("@ShortOverview", entry.ShortOverview); statement.TryBind("@Type", entry.Type); statement.TryBind("@ItemId", entry.ItemId); - statement.TryBind("@UserId", entry.UserId); + + if (entry.UserId.Equals(Guid.Empty)) + { + statement.TryBindNull("@UserId"); + } + else + { + statement.TryBind("@UserId", entry.UserId.ToString("N")); + } + statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); statement.TryBind("@LogSeverity", entry.Severity.ToString()); @@ -97,7 +162,7 @@ namespace Emby.Server.Implementations.Activity } } - public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) + public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) { using (WriteLock.Read()) { @@ -110,6 +175,17 @@ namespace Emby.Server.Implementations.Activity { whereClauses.Add("DateCreated>=@DateCreated"); } + if (hasUserId.HasValue) + { + if (hasUserId.Value) + { + whereClauses.Add("UserId not null"); + } + else + { + whereClauses.Add("UserId is null"); + } + } var whereTextWithoutPaging = whereClauses.Count == 0 ? string.Empty : @@ -121,7 +197,7 @@ namespace Emby.Server.Implementations.Activity string.Empty : " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})", pagingWhereText, startIndex.Value.ToString(_usCulture))); } @@ -141,7 +217,7 @@ namespace Emby.Server.Implementations.Activity var statementTexts = new List(); statementTexts.Add(commandText); - statementTexts.Add("select count (Id) from ActivityLogEntries" + whereTextWithoutPaging); + statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging); return connection.RunInTransaction(db => { @@ -187,7 +263,7 @@ namespace Emby.Server.Implementations.Activity var info = new ActivityLogEntry { - Id = reader[index].ReadGuidFromBlob().ToString("N") + Id = reader[index].ToInt64() }; index++; @@ -223,7 +299,7 @@ namespace Emby.Server.Implementations.Activity index++; if (reader[index].SQLiteType != SQLiteType.Null) { - info.UserId = reader[index].ToString(); + info.UserId = new Guid(reader[index].ToString()); } index++; diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 1e63aa1a6d..52e421374b 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -49,6 +49,15 @@ namespace Emby.Server.Implementations.AppBase } } + private const string _virtualDataPath = "%AppDataPath%"; + public string VirtualDataPath + { + get + { + return _virtualDataPath; + } + } + /// /// Gets the image cache path. /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 26450c06ca..3208c6a1f8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,12 +1,9 @@ using Emby.Common.Implementations.Serialization; -using Emby.Dlna; -using Emby.Dlna.ConnectionManager; -using Emby.Dlna.ContentDirectory; -using Emby.Dlna.Main; -using Emby.Dlna.MediaReceiverRegistrar; -using Emby.Dlna.Ssdp; using Emby.Drawing; using Emby.Photos; +using Emby.Dlna; +using Emby.Dlna.Main; +using Emby.Dlna.Ssdp; using Emby.Server.Implementations.Activity; using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Channels; @@ -26,14 +23,13 @@ using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.MediaEncoder; using Emby.Server.Implementations.Net; -using Emby.Server.Implementations.Notifications; +using Emby.Notifications; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Reflection; using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.Social; using Emby.Server.Implementations.Threading; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; @@ -46,7 +42,7 @@ using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Progress; +using MediaBrowser.Model.Extensions; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; @@ -60,6 +56,7 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -74,7 +71,6 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Controller.Sync; using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; @@ -93,7 +89,6 @@ using MediaBrowser.Model.News; using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.Social; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Text; @@ -105,7 +100,6 @@ using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; -using OpenSubtitlesHandler; using ServiceStack; using System; using System.Collections.Concurrent; @@ -122,13 +116,16 @@ using System.Threading; using System.Threading.Tasks; using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; +using MediaBrowser.Controller.Authentication; +using System.Diagnostics; +using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations { /// /// Class CompositionRoot /// - public abstract class ApplicationHost : IServerApplicationHost, IDependencyContainer, IDisposable + public abstract class ApplicationHost : IServerApplicationHost, IDisposable { /// /// Gets a value indicating whether this instance can self restart. @@ -218,29 +215,17 @@ namespace Emby.Server.Implementations /// The application paths. protected ServerApplicationPaths ApplicationPaths { get; set; } - /// - /// Gets assemblies that failed to load - /// - /// The failed assemblies. - public List FailedAssemblies { get; protected set; } - /// /// Gets all concrete types. /// /// All concrete types. - public Type[] AllConcreteTypes { get; protected set; } + public Tuple[] AllConcreteTypes { get; protected set; } /// /// The disposable parts /// protected readonly List DisposableParts = new List(); - /// - /// Gets a value indicating whether this instance is first run. - /// - /// true if this instance is first run; otherwise, false. - public bool IsFirstRun { get; private set; } - /// /// Gets the configuration manager. /// @@ -276,7 +261,6 @@ namespace Emby.Server.Implementations protected readonly SimpleInjector.Container Container = new SimpleInjector.Container(); protected ISystemEvents SystemEvents { get; set; } - protected IMemoryStreamFactory MemoryStreamFactory { get; set; } /// /// Gets the server configuration manager. @@ -296,11 +280,11 @@ namespace Emby.Server.Implementations return new ServerConfigurationManager(ApplicationPaths, LogManager, XmlSerializer, FileSystemManager); } - /// - /// Gets or sets the server manager. - /// - /// The server manager. - private IServerManager ServerManager { get; set; } + protected virtual IResourceFileManager CreateResourceFileManager() + { + return new ResourceFileManager(HttpResultFactory, LogManager.GetLogger("ResourceManager"), FileSystemManager); + } + /// /// Gets or sets the user manager. /// @@ -345,7 +329,7 @@ namespace Emby.Server.Implementations private IEncodingManager EncodingManager { get; set; } private IChannelManager ChannelManager { get; set; } - private ISyncManager SyncManager { get; set; } + protected ITextEncoding TextEncoding { get; private set; } /// /// Gets or sets the user data repository. @@ -355,7 +339,6 @@ namespace Emby.Server.Implementations private IUserRepository UserRepository { get; set; } internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; } internal IItemRepository ItemRepository { get; set; } - private INotificationsRepository NotificationsRepository { get; set; } private INotificationManager NotificationManager { get; set; } private ISubtitleManager SubtitleManager { get; set; } @@ -386,7 +369,7 @@ namespace Emby.Server.Implementations /// /// The zip client. protected IZipClient ZipClient { get; private set; } - + protected IHttpResultFactory HttpResultFactory { get; private set; } protected IAuthService AuthService { get; private set; } public StartupOptions StartupOptions { get; private set; } @@ -428,11 +411,9 @@ namespace Emby.Server.Implementations XmlSerializer = new MyXmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer")); NetworkManager = networkManager; + networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; EnvironmentInfo = environmentInfo; SystemEvents = systemEvents; - MemoryStreamFactory = new MemoryStreamProvider(); - - FailedAssemblies = new List(); ApplicationPaths = applicationPaths; LogManager = logManager; @@ -456,6 +437,27 @@ namespace Emby.Server.Implementations NetworkManager.NetworkChanged += NetworkManager_NetworkChanged; } + public string ExpandVirtualPath(string path) + { + var appPaths = ApplicationPaths; + + return path.Replace(appPaths.VirtualDataPath, appPaths.DataPath, StringComparison.OrdinalIgnoreCase) + .Replace(appPaths.VirtualInternalMetadataPath, appPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase); + } + + public string ReverseVirtualPath(string path) + { + var appPaths = ApplicationPaths; + + return path.Replace(appPaths.DataPath, appPaths.VirtualDataPath, StringComparison.OrdinalIgnoreCase) + .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase); + } + + private string[] GetConfiguredLocalSubnets() + { + return ServerConfigurationManager.Configuration.LocalNetworkSubnets; + } + private void NetworkManager_NetworkChanged(object sender, EventArgs e) { _validAddressResults.Clear(); @@ -470,7 +472,7 @@ namespace Emby.Server.Implementations { get { - return _version ?? (_version = GetAssembly(GetType()).GetName().Version); + return _version ?? (_version = GetType().GetTypeInfo().Assembly.GetName().Version); } } @@ -500,9 +502,17 @@ namespace Emby.Server.Implementations } } - private Assembly GetAssembly(Type type) + private Tuple GetAssembly(Type type) { - return type.GetTypeInfo().Assembly; + var assembly = type.GetTypeInfo().Assembly; + string path = null; + + return new Tuple(assembly, path); + } + + public virtual IStreamHelper CreateStreamHelper() + { + return new StreamHelper(); } public virtual bool SupportsAutoRunAtStartup @@ -520,16 +530,7 @@ namespace Emby.Server.Implementations /// System.Object. public object CreateInstance(Type type) { - try - { - return Container.GetInstance(type); - } - catch (Exception ex) - { - Logger.ErrorException("Error creating {0}", ex, type.FullName); - - throw; - } + return Container.GetInstance(type); } /// @@ -537,8 +538,10 @@ namespace Emby.Server.Implementations /// /// The type. /// System.Object. - protected object CreateInstanceSafe(Type type) + protected object CreateInstanceSafe(Tuple typeInfo) { + var type = typeInfo.Item1; + try { return Container.GetInstance(type); @@ -615,15 +618,16 @@ namespace Emby.Server.Implementations /// /// The file. /// Assembly. - protected Assembly LoadAssembly(string file) + protected Tuple LoadAssembly(string file) { try { - return Assembly.Load(File.ReadAllBytes(file)); + var assembly = Assembly.Load(File.ReadAllBytes(file)); + + return new Tuple(assembly, file); } catch (Exception ex) { - FailedAssemblies.Add(file); Logger.ErrorException("Error loading assembly {0}", ex, file); return null; } @@ -634,11 +638,11 @@ namespace Emby.Server.Implementations /// /// /// IEnumerable{Type}. - public IEnumerable GetExportTypes() + public IEnumerable> GetExportTypes() { var currentType = typeof(T); - return AllConcreteTypes.Where(currentType.IsAssignableFrom); + return AllConcreteTypes.Where(i => currentType.IsAssignableFrom(i.Item1)); } /// @@ -666,6 +670,33 @@ namespace Emby.Server.Implementations return parts; } + public List> GetExportsWithInfo(bool manageLiftime = true) + { + var parts = GetExportTypes() + .Select(i => + { + var obj = CreateInstanceSafe(i); + + if (obj == null) + { + return null; + } + return new Tuple((T)obj, i.Item2); + }) + .Where(i => i != null) + .ToList(); + + if (manageLiftime) + { + lock (DisposableParts) + { + DisposableParts.AddRange(parts.Select(i => i.Item1).OfType()); + } + } + + return parts; + } + private void SetBaseExceptionMessage() { var builder = GetBaseExceptionMessage(ApplicationPaths); @@ -687,25 +718,42 @@ namespace Emby.Server.Implementations ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; - await MediaEncoder.Init().ConfigureAwait(false); + MediaEncoder.Init(); - if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) - { - if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) - { - ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false; - ServerConfigurationManager.SaveConfiguration(); - } - } + //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) + //{ + // if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + // { + // ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false; + // ServerConfigurationManager.SaveConfiguration(); + // } + //} Logger.Info("ServerId: {0}", SystemId); + + var entryPoints = GetExports().ToList(); + RunEntryPoints(entryPoints, true); + Logger.Info("Core startup complete"); HttpServer.GlobalResponse = null; Logger.Info("Post-init migrations complete"); - foreach (var entryPoint in GetExports().ToList()) + RunEntryPoints(entryPoints, false); + Logger.Info("All entry points have started"); + + LogManager.RemoveConsoleOutput(); + } + + private void RunEntryPoints(IEnumerable entryPoints, bool isBeforeStartup) + { + foreach (var entryPoint in entryPoints) { + if (isBeforeStartup != (entryPoint is IRunBeforeStartup)) + { + continue; + } + var name = entryPoint.GetType().FullName; Logger.Info("Starting entry point {0}", name); var now = DateTime.UtcNow; @@ -719,9 +767,6 @@ namespace Emby.Server.Implementations } Logger.Info("Entry point completed: {0}. Duration: {1} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture), "ImageInfos"); } - Logger.Info("All entry points have started"); - - LogManager.RemoveConsoleOutput(); } /// @@ -741,20 +786,10 @@ namespace Emby.Server.Implementations private IJsonSerializer CreateJsonSerializer() { - try - { - // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4 - Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0="); - } - catch - { - // Failing under mono - } - return new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer")); } - public async Task Init(IProgress progress) + public void Init() { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -766,39 +801,22 @@ namespace Emby.Server.Implementations HttpsPort = ServerConfiguration.DefaultHttpsPort; } - progress.Report(1); - JsonSerializer = CreateJsonSerializer(); OnLoggerLoaded(true); LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false); - IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted; - progress.Report(2); - LogManager.LogSeverity = ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info; - progress.Report(3); - DiscoverTypes(); - progress.Report(14); SetHttpLimit(); - progress.Report(15); - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(.8 * p + 15)); - - await RegisterResources(innerProgress).ConfigureAwait(false); + RegisterResources(); FindParts(); - progress.Report(95); - - await InstallIsoMounters(CancellationToken.None).ConfigureAwait(false); - - progress.Report(100); } protected virtual void OnLoggerLoaded(bool isFirstLoad) @@ -810,16 +828,6 @@ namespace Emby.Server.Implementations LogEnvironmentInfo(Logger, ApplicationPaths, false); } - // Put the app config in the log for troubleshooting purposes - var configJson = new StringBuilder(JsonSerializer.SerializeToString(ConfigurationManager.CommonConfiguration)); - - if (!string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.CertificatePassword)) - { - configJson = configJson.Replace(ServerConfigurationManager.Configuration.CertificatePassword, "####"); - } - - Logger.LogMultiline("Application configuration:", LogSeverity.Info, configJson); - if (Plugins != null) { var pluginBuilder = new StringBuilder(); @@ -834,17 +842,18 @@ namespace Emby.Server.Implementations } protected abstract IConnectManager CreateConnectManager(); - protected abstract ISyncManager CreateSyncManager(); protected virtual IHttpClient CreateHttpClient() { - return new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent); + return new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, GetDefaultUserAgent); } + public static IStreamHelper StreamHelper { get; set; } + /// /// Registers resources that classes will depend on /// - protected async Task RegisterResources(IProgress progress) + protected void RegisterResources() { RegisterSingleInstance(ConfigurationManager); RegisterSingleInstance(this); @@ -852,7 +861,6 @@ namespace Emby.Server.Implementations RegisterSingleInstance(ApplicationPaths); RegisterSingleInstance(JsonSerializer); - RegisterSingleInstance(MemoryStreamFactory); RegisterSingleInstance(SystemEvents); RegisterSingleInstance(LogManager, false); @@ -881,6 +889,10 @@ namespace Emby.Server.Implementations TimerFactory = new TimerFactory(); RegisterSingleInstance(TimerFactory); + var streamHelper = CreateStreamHelper(); + ApplicationHost.StreamHelper = streamHelper; + RegisterSingleInstance(streamHelper); + RegisterSingleInstance(CryptographyProvider); SocketFactory = new SocketFactory(LogManager.GetLogger("SocketFactory")); @@ -891,13 +903,14 @@ namespace Emby.Server.Implementations SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager, FileSystemManager, CryptographyProvider); RegisterSingleInstance(SecurityManager); - InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime); + InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ServerConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime); RegisterSingleInstance(InstallationManager); ZipClient = new ZipClient(FileSystemManager); RegisterSingleInstance(ZipClient); - RegisterSingleInstance(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, MemoryStreamFactory)); + HttpResultFactory = new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); + RegisterSingleInstance(HttpResultFactory); RegisterSingleInstance(this); RegisterSingleInstance(ApplicationPaths); @@ -911,26 +924,25 @@ namespace Emby.Server.Implementations StringExtensions.LocalizationManager = LocalizationManager; RegisterSingleInstance(LocalizationManager); - ITextEncoding textEncoding = new TextEncoding.TextEncoding(FileSystemManager, LogManager.GetLogger("TextEncoding"), JsonSerializer); - RegisterSingleInstance(textEncoding); - Utilities.EncodingHelper = textEncoding; - BlurayExaminer = new BdInfoExaminer(FileSystemManager, textEncoding); + TextEncoding = new TextEncoding.TextEncoding(FileSystemManager, LogManager.GetLogger("TextEncoding"), JsonSerializer); + RegisterSingleInstance(TextEncoding); + BlurayExaminer = new BdInfoExaminer(FileSystemManager, TextEncoding); RegisterSingleInstance(BlurayExaminer); RegisterSingleInstance(new XmlReaderSettingsFactory()); - UserDataManager = new UserDataManager(LogManager, ServerConfigurationManager); + UserDataManager = new UserDataManager(LogManager, ServerConfigurationManager, () => UserManager); RegisterSingleInstance(UserDataManager); UserRepository = GetUserRepository(); // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it RegisterSingleInstance(UserRepository); - var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager.GetLogger("SqliteDisplayPreferencesRepository"), JsonSerializer, ApplicationPaths, MemoryStreamFactory, FileSystemManager); + var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager.GetLogger("SqliteDisplayPreferencesRepository"), JsonSerializer, ApplicationPaths, FileSystemManager); DisplayPreferencesRepository = displayPreferencesRepo; RegisterSingleInstance(DisplayPreferencesRepository); - var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager.GetLogger("SqliteItemRepository"), MemoryStreamFactory, assemblyInfo, FileSystemManager, EnvironmentInfo, TimerFactory); + var itemRepo = new SqliteItemRepository(ServerConfigurationManager, this, JsonSerializer, LogManager.GetLogger("SqliteItemRepository"), assemblyInfo, FileSystemManager, EnvironmentInfo, TimerFactory); ItemRepository = itemRepo; RegisterSingleInstance(ItemRepository); @@ -940,7 +952,7 @@ namespace Emby.Server.Implementations UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer, FileSystemManager, CryptographyProvider); RegisterSingleInstance(UserManager); - LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); + LibraryManager = new LibraryManager(this, Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); RegisterSingleInstance(LibraryManager); var musicManager = new MusicManager(LibraryManager); @@ -949,24 +961,23 @@ namespace Emby.Server.Implementations LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents, EnvironmentInfo); RegisterSingleInstance(LibraryMonitor); - ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer, MemoryStreamFactory); - RegisterSingleInstance(ProviderManager); - RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets); + HttpServer = new HttpListenerHost(this, + LogManager.GetLogger("HttpServer"), + ServerConfigurationManager, + "web/index.html", + NetworkManager, + TextEncoding, + JsonSerializer, + XmlSerializer, + GetParseFn); + HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); - RegisterSingleInstance(HttpServer, false); - progress.Report(10); - - ServerManager = new ServerManager.ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager, MemoryStreamFactory, textEncoding); - RegisterSingleInstance(ServerManager); - - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15)); + RegisterSingleInstance(HttpServer); ImageProcessor = GetImageProcessor(); RegisterSingleInstance(ImageProcessor); @@ -974,112 +985,101 @@ namespace Emby.Server.Implementations TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); RegisterSingleInstance(TVSeriesManager); - SyncManager = CreateSyncManager(); - RegisterSingleInstance(SyncManager); - - DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager); - RegisterSingleInstance(DtoService); - var encryptionManager = new EncryptionManager(); RegisterSingleInstance(encryptionManager); ConnectManager = CreateConnectManager(); RegisterSingleInstance(ConnectManager); - var deviceRepo = new SqliteDeviceRepository(LogManager.GetLogger("DeviceManager"), ServerConfigurationManager, FileSystemManager, JsonSerializer); - deviceRepo.Initialize(); - DeviceManager = new DeviceManager(deviceRepo, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager); - RegisterSingleInstance(deviceRepo); + DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager); RegisterSingleInstance(DeviceManager); var newsService = new Emby.Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - progress.Report(15); + MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager, TimerFactory, () => MediaEncoder); + RegisterSingleInstance(MediaSourceManager); + + SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, MediaSourceManager, ServerConfigurationManager, LocalizationManager); + RegisterSingleInstance(SubtitleManager); + + ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); + RegisterSingleInstance(ProviderManager); + + DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager); + RegisterSingleInstance(DtoService); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); RegisterSingleInstance(ChannelManager); - MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager, TimerFactory); - RegisterSingleInstance(MediaSourceManager); - SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager, TimerFactory); RegisterSingleInstance(SessionManager); var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer, this, assemblyInfo); RegisterSingleInstance(dlnaManager); - var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient, new XmlReaderSettingsFactory()); - RegisterSingleInstance(connectionManager); - - CollectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"), ProviderManager); + CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"), ProviderManager); RegisterSingleInstance(CollectionManager); PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager, ProviderManager); RegisterSingleInstance(PlaylistManager); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, SecurityManager); + LiveTvManager = new LiveTvManager(this, HttpClient, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, SecurityManager, () => ChannelManager); RegisterSingleInstance(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); RegisterSingleInstance(UserViewManager); - var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager, () => MediaEncoder, new XmlReaderSettingsFactory(), TVSeriesManager); - RegisterSingleInstance(contentDirectory); - - var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager, new XmlReaderSettingsFactory()); - RegisterSingleInstance(mediaRegistrar); - NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager); RegisterSingleInstance(NotificationManager); - SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager, ServerConfigurationManager); - RegisterSingleInstance(SubtitleManager); - RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory)); ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository); RegisterSingleInstance(ChapterManager); - await RegisterMediaEncoder(innerProgress).ConfigureAwait(false); - progress.Report(90); + RegisterMediaEncoder(assemblyInfo); EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager); RegisterSingleInstance(EncodingManager); - var sharingRepo = new SharingRepository(LogManager.GetLogger("SharingRepository"), ApplicationPaths, FileSystemManager); - sharingRepo.Initialize(); - // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it - RegisterSingleInstance(sharingRepo); - RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); - var activityLogRepo = GetActivityLogRepository(); RegisterSingleInstance(activityLogRepo); RegisterSingleInstance(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager)); - var authContext = new AuthorizationContext(AuthenticationRepository, ConnectManager); + var authContext = new AuthorizationContext(AuthenticationRepository, ConnectManager, UserManager); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager); + AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, NetworkManager); RegisterSingleInstance(AuthService); - SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, MemoryStreamFactory, ProcessFactory, textEncoding); + SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory, TextEncoding); RegisterSingleInstance(SubtitleEncoder); + RegisterSingleInstance(CreateResourceFileManager()); + displayPreferencesRepo.Initialize(); var userDataRepo = new SqliteUserDataRepository(LogManager.GetLogger("SqliteUserDataRepository"), ApplicationPaths, FileSystemManager); - ((UserDataManager)UserDataManager).Repository = userDataRepo; - itemRepo.Initialize(userDataRepo); - ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; - ConfigureNotificationsRepository(); - progress.Report(100); - SetStaticProperties(); ((UserManager)UserManager).Initialize(); + + ((UserDataManager)UserDataManager).Repository = userDataRepo; + itemRepo.Initialize(userDataRepo, UserManager); + ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; + } + + protected virtual IBrotliCompressor CreateBrotliCompressor() + { + return null; + } + + private static Func GetParseFn(Type propertyType) + { + return s => JsvReader.GetParseFn(propertyType)(s); } public virtual string PackageRuntime @@ -1099,7 +1099,13 @@ namespace Emby.Server.Implementations { var builder = new StringBuilder(); - builder.AppendLine(string.Format("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs()))); + // Distinct these to prevent users from reporting problems that aren't actually problems + var commandLineArgs = Environment + .GetCommandLineArgs() + .Distinct() + .ToArray(); + + builder.AppendLine(string.Format("Command line: {0}", string.Join(" ", commandLineArgs))); builder.AppendLine(string.Format("Operating system: {0}", Environment.OSVersion)); builder.AppendLine(string.Format("64-Bit OS: {0}", Environment.Is64BitOperatingSystem)); @@ -1136,37 +1142,6 @@ namespace Emby.Server.Implementations } } - /// - /// Installs the iso mounters. - /// - /// The cancellation token. - /// Task. - private async Task InstallIsoMounters(CancellationToken cancellationToken) - { - var list = new List(); - - foreach (var isoMounter in GetExports()) - { - try - { - if (isoMounter.RequiresInstallation && !isoMounter.IsInstalled) - { - Logger.Info("Installing {0}", isoMounter.Name); - - await isoMounter.Install(cancellationToken).ConfigureAwait(false); - } - - list.Add(isoMounter); - } - catch (Exception ex) - { - Logger.ErrorException("{0} failed to load.", ex, isoMounter.Name); - } - } - - IsoManager.AddParts(list); - } - protected string GetDefaultUserAgent() { var name = FormatAttribute(Name); @@ -1222,7 +1197,7 @@ namespace Emby.Server.Implementations //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { - //throw new FileNotFoundException("Secure requested, no private key included", certificateLocation); + Logger.Error("No private key included in SSL cert {0}.", certificateLocation); return null; } @@ -1255,7 +1230,6 @@ namespace Emby.Server.Implementations info.FFProbeFilename = "ffprobe"; info.ArchiveType = "7z"; info.Version = "20170308"; - info.DownloadUrls = GetLinuxDownloadUrls(); } else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) { @@ -1263,7 +1237,6 @@ namespace Emby.Server.Implementations info.FFProbeFilename = "ffprobe.exe"; info.Version = "20170308"; info.ArchiveType = "7z"; - info.DownloadUrls = GetWindowsDownloadUrls(); } else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) { @@ -1271,80 +1244,27 @@ namespace Emby.Server.Implementations info.FFProbeFilename = "ffprobe"; info.ArchiveType = "7z"; info.Version = "20170308"; - info.DownloadUrls = GetMacDownloadUrls(); - } - else - { - // No version available - user requirement - info.DownloadUrls = new string[] { }; } return info; } - private string[] GetMacDownloadUrls() + protected virtual FFMpegInfo GetFFMpegInfo() { - switch (EnvironmentInfo.SystemArchitecture) - { - case MediaBrowser.Model.System.Architecture.X64: - return new[] - { - "https://embydata.com/downloads/ffmpeg/osx/ffmpeg-x64-20170308.7z" - }; - } - - return new string[] { }; - } - - private string[] GetWindowsDownloadUrls() - { - switch (EnvironmentInfo.SystemArchitecture) - { - case MediaBrowser.Model.System.Architecture.X64: - return new[] - { - "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win64.7z" - }; - case MediaBrowser.Model.System.Architecture.X86: - return new[] - { - "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win32.7z" - }; - } - - return new string[] { }; - } - - private string[] GetLinuxDownloadUrls() - { - switch (EnvironmentInfo.SystemArchitecture) - { - case MediaBrowser.Model.System.Architecture.X64: - return new[] - { - "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-64bit-static.7z" - }; - case MediaBrowser.Model.System.Architecture.X86: - return new[] - { - "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-32bit-static.7z" - }; - } - - return new string[] { }; + return new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo()) + .GetFFMpegInfo(StartupOptions); } /// /// Registers the media encoder. /// /// Task. - private async Task RegisterMediaEncoder(IProgress progress) + private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) { string encoderPath = null; string probePath = null; - var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo()) - .GetFFMpegInfo(StartupOptions, progress).ConfigureAwait(false); + var info = GetFFMpegInfo(); encoderPath = info.EncoderPath; probePath = info.ProbePath; @@ -1366,12 +1286,11 @@ namespace Emby.Server.Implementations () => MediaSourceManager, HttpClient, ZipClient, - MemoryStreamFactory, ProcessFactory, - (Environment.ProcessorCount > 2 ? 14000 : 40000), - EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows, EnvironmentInfo, - BlurayExaminer); + BlurayExaminer, + assemblyInfo, + this); MediaEncoder = mediaEncoder; RegisterSingleInstance(MediaEncoder); @@ -1383,7 +1302,7 @@ namespace Emby.Server.Implementations /// Task{IUserRepository}. private IUserRepository GetUserRepository() { - var repo = new SqliteUserRepository(LogManager.GetLogger("SqliteUserRepository"), ApplicationPaths, JsonSerializer, MemoryStreamFactory); + var repo = new SqliteUserRepository(LogManager.GetLogger("SqliteUserRepository"), ApplicationPaths, JsonSerializer); repo.Initialize(); @@ -1392,7 +1311,7 @@ namespace Emby.Server.Implementations private IAuthenticationRepository GetAuthenticationRepository() { - var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths); + var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager); repo.Initialize(); @@ -1408,25 +1327,13 @@ namespace Emby.Server.Implementations return repo; } - /// - /// Configures the repositories. - /// - private void ConfigureNotificationsRepository() - { - var repo = new SqliteNotificationsRepository(LogManager.GetLogger("SqliteNotificationsRepository"), ServerConfigurationManager.ApplicationPaths, FileSystemManager); - - repo.Initialize(); - - NotificationsRepository = repo; - - RegisterSingleInstance(NotificationsRepository); - } - /// /// Dirty hacks /// private void SetStaticProperties() { + ((SqliteItemRepository)ItemRepository).ImageProcessor = ImageProcessor; + // For now there's no real way to inject these properly BaseItem.Logger = LogManager.GetLogger("BaseItem"); BaseItem.ConfigurationManager = ServerConfigurationManager; @@ -1434,20 +1341,19 @@ namespace Emby.Server.Implementations BaseItem.ProviderManager = ProviderManager; BaseItem.LocalizationManager = LocalizationManager; BaseItem.ItemRepository = ItemRepository; - User.XmlSerializer = XmlSerializer; User.UserManager = UserManager; - Folder.UserManager = UserManager; BaseItem.FileSystem = FileSystemManager; BaseItem.UserDataManager = UserDataManager; BaseItem.ChannelManager = ChannelManager; - BaseItem.LiveTvManager = LiveTvManager; + Video.LiveTvManager = LiveTvManager; Folder.UserViewManager = UserViewManager; UserView.TVSeriesManager = TVSeriesManager; UserView.PlaylistManager = PlaylistManager; - BaseItem.CollectionManager = CollectionManager; + UserView.CollectionManager = CollectionManager; BaseItem.MediaSourceManager = MediaSourceManager; CollectionFolder.XmlSerializer = XmlSerializer; - Utilities.CryptographyProvider = CryptographyProvider; + CollectionFolder.JsonSerializer = JsonSerializer; + CollectionFolder.ApplicationHost = this; AuthenticatedAttribute.AuthService = AuthService; } @@ -1463,19 +1369,14 @@ namespace Emby.Server.Implementations ConfigurationManager.SaveConfiguration(); } - RegisterModules(); - ConfigurationManager.AddParts(GetExports()); - Plugins = GetExports().Select(LoadPlugin).Where(i => i != null).ToArray(); + Plugins = GetExportsWithInfo().Select(LoadPlugin).Where(i => i != null).ToArray(); - HttpServer.Init(GetExports(false)); - - ServerManager.AddWebSocketListeners(GetExports(false)); + HttpServer.Init(GetExports(false), GetExports()); StartServer(); LibraryManager.AddParts(GetExports(), - GetExports(), GetExports(), GetExports(), GetExports(), @@ -1493,18 +1394,21 @@ namespace Emby.Server.Implementations SubtitleManager.AddParts(GetExports()); - SessionManager.AddParts(GetExports()); - ChannelManager.AddParts(GetExports()); MediaSourceManager.AddParts(GetExports()); NotificationManager.AddParts(GetExports(), GetExports()); - SyncManager.AddParts(GetExports()); + UserManager.AddParts(GetExports()); + + IsoManager.AddParts(GetExports()); } - private IPlugin LoadPlugin(IPlugin plugin) + private IPlugin LoadPlugin(Tuple info) { + var plugin = info.Item1; + var assemblyFilePath = info.Item2; + try { var assemblyPlugin = plugin as IPluginAssembly; @@ -1514,10 +1418,9 @@ namespace Emby.Server.Implementations var assembly = plugin.GetType().Assembly; var assemblyName = assembly.GetName(); - var assemblyFileName = assemblyName.Name + ".dll"; - var assemblyFilePath = Path.Combine(ApplicationPaths.PluginsPath, assemblyFileName); + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - assemblyPlugin.SetAttributes(assemblyFilePath, assemblyFileName, assemblyName.Version); + assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); try { @@ -1536,8 +1439,11 @@ namespace Emby.Server.Implementations } } - var isFirstRun = !File.Exists(plugin.ConfigurationFilePath); - plugin.SetStartupInfo(isFirstRun, File.GetLastWriteTimeUtc, s => Directory.CreateDirectory(s)); + var hasPluginConfiguration = plugin as IHasPluginConfiguration; + if (hasPluginConfiguration != null) + { + hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); + } } catch (Exception ex) { @@ -1553,18 +1459,32 @@ namespace Emby.Server.Implementations /// protected void DiscoverTypes() { - FailedAssemblies.Clear(); + Logger.Info("Loading assemblies"); - var assemblies = GetComposablePartAssemblies().ToList(); + var assemblyInfos = GetComposablePartAssemblies(); - foreach (var assembly in assemblies) + foreach (var assemblyInfo in assemblyInfos) { - Logger.Info("Loading {0}", assembly.FullName); + var assembly = assemblyInfo.Item1; + var path = assemblyInfo.Item2; + + if (path == null) + { + Logger.Info("Loading {0}", assembly.FullName); + } + else + { + Logger.Info("Loading {0} from {1}", assembly.FullName, path); + } } - AllConcreteTypes = assemblies + AllConcreteTypes = assemblyInfos .SelectMany(GetTypes) - .Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType) + .Where(info => + { + var t = info.Item1; + return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType; + }) .ToArray(); } @@ -1572,22 +1492,21 @@ namespace Emby.Server.Implementations /// 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 /// - /// The assembly. - /// IEnumerable{Type}. - /// assembly - protected List GetTypes(Assembly assembly) + protected List> GetTypes(Tuple assemblyInfo) { - if (assembly == null) + if (assemblyInfo == null) { - return new List(); + return new List>(); } + var assembly = assemblyInfo.Item1; + try { // This null checking really shouldn't be needed but adding it due to some // unhandled exceptions in mono 5.0 that are a little hard to hunt down var types = assembly.GetTypes() ?? new Type[] { }; - return types.Where(t => t != null).ToList(); + return types.Where(t => t != null).Select(i => new Tuple(i, assemblyInfo.Item2)).ToList(); } catch (ReflectionTypeLoadException ex) { @@ -1604,24 +1523,22 @@ namespace Emby.Server.Implementations // If it fails we can still get a list of the Types it was able to resolve var types = ex.Types ?? new Type[] { }; - return types.Where(t => t != null).ToList(); + return types.Where(t => t != null).Select(i => new Tuple(i, assemblyInfo.Item2)).ToList(); } catch (Exception ex) { Logger.ErrorException("Error loading types from assembly", ex); - return new List(); + return new List>(); } } private CertificateInfo CertificateInfo { get; set; } - private X509Certificate Certificate { get; set; } + protected X509Certificate Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { - var hosts = new List(); - - hosts.Add("+"); + var hosts = new[] { "+" }; return hosts.SelectMany(i => { @@ -1639,6 +1556,8 @@ namespace Emby.Server.Implementations }); } + protected abstract IHttpListener CreateHttpListener(); + /// /// Starts the server. /// @@ -1646,12 +1565,16 @@ namespace Emby.Server.Implementations { try { - ServerManager.Start(GetUrlPrefixes().ToArray()); + ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener()); return; } catch (Exception ex) { - Logger.ErrorException("Error starting http server", ex); + var msg = string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase) + ? "The http server is unable to start due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system." + : "Error starting Http Server"; + + Logger.ErrorException(msg, ex); if (HttpPort == ServerConfiguration.DefaultHttpPort) { @@ -1663,7 +1586,7 @@ namespace Emby.Server.Implementations try { - ServerManager.Start(GetUrlPrefixes().ToArray()); + ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener()); } catch (Exception ex) { @@ -1822,10 +1745,9 @@ namespace Emby.Server.Implementations /// Gets the composable part assemblies. /// /// IEnumerable{Assembly}. - protected IEnumerable GetComposablePartAssemblies() + protected List> GetComposablePartAssemblies() { - var list = GetPluginAssemblies() - .ToList(); + var list = GetPluginAssemblies(); // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that // This will prevent the .dll file from getting locked, and allow us to replace it when needed @@ -1863,10 +1785,13 @@ namespace Emby.Server.Implementations // Local metadata list.Add(GetAssembly(typeof(BoxSetXmlSaver))); + // Notifications + list.Add(GetAssembly(typeof(NotificationManager))); + // Xbmc list.Add(GetAssembly(typeof(ArtistNfoProvider))); - list.AddRange(GetAssembliesWithPartsInternal()); + list.AddRange(GetAssembliesWithPartsInternal().Select(i => new Tuple(i, null))); return list.ToList(); } @@ -1877,25 +1802,92 @@ namespace Emby.Server.Implementations /// Gets the plugin assemblies. /// /// IEnumerable{Assembly}. - private IEnumerable GetPluginAssemblies() + private List> GetPluginAssemblies() + { + // Copy pre-installed plugins + var sourcePath = Path.Combine(ApplicationPaths.ApplicationResourcesPath, "plugins"); + CopyPlugins(sourcePath, ApplicationPaths.PluginsPath); + + return GetPluginAssemblies(ApplicationPaths.PluginsPath); + } + + private void CopyPlugins(string source, string target) + { + List files; + + try + { + files = Directory.EnumerateFiles(source, "*.dll", SearchOption.TopDirectoryOnly) + .ToList(); + + } + catch (DirectoryNotFoundException) + { + return; + } + + if (files.Count == 0) + { + return; + } + + foreach (var sourceFile in files) + { + var filename = Path.GetFileName(sourceFile); + var targetFile = Path.Combine(target, filename); + + var targetFileExists = File.Exists(targetFile); + + if (!targetFileExists && ServerConfigurationManager.Configuration.UninstalledPlugins.Contains(filename, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + if (targetFileExists && GetDllVersion(targetFile) >= GetDllVersion(sourceFile)) + { + continue; + } + + Directory.CreateDirectory(target); + File.Copy(sourceFile, targetFile, true); + } + } + + private Version GetDllVersion(string path) { try { - return Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly) - .Where(EnablePlugin) + var result = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion); + + Logger.Info("File {0} has version {1}", path, result); + + return result; + } + catch (Exception ex) + { + Logger.ErrorException("Error getting version number from {0}", ex, path); + + return new Version(1, 0); + } + } + + private List> GetPluginAssemblies(string path) + { + try + { + return FilterAssembliesToLoad(Directory.EnumerateFiles(path, "*.dll", SearchOption.TopDirectoryOnly)) .Select(LoadAssembly) .Where(a => a != null) .ToList(); } catch (DirectoryNotFoundException) { - return new List(); + return new List>(); } } - private bool EnablePlugin(string path) + private IEnumerable FilterAssembliesToLoad(IEnumerable paths) { - var filename = Path.GetFileName(path); var exclude = new[] { @@ -1903,6 +1895,7 @@ namespace Emby.Server.Implementations "mbintros.dll", "embytv.dll", "Messenger.dll", + "Messages.dll", "MediaBrowser.Plugins.TvMazeProvider.dll", "MBBookshelf.dll", "MediaBrowser.Channels.Adult.YouJizz.dll", @@ -1927,10 +1920,49 @@ namespace Emby.Server.Implementations "MediaBrowser.Channels.HitboxTV.dll", "MediaBrowser.Channels.HockeyStreams.dll", "MediaBrowser.Plugins.ITV.dll", - "MediaBrowser.Plugins.Lastfm.dll" + "MediaBrowser.Plugins.Lastfm.dll", + "ServerRestart.dll", + "MediaBrowser.Plugins.NotifyMyAndroidNotifications.dll", + "MetadataViewer.dll" }; - return !exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase); + var minRequiredVersions = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "GameBrowser.dll", new Version(3, 1) }, + { "moviethemesongs.dll", new Version(1, 6) }, + { "themesongs.dll", new Version(1, 2) } + }; + + return paths.Where(path => + { + var filename = Path.GetFileName(path); + if (exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + Version minRequiredVersion; + if (minRequiredVersions.TryGetValue(filename, out minRequiredVersion)) + { + try + { + var version = Version.Parse(FileVersionInfo.GetVersionInfo(path).FileVersion); + + if (version < minRequiredVersion) + { + Logger.Info("Not loading {0} {1} because the minimum supported version is {2}. Please update to the newer version", filename, version, minRequiredVersion); + return false; + } + } + catch (Exception ex) + { + Logger.ErrorException("Error getting version number from {0}", ex, path); + + return false; + } + } + return true; + }); } /// @@ -1947,16 +1979,13 @@ namespace Emby.Server.Implementations IsShuttingDown = IsShuttingDown, Version = ApplicationVersion.ToString(), WebSocketPortNumber = HttpPort, - FailedPluginAssemblies = FailedAssemblies.ToArray(), - InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToArray(), CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), Id = SystemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, LogPath = ApplicationPaths.LogDirectoryPath, - ItemsByNamePath = ApplicationPaths.ItemsByNamePath, + ItemsByNamePath = ApplicationPaths.InternalMetadataPath, InternalMetadataPath = ApplicationPaths.InternalMetadataPath, CachePath = ApplicationPaths.CachePath, - MacAddress = GetMacAddress(), HttpServerPortNumber = HttpPort, SupportsHttps = SupportsHttps, HttpsPortNumber = HttpsPort, @@ -1979,6 +2008,16 @@ namespace Emby.Server.Implementations }; } + public WakeOnLanInfo[] GetWakeOnLanInfo() + { + return NetworkManager.GetMacAddresses() + .Select(i => new WakeOnLanInfo + { + MacAddress = i + }) + .ToArray(); + } + public async Task GetPublicSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); @@ -1998,7 +2037,7 @@ namespace Emby.Server.Implementations { get { - return SupportsHttps && (ServerConfigurationManager.Configuration.EnableHttps || ServerConfigurationManager.Configuration.RequireHttps); + return SupportsHttps && ServerConfigurationManager.Configuration.EnableHttps; } } @@ -2127,14 +2166,20 @@ namespace Emby.Server.Implementations return cachedResult; } + var logPing = false; + +#if DEBUG + logPing = true; +#endif + try { using (var response = await HttpClient.SendAsync(new HttpRequestOptions { Url = apiUrl, LogErrorResponseBody = false, - LogErrors = false, - LogRequest = false, + LogErrors = logPing, + LogRequest = logPing, TimeoutMs = 30000, BufferContent = false, @@ -2158,9 +2203,9 @@ namespace Emby.Server.Implementations Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled"); throw; } - catch + catch (Exception ex) { - Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false); + Logger.Debug("Ping test result to {0}. Success: {1} {2}", apiUrl, false, ex.Message); _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false); return false; @@ -2171,7 +2216,7 @@ namespace Emby.Server.Implementations { get { - return string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.ServerName) + return string.IsNullOrEmpty(ServerConfigurationManager.Configuration.ServerName) ? Environment.MachineName : ServerConfigurationManager.Configuration.ServerName; } @@ -2181,23 +2226,6 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } - /// - /// Gets the mac address. - /// - /// System.String. - private string GetMacAddress() - { - try - { - return NetworkManager.GetMacAddress(); - } - catch (Exception ex) - { - Logger.ErrorException("Error getting mac address", ex); - return null; - } - } - /// /// Shuts down. /// @@ -2296,7 +2324,7 @@ namespace Emby.Server.Implementations try { var result = await new GithubUpdater(HttpClient, JsonSerializer).CheckForUpdateResult("MediaBrowser", - "Emby", + "Emby.Releases", ApplicationVersion, updateLevel, ReleaseAssetFilename, @@ -2370,7 +2398,7 @@ namespace Emby.Server.Implementations /// The hostname in private static string GetHostnameFromExternalDns(string externalDns) { - if (string.IsNullOrWhiteSpace(externalDns)) + if (string.IsNullOrEmpty(externalDns)) { return "localhost"; } @@ -2424,25 +2452,6 @@ namespace Emby.Server.Implementations { } - private void RegisterModules() - { - var moduleTypes = GetExportTypes(); - - foreach (var type in moduleTypes) - { - try - { - var instance = Activator.CreateInstance(type) as IDependencyModule; - if (instance != null) - instance.BindDependencies(this); - } - catch (Exception ex) - { - Logger.ErrorException("Error setting up dependency bindings for " + type.Name, ex); - } - } - } - /// /// Called when [application updated]. /// @@ -2471,7 +2480,6 @@ namespace Emby.Server.Implementations _disposed = true; Dispose(true); - GC.SuppressFinalize(this); } } @@ -2507,19 +2515,50 @@ namespace Emby.Server.Implementations } } - void IDependencyContainer.RegisterSingleInstance(T obj, bool manageLifetime) + private Dictionary _values; + public string GetValue(string name) { - RegisterSingleInstance(obj, manageLifetime); + if (_values == null) + { + _values = LoadValues(); + } + + string value; + + if (_values.TryGetValue(name, out value)) + { + return value; + } + + return null; } - void IDependencyContainer.RegisterSingleInstance(Func func) + private Dictionary LoadValues() { - RegisterSingleInstance(func); - } + Dictionary values = new Dictionary(StringComparer.OrdinalIgnoreCase); - void IDependencyContainer.Register(Type typeInterface, Type typeImplementation) - { - Container.Register(typeInterface, typeImplementation); + using (var stream = typeof(ApplicationHost).Assembly.GetManifestResourceStream(typeof(ApplicationHost).Namespace + ".values.txt")) + { + using (var reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (string.IsNullOrEmpty(line)) + { + continue; + } + + var index = line.IndexOf('='); + if (index != -1) + { + values[line.Substring(0, index)] = line.Substring(index + 1); + } + } + } + } + + return values; } } diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs index 32938e151f..fd61f2617e 100644 --- a/Emby.Server.Implementations/Archiving/ZipClient.cs +++ b/Emby.Server.Implementations/Archiving/ZipClient.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.IO; using SharpCompress.Archives.Rar; using SharpCompress.Archives.SevenZip; using SharpCompress.Archives.Tar; +using SharpCompress.Common; using SharpCompress.Readers; using SharpCompress.Readers.GZip; using SharpCompress.Readers.Zip; @@ -185,44 +186,5 @@ namespace Emby.Server.Implementations.Archiving } } } - - /// - /// Extracts all from rar. - /// - /// The source file. - /// The target path. - /// if set to true [overwrite existing files]. - public void ExtractAllFromRar(string sourceFile, string targetPath, bool overwriteExistingFiles) - { - using (var fileStream = _fileSystem.OpenRead(sourceFile)) - { - ExtractAllFromRar(fileStream, targetPath, overwriteExistingFiles); - } - } - - /// - /// Extracts all from rar. - /// - /// The source. - /// The target path. - /// if set to true [overwrite existing files]. - public void ExtractAllFromRar(Stream source, string targetPath, bool overwriteExistingFiles) - { - using (var archive = RarArchive.Open(source)) - { - using (var reader = archive.ExtractAllEntries()) - { - var options = new ExtractionOptions(); - options.ExtractFullPath = true; - - if (overwriteExistingFiles) - { - options.Overwrite = true; - } - - reader.WriteAllToDirectory(targetPath, options); - } - } - } } } diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 71497f6bf8..007f60a9bb 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -13,44 +13,22 @@ namespace Emby.Server.Implementations.Browser /// /// The page. /// The app host. - public static void OpenDashboardPage(string page, IServerApplicationHost appHost) + private static void OpenDashboardPage(string page, IServerApplicationHost appHost) { var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page; OpenUrl(appHost, url); } - /// - /// Opens the community. - /// - public static void OpenCommunity(IServerApplicationHost appHost) - { - OpenUrl(appHost, "http://emby.media/community"); - } - - public static void OpenEmbyPremiere(IServerApplicationHost appHost) - { - OpenDashboardPage("supporterkey.html", appHost); - } - /// /// Opens the web client. /// /// The app host. - public static void OpenWebClient(IServerApplicationHost appHost) + public static void OpenWebApp(IServerApplicationHost appHost) { OpenDashboardPage("index.html", appHost); } - /// - /// Opens the dashboard. - /// - /// The app host. - public static void OpenDashboard(IServerApplicationHost appHost) - { - OpenDashboardPage("dashboard.html", appHost); - } - /// /// Opens the URL. /// diff --git a/Emby.Server.Implementations/Channels/ChannelConfigurations.cs b/Emby.Server.Implementations/Channels/ChannelConfigurations.cs deleted file mode 100644 index ef0973e7f4..0000000000 --- a/Emby.Server.Implementations/Channels/ChannelConfigurations.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Channels -{ - public static class ChannelConfigurationExtension - { - public static ChannelOptions GetChannelsConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration("channels"); - } - } - - public class ChannelConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - Key = "channels", - ConfigurationType = typeof (ChannelOptions) - } - }; - } - } -} diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 7be4101c80..8448d36406 100644 --- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -18,24 +18,17 @@ namespace Emby.Server.Implementations.Channels _channelManager = (ChannelManager)channelManager; } - public Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) + public Task> GetMediaSources(BaseItem item, CancellationToken cancellationToken) { - var baseItem = (BaseItem) item; - - if (baseItem.SourceType == SourceType.Channel) + if (item.SourceType == SourceType.Channel) { - return _channelManager.GetDynamicMediaSources(baseItem, cancellationToken); + return _channelManager.GetDynamicMediaSources(item, cancellationToken); } return Task.FromResult>(new List()); } - public Task> OpenMediaSource(string openToken, bool allowLiveStreamProbe, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task CloseMediaSource(string liveStreamId) + public Task OpenMediaSource(string openToken, List currentLiveStreams, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs index 0c363c585f..a6643e83ca 100644 --- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs @@ -18,12 +18,12 @@ namespace Emby.Server.Implementations.Channels _channelManager = channelManager; } - public IEnumerable GetSupportedImages(IHasMetadata item) + public IEnumerable GetSupportedImages(BaseItem item) { return GetChannel(item).GetSupportedChannelImages(); } - public Task GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken) + public Task GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) { var channel = GetChannel(item); @@ -35,19 +35,19 @@ namespace Emby.Server.Implementations.Channels get { return "Channel Image Provider"; } } - public bool Supports(IHasMetadata item) + public bool Supports(BaseItem item) { return item is Channel; } - private IChannel GetChannel(IHasMetadata item) + private IChannel GetChannel(BaseItem item) { var channel = (Channel)item; return ((ChannelManager)_channelManager).GetChannelProvider(channel); } - public bool HasChanged(IHasMetadata item, IDirectoryService directoryService) + public bool HasChanged(BaseItem item, IDirectoryService directoryService) { return GetSupportedImages(item).Any(i => !item.HasImage(i)); } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index c566ca25bd..e832c7c6f8 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Channels private readonly IProviderManager _providerManager; private readonly ILocalizationManager _localization; - private readonly ConcurrentDictionary _refreshedItems = new ConcurrentDictionary(); public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization, IHttpClient httpClient, IProviderManager providerManager) { @@ -72,7 +71,7 @@ namespace Emby.Server.Implementations.Channels { get { - return TimeSpan.FromHours(6); + return TimeSpan.FromHours(3); } } @@ -81,6 +80,51 @@ namespace Emby.Server.Implementations.Channels Channels = channels.ToArray(); } + public bool EnableMediaSourceDisplay(BaseItem item) + { + var internalChannel = _libraryManager.GetItemById(item.ChannelId); + var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); + + return !(channel is IDisableMediaSourceDisplay); + } + + public bool CanDelete(BaseItem item) + { + var internalChannel = _libraryManager.GetItemById(item.ChannelId); + var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); + + var supportsDelete = channel as ISupportsDelete; + return supportsDelete != null && supportsDelete.CanDelete(item); + } + + public bool EnableMediaProbe(BaseItem item) + { + var internalChannel = _libraryManager.GetItemById(item.ChannelId); + var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); + + return channel is ISupportsMediaProbe; + } + + public Task DeleteItem(BaseItem item) + { + var internalChannel = _libraryManager.GetItemById(item.ChannelId); + if (internalChannel == null) + { + throw new ArgumentException(); + } + + var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); + + var supportsDelete = channel as ISupportsDelete; + + if (supportsDelete == null) + { + throw new ArgumentException(); + } + + return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None); + } + private IEnumerable GetAllChannels() { return Channels @@ -92,9 +136,9 @@ namespace Emby.Server.Implementations.Channels return GetAllChannels().Select(i => GetInternalChannelId(i.Name)); } - public Task> GetChannelsInternal(ChannelQuery query, CancellationToken cancellationToken) + public QueryResult GetChannelsInternal(ChannelQuery query) { - var user = string.IsNullOrWhiteSpace(query.UserId) + var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); @@ -103,6 +147,25 @@ namespace Emby.Server.Implementations.Channels .OrderBy(i => i.SortName) .ToList(); + if (query.IsRecordingsFolder.HasValue) + { + var val = query.IsRecordingsFolder.Value; + channels = channels.Where(i => + { + try + { + var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes; + + return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val; + } + catch + { + return false; + } + + }).ToList(); + } + if (query.SupportsLatestItems.HasValue) { var val = query.SupportsLatestItems.Value; @@ -119,6 +182,23 @@ namespace Emby.Server.Implementations.Channels }).ToList(); } + + if (query.SupportsMediaDeletion.HasValue) + { + var val = query.SupportsMediaDeletion.Value; + channels = channels.Where(i => + { + try + { + return GetChannelProvider(i) is ISupportsDelete == val; + } + catch + { + return false; + } + + }).ToList(); + } if (query.IsFavorite.HasValue) { var val = query.IsFavorite.Value; @@ -161,22 +241,29 @@ namespace Emby.Server.Implementations.Channels var returnItems = all.ToArray(all.Count); - var result = new QueryResult + if (query.RefreshLatestChannelItems) + { + foreach (var item in returnItems) + { + var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None); + Task.WaitAll(task); + } + } + + return new QueryResult { Items = returnItems, TotalRecordCount = totalCount }; - - return Task.FromResult(result); } - public async Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken) + public QueryResult GetChannels(ChannelQuery query) { - var user = string.IsNullOrWhiteSpace(query.UserId) + var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); - var internalResult = await GetChannelsInternal(query, cancellationToken).ConfigureAwait(false); + var internalResult = GetChannelsInternal(query); var dtoOptions = new DtoOptions() { @@ -195,8 +282,6 @@ namespace Emby.Server.Implementations.Channels public async Task RefreshChannels(IProgress progress, CancellationToken cancellationToken) { - _refreshedItems.Clear(); - var allChannelsList = GetAllChannels().ToList(); var numComplete = 0; @@ -230,7 +315,7 @@ namespace Emby.Server.Implementations.Channels private Channel GetChannelEntity(IChannel channel) { - var item = GetChannel(GetInternalChannelId(channel.Name).ToString("N")); + var item = GetChannel(GetInternalChannelId(channel.Name)); if (item == null) { @@ -240,23 +325,23 @@ namespace Emby.Server.Implementations.Channels return item; } - private List GetSavedMediaSources(BaseItem item) + private List GetSavedMediaSources(BaseItem item) { - var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasources.json"); + var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json"); try { - return _jsonSerializer.DeserializeFromFile>(path) ?? new List(); + return _jsonSerializer.DeserializeFromFile>(path) ?? new List(); } catch { - return new List(); + return new List(); } } - private void SaveMediaSources(BaseItem item, List mediaSources) + private void SaveMediaSources(BaseItem item, List mediaSources) { - var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasources.json"); + var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json"); if (mediaSources == null || mediaSources.Count == 0) { @@ -278,10 +363,10 @@ namespace Emby.Server.Implementations.Channels public IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken) { - IEnumerable results = GetSavedMediaSources(item); + IEnumerable results = GetSavedMediaSources(item); - return SortMediaInfoResults(results) - .Select(i => GetMediaSource(item, i)) + return results + .Select(i => NormalizeMediaSource(item, i)) .ToList(); } @@ -292,7 +377,7 @@ namespace Emby.Server.Implementations.Channels var requiresCallback = channelPlugin as IRequiresMediaInfoCallback; - IEnumerable results; + IEnumerable results; if (requiresCallback != null) { @@ -301,20 +386,20 @@ namespace Emby.Server.Implementations.Channels } else { - results = new List(); + results = new List(); } - return SortMediaInfoResults(results) - .Select(i => GetMediaSource(item, i)) + return results + .Select(i => NormalizeMediaSource(item, i)) .ToList(); } - private readonly ConcurrentDictionary>> _channelItemMediaInfo = - new ConcurrentDictionary>>(); + private readonly ConcurrentDictionary>> _channelItemMediaInfo = + new ConcurrentDictionary>>(); - private async Task> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) + private async Task> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) { - Tuple> cachedInfo; + Tuple> cachedInfo; if (_channelItemMediaInfo.TryGetValue(id, out cachedInfo)) { @@ -328,56 +413,24 @@ namespace Emby.Server.Implementations.Channels .ConfigureAwait(false); var list = mediaInfo.ToList(); - var item2 = new Tuple>(DateTime.UtcNow, list); + var item2 = new Tuple>(DateTime.UtcNow, list); _channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2); return list; } - private MediaSourceInfo GetMediaSource(BaseItem item, ChannelMediaInfo info) + private MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info) { - var source = info.ToMediaSource(item.Id); + info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks; - source.RunTimeTicks = source.RunTimeTicks ?? item.RunTimeTicks; - - return source; - } - - private IEnumerable SortMediaInfoResults(IEnumerable channelMediaSources) - { - var list = channelMediaSources.ToList(); - - var options = _config.GetChannelsConfiguration(); - - var width = options.PreferredStreamingWidth; - - if (width.HasValue) - { - var val = width.Value; - - var res = list - .OrderBy(i => i.Width.HasValue && i.Width.Value <= val ? 0 : 1) - .ThenBy(i => Math.Abs((i.Width ?? 0) - val)) - .ThenByDescending(i => i.Width ?? 0) - .ThenBy(list.IndexOf) - .ToList(); - - - return res; - } - - return list - .OrderByDescending(i => i.Width ?? 0) - .ThenBy(list.IndexOf); + return info; } private async Task GetChannel(IChannel channelInfo, CancellationToken cancellationToken) { - var parentFolder = GetInternalChannelFolder(cancellationToken); - var parentFolderId = parentFolder.Id; + var parentFolderId = Guid.Empty; var id = GetInternalChannelId(channelInfo.Name); - var idString = id.ToString("N"); var path = Channel.GetInternalMetadataPath(_config.ApplicationPaths.InternalMetadataPath, id); @@ -405,11 +458,11 @@ namespace Emby.Server.Implementations.Channels } item.Path = path; - if (!string.Equals(item.ChannelId, idString, StringComparison.OrdinalIgnoreCase)) + if (!item.ChannelId.Equals(id)) { forceUpdate = true; } - item.ChannelId = idString; + item.ChannelId = id; if (item.ParentId != parentFolderId) { @@ -419,25 +472,24 @@ namespace Emby.Server.Implementations.Channels item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating); item.Overview = channelInfo.Description; - item.HomePageUrl = channelInfo.HomePageUrl; if (string.IsNullOrWhiteSpace(item.Name)) { item.Name = channelInfo.Name; } - item.OnMetadataChanged(); - if (isNew) { - _libraryManager.CreateItem(item, cancellationToken); - } - else if (forceUpdate) - { - item.UpdateToRepository(ItemUpdateType.None, cancellationToken); + item.OnMetadataChanged(); + _libraryManager.CreateItem(item, null); } - await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken); + await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem) + { + ForceSave = !isNew && forceUpdate + + }, cancellationToken); + return item; } @@ -458,6 +510,11 @@ namespace Emby.Server.Implementations.Channels } } + public Channel GetChannel(Guid id) + { + return _libraryManager.GetItemById(id) as Channel; + } + public Channel GetChannel(string id) { return _libraryManager.GetItemById(id) as Channel; @@ -468,14 +525,14 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, - OrderBy = new Tuple[] { new Tuple(ItemSortBy.SortName, SortOrder.Ascending) } + OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray(); } public ChannelFeatures GetChannelFeatures(string id) { - if (string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } @@ -486,13 +543,8 @@ namespace Emby.Server.Implementations.Channels return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures()); } - public bool SupportsSync(string channelId) + public bool SupportsExternalTransfer(Guid channelId) { - if (string.IsNullOrWhiteSpace(channelId)) - { - throw new ArgumentNullException("channelId"); - } - //var channel = GetChannel(channelId); var channelProvider = GetChannelProvider(channelId); @@ -503,7 +555,6 @@ namespace Emby.Server.Implementations.Channels IChannel provider, InternalChannelFeatures features) { - var isIndexable = provider is IIndexableChannel; var supportsLatest = provider is ISupportsLatestMedia; return new ChannelFeatures @@ -518,57 +569,28 @@ namespace Emby.Server.Implementations.Channels SupportsLatestMedia = supportsLatest, Name = channel.Name, Id = channel.Id.ToString("N"), - SupportsContentDownloading = features.SupportsContentDownloading && (isIndexable || supportsLatest), + SupportsContentDownloading = features.SupportsContentDownloading, AutoRefreshLevels = features.AutoRefreshLevels }; } private Guid GetInternalChannelId(string name) { - if (string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel)); } - public async Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken) + public async Task> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken) { - var user = string.IsNullOrWhiteSpace(query.UserId) - ? null - : _userManager.GetUserById(query.UserId); - - var limit = query.Limit; - - // See below about parental control - if (user != null) - { - query.StartIndex = null; - query.Limit = null; - } - var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false); var items = internalResult.Items; var totalRecordCount = internalResult.TotalRecordCount; - // Supporting parental control is a hack because it has to be done after querying the remote data source - // This will get screwy if apps try to page, so limit to 10 results in an attempt to always keep them on the first page - if (user != null) - { - items = items.Where(i => i.IsVisible(user)) - .Take(limit ?? 10) - .ToArray(); - - totalRecordCount = items.Length; - } - - var dtoOptions = new DtoOptions() - { - Fields = query.Fields - }; - - var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); + var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User); var result = new QueryResult { @@ -579,407 +601,150 @@ namespace Emby.Server.Implementations.Channels return result; } - public async Task> GetLatestChannelItemsInternal(AllChannelMediaQuery query, CancellationToken cancellationToken) + public async Task> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken) { - var user = string.IsNullOrWhiteSpace(query.UserId) - ? null - : _userManager.GetUserById(query.UserId); - - if (!string.IsNullOrWhiteSpace(query.UserId) && user == null) - { - throw new ArgumentException("User not found."); - } - - var channels = GetAllChannels(); + var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray(); if (query.ChannelIds.Length > 0) { // Avoid implicitly captured closure var ids = query.ChannelIds; channels = channels - .Where(i => ids.Contains(GetInternalChannelId(i.Name).ToString("N"))) + .Where(i => ids.Contains(GetInternalChannelId(i.Name))) .ToArray(); } - // Avoid implicitly captured closure - var userId = query.UserId; + if (channels.Length == 0) + { + return new QueryResult(); + } - var tasks = channels - .Select(async i => + foreach (var channel in channels) + { + await RefreshLatestChannelItems(channel, cancellationToken).ConfigureAwait(false); + } + + query.IsFolder = false; + + // hack for trailers, figure out a better way later + var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1; + + if (sortByPremiereDate) + { + query.OrderBy = new [] { - var indexable = i as ISupportsLatestMedia; - - if (indexable != null) - { - try - { - var result = await GetLatestItems(indexable, i, userId, cancellationToken).ConfigureAwait(false); - - var resultItems = result.ToList(); - - return new Tuple(i, new ChannelItemResult - { - Items = resultItems, - TotalRecordCount = resultItems.Count - }); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting all media from {0}", ex, i.Name); - } - } - return new Tuple(i, new ChannelItemResult()); - }); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - var totalCount = results.Length; - - IEnumerable> items = results - .SelectMany(i => i.Item2.Items.Select(m => new Tuple(i.Item1, m))); - - if (query.ContentTypes.Length > 0) - { - // Avoid implicitly captured closure - var contentTypes = query.ContentTypes; - - items = items.Where(i => contentTypes.Contains(i.Item2.ContentType)); + new ValueTuple(ItemSortBy.PremiereDate, SortOrder.Descending), + new ValueTuple(ItemSortBy.ProductionYear, SortOrder.Descending), + new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) + }; } - if (query.ExtraTypes.Length > 0) + else { - // Avoid implicitly captured closure - var contentTypes = query.ExtraTypes; - - items = items.Where(i => contentTypes.Contains(i.Item2.ExtraType)); + query.OrderBy = new [] + { + new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) + }; } - // Avoid implicitly captured closure - var token = cancellationToken; - var internalItems = items.Select(i => - { - var channelProvider = i.Item1; - var internalChannelId = GetInternalChannelId(channelProvider.Name); - return GetChannelItemEntity(i.Item2, channelProvider, internalChannelId, token); - }).ToArray(); - - internalItems = ApplyFilters(internalItems, query.Filters, user).ToArray(); - RefreshIfNeeded(internalItems); - - if (query.StartIndex.HasValue) - { - internalItems = internalItems.Skip(query.StartIndex.Value).ToArray(); - } - if (query.Limit.HasValue) - { - internalItems = internalItems.Take(query.Limit.Value).ToArray(); - } - - return new QueryResult - { - TotalRecordCount = totalCount, - Items = internalItems - }; + return _libraryManager.GetItemsResult(query); } - private async Task> GetLatestItems(ISupportsLatestMedia indexable, IChannel channel, string userId, CancellationToken cancellationToken) + private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken) { - var cacheLength = CacheLength; - var cachePath = GetChannelDataCachePath(channel, userId, "channelmanager-latest", null, false); + var internalChannel = await GetChannel(channel, cancellationToken); - try + var query = new InternalItemsQuery(); + query.Parent = internalChannel; + query.EnableTotalRecordCount = false; + query.ChannelIds = new Guid[] { internalChannel.Id }; + + var result = await GetChannelItemsInternal(query, new SimpleProgress(), cancellationToken).ConfigureAwait(false); + + foreach (var item in result.Items) { - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + var folder = item as Folder; + + if (folder != null) { - return _jsonSerializer.DeserializeFromFile>(cachePath); - } - } - catch (FileNotFoundException) - { - - } - catch (IOException) - { - - } - - await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - try - { - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + await GetChannelItemsInternal(new InternalItemsQuery { - return _jsonSerializer.DeserializeFromFile>(cachePath); - } + Parent = folder, + EnableTotalRecordCount = false, + ChannelIds = new Guid[] { internalChannel.Id } + + }, new SimpleProgress(), cancellationToken).ConfigureAwait(false); } - catch (FileNotFoundException) - { - - } - catch (IOException) - { - - } - - var result = await indexable.GetLatestMedia(new ChannelLatestMediaSearch - { - UserId = userId - - }, cancellationToken).ConfigureAwait(false); - - var resultItems = result.ToList(); - - CacheResponse(resultItems, cachePath); - - return resultItems; - } - finally - { - _resourcePool.Release(); } } - public async Task> GetAllMediaInternal(AllChannelMediaQuery query, CancellationToken cancellationToken) - { - var channels = GetAllChannels(); - - if (query.ChannelIds.Length > 0) - { - // Avoid implicitly captured closure - var ids = query.ChannelIds; - channels = channels - .Where(i => ids.Contains(GetInternalChannelId(i.Name).ToString("N"))) - .ToArray(); - } - - var tasks = channels - .Select(async i => - { - var indexable = i as IIndexableChannel; - - if (indexable != null) - { - try - { - var result = await GetAllItems(indexable, i, new InternalAllChannelMediaQuery - { - UserId = query.UserId, - ContentTypes = query.ContentTypes, - ExtraTypes = query.ExtraTypes, - TrailerTypes = query.TrailerTypes - - }, cancellationToken).ConfigureAwait(false); - - return new Tuple(i, result); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting all media from {0}", ex, i.Name); - } - } - return new Tuple(i, new ChannelItemResult()); - }); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - var totalCount = results.Length; - - IEnumerable> items = results - .SelectMany(i => i.Item2.Items.Select(m => new Tuple(i.Item1, m))) - .OrderBy(i => i.Item2.Name); - - if (query.StartIndex.HasValue) - { - items = items.Skip(query.StartIndex.Value); - } - if (query.Limit.HasValue) - { - items = items.Take(query.Limit.Value); - } - - // Avoid implicitly captured closure - var token = cancellationToken; - var internalItems = items.Select(i => - { - var channelProvider = i.Item1; - var internalChannelId = GetInternalChannelId(channelProvider.Name); - return GetChannelItemEntity(i.Item2, channelProvider, internalChannelId, token); - }).ToArray(); - - return new QueryResult - { - TotalRecordCount = totalCount, - Items = internalItems - }; - } - - public async Task> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken) - { - var user = string.IsNullOrWhiteSpace(query.UserId) - ? null - : _userManager.GetUserById(query.UserId); - - var internalResult = await GetAllMediaInternal(query, cancellationToken).ConfigureAwait(false); - - RefreshIfNeeded(internalResult.Items); - - var dtoOptions = new DtoOptions() - { - Fields = query.Fields - }; - - var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); - - var result = new QueryResult - { - Items = returnItems, - TotalRecordCount = internalResult.TotalRecordCount - }; - - return result; - } - - private async Task GetAllItems(IIndexableChannel indexable, IChannel channel, InternalAllChannelMediaQuery query, CancellationToken cancellationToken) - { - var cacheLength = CacheLength; - var folderId = _jsonSerializer.SerializeToString(query).GetMD5().ToString("N"); - var cachePath = GetChannelDataCachePath(channel, query.UserId, folderId, null, false); - - try - { - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) - { - return _jsonSerializer.DeserializeFromFile(cachePath); - } - } - catch (FileNotFoundException) - { - - } - catch (IOException) - { - - } - - await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - try - { - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) - { - return _jsonSerializer.DeserializeFromFile(cachePath); - } - } - catch (FileNotFoundException) - { - - } - catch (IOException) - { - - } - - var result = await indexable.GetAllMedia(query, cancellationToken).ConfigureAwait(false); - - CacheResponse(result, cachePath); - - return result; - } - finally - { - _resourcePool.Release(); - } - } - - public async Task> GetChannelItemsInternal(ChannelItemQuery query, IProgress progress, CancellationToken cancellationToken) + public async Task> GetChannelItemsInternal(InternalItemsQuery query, IProgress progress, CancellationToken cancellationToken) { // Get the internal channel entity - var channel = GetChannel(query.ChannelId); + var channel = GetChannel(query.ChannelIds[0]); // Find the corresponding channel provider plugin var channelProvider = GetChannelProvider(channel); - var channelInfo = channelProvider.GetChannelFeatures(); - - int? providerStartIndex = null; - int? providerLimit = null; - - if (channelInfo.MaxPageSize.HasValue) - { - providerStartIndex = query.StartIndex; - - if (query.Limit.HasValue && query.Limit.Value > channelInfo.MaxPageSize.Value) - { - query.Limit = Math.Min(query.Limit.Value, channelInfo.MaxPageSize.Value); - } - providerLimit = query.Limit; - - // This will cause some providers to fail - if (providerLimit == 0) - { - providerLimit = 1; - } - } - - var user = string.IsNullOrWhiteSpace(query.UserId) - ? null - : _userManager.GetUserById(query.UserId); + var user = query.User; ChannelItemSortField? sortField = null; - ChannelItemSortField parsedField; var sortDescending = false; - if (query.OrderBy.Length == 1 && - Enum.TryParse(query.OrderBy[0].Item1, true, out parsedField)) - { - sortField = parsedField; - sortDescending = query.OrderBy[0].Item2 == SortOrder.Descending; - } + var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel; var itemsResult = await GetChannelItems(channelProvider, user, - query.FolderId, - providerStartIndex, - providerLimit, + parentItem is Channel ? null : parentItem.ExternalId, sortField, sortDescending, cancellationToken) .ConfigureAwait(false); - var providerTotalRecordCount = providerLimit.HasValue ? itemsResult.TotalRecordCount : null; - - var internalItems = itemsResult.Items.Select(i => GetChannelItemEntity(i, channelProvider, channel.Id, cancellationToken)).ToArray(); - - if (user != null) + if (query.ParentId.Equals(Guid.Empty)) { - internalItems = internalItems.Where(i => i.IsVisible(user)).ToArray(); + query.Parent = channel; + } + query.ChannelIds = Array.Empty(); - if (providerTotalRecordCount.HasValue) + // Not yet sure why this is causing a problem + query.GroupByPresentationUniqueKey = false; + + //_logger.Debug("GetChannelItemsInternal"); + + // null if came from cache + if (itemsResult != null) + { + var internalItems = itemsResult.Items + .Select(i => GetChannelItemEntity(i, channelProvider, channel.Id, parentItem, cancellationToken)) + .ToArray(); + + var existingIds = _libraryManager.GetItemIds(query); + var deadIds = existingIds.Except(internalItems.Select(i => i.Id)) + .ToArray(); + + foreach (var deadId in deadIds) { - providerTotalRecordCount = providerTotalRecordCount.Value; + var deadItem = _libraryManager.GetItemById(deadId); + if (deadItem != null) + { + _libraryManager.DeleteItem(deadItem, new DeleteOptions + { + DeleteFileLocation = false, + DeleteFromExternalProvider = false + + }, parentItem, false); + } } } - return GetReturnItems(internalItems, providerTotalRecordCount, user, query); + return _libraryManager.GetItemsResult(query); } - public async Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken) + public async Task> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken) { - var user = string.IsNullOrWhiteSpace(query.UserId) - ? null - : _userManager.GetUserById(query.UserId); - var internalResult = await GetChannelItemsInternal(query, new SimpleProgress(), cancellationToken).ConfigureAwait(false); - var dtoOptions = new DtoOptions() - { - Fields = query.Fields - }; - - var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User); var result = new QueryResult { @@ -993,29 +758,24 @@ namespace Emby.Server.Implementations.Channels private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private async Task GetChannelItems(IChannel channel, User user, - string folderId, - int? startIndex, - int? limit, + string externalFolderId, ChannelItemSortField? sortField, bool sortDescending, CancellationToken cancellationToken) { - var userId = user.Id.ToString("N"); + var userId = user == null ? null : user.Id.ToString("N"); var cacheLength = CacheLength; - var cachePath = GetChannelDataCachePath(channel, userId, folderId, sortField, sortDescending); + var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending); try { - if (!startIndex.HasValue && !limit.HasValue) + if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + var cachedResult = _jsonSerializer.DeserializeFromFile(cachePath); + if (cachedResult != null) { - var cachedResult = _jsonSerializer.DeserializeFromFile(cachePath); - if (cachedResult != null) - { - return cachedResult; - } + return null; } } } @@ -1034,15 +794,12 @@ namespace Emby.Server.Implementations.Channels { try { - if (!startIndex.HasValue && !limit.HasValue) + if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) { - if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow) + var cachedResult = _jsonSerializer.DeserializeFromFile(cachePath); + if (cachedResult != null) { - var cachedResult = _jsonSerializer.DeserializeFromFile(cachePath); - if (cachedResult != null) - { - return cachedResult; - } + return null; } } } @@ -1057,19 +814,13 @@ namespace Emby.Server.Implementations.Channels var query = new InternalChannelItemQuery { - UserId = userId, - StartIndex = startIndex, - Limit = limit, + UserId = user == null ? Guid.Empty : user.Id, SortBy = sortField, - SortDescending = sortDescending + SortDescending = sortDescending, + FolderId = externalFolderId }; - if (!string.IsNullOrWhiteSpace(folderId)) - { - var categoryItem = _libraryManager.GetItemById(new Guid(folderId)); - - query.FolderId = categoryItem.ExternalId; - } + query.FolderId = externalFolderId; var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false); @@ -1078,10 +829,7 @@ namespace Emby.Server.Implementations.Channels throw new InvalidOperationException("Channel returned a null result from GetChannelItems"); } - if (!startIndex.HasValue && !limit.HasValue) - { - CacheResponse(result, cachePath); - } + CacheResponse(result, cachePath); return result; } @@ -1107,7 +855,7 @@ namespace Emby.Server.Implementations.Channels private string GetChannelDataCachePath(IChannel channel, string userId, - string folderId, + string externalFolderId, ChannelItemSortField? sortField, bool sortDescending) { @@ -1121,10 +869,10 @@ namespace Emby.Server.Implementations.Channels userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty; } - var filename = string.IsNullOrWhiteSpace(folderId) ? "root" : folderId; + var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N"); filename += userCacheKey; - var version = (channel.DataVersion ?? string.Empty).GetMD5().ToString("N"); + var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N"); if (sortField.HasValue) { @@ -1144,40 +892,6 @@ namespace Emby.Server.Implementations.Channels filename + ".json"); } - private QueryResult GetReturnItems(IEnumerable items, - int? totalCountFromProvider, - User user, - ChannelItemQuery query) - { - items = ApplyFilters(items, query.Filters, user); - - items = _libraryManager.Sort(items, user, query.OrderBy); - - var all = items.ToList(); - var totalCount = totalCountFromProvider ?? all.Count; - - if (!totalCountFromProvider.HasValue) - { - if (query.StartIndex.HasValue) - { - all = all.Skip(query.StartIndex.Value).ToList(); - } - if (query.Limit.HasValue) - { - all = all.Take(query.Limit.Value).ToList(); - } - } - - var returnItemArray = all.ToArray(all.Count); - RefreshIfNeeded(returnItemArray); - - return new QueryResult - { - Items = returnItemArray, - TotalRecordCount = totalCount - }; - } - private string GetIdToHash(string externalId, string channelName) { // Increment this as needed to force new downloads @@ -1185,7 +899,7 @@ namespace Emby.Server.Implementations.Channels return externalId + (channelName ?? string.Empty) + "16"; } - private T GetItemById(string idString, string channelName, string channnelDataVersion, out bool isNew) + private T GetItemById(string idString, string channelName, out bool isNew) where T : BaseItem, new() { var id = GetIdToHash(idString, channelName).GetMBId(typeof(T)); @@ -1201,7 +915,7 @@ namespace Emby.Server.Implementations.Channels _logger.ErrorException("Error retrieving channel item from database", ex); } - if (item == null || !string.Equals(item.ExternalEtag, channnelDataVersion, StringComparison.Ordinal)) + if (item == null) { item = new T(); isNew = true; @@ -1211,13 +925,14 @@ namespace Emby.Server.Implementations.Channels isNew = false; } - item.ExternalEtag = channnelDataVersion; item.Id = id; return item; } - private BaseItem GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, CancellationToken cancellationToken) + private BaseItem GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken) { + var parentFolderId = parentFolder.Id; + BaseItem item; bool isNew; bool forceUpdate = false; @@ -1226,66 +941,76 @@ namespace Emby.Server.Implementations.Channels { if (info.FolderType == ChannelFolderType.MusicAlbum) { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } else if (info.FolderType == ChannelFolderType.MusicArtist) { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } else if (info.FolderType == ChannelFolderType.PhotoAlbum) { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } else if (info.FolderType == ChannelFolderType.Series) { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } else if (info.FolderType == ChannelFolderType.Season) { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } else { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } } else if (info.MediaType == ChannelMediaType.Audio) { if (info.ContentType == ChannelMediaContentType.Podcast) { - item = GetItemById(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew); + item = GetItemById(info.Id, channelProvider.Name, out isNew); } else { - item = GetItemById public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository { - private readonly IMemoryStreamFactory _memoryStreamProvider; protected IFileSystem FileSystem { get; private set; } - public SqliteDisplayPreferencesRepository(ILogger logger, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider, IFileSystem fileSystem) + public SqliteDisplayPreferencesRepository(ILogger logger, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IFileSystem fileSystem) : base(logger) { _jsonSerializer = jsonSerializer; - _memoryStreamProvider = memoryStreamProvider; FileSystem = fileSystem; DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); } @@ -98,7 +96,7 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException("displayPreferences"); } - if (string.IsNullOrWhiteSpace(displayPreferences.Id)) + if (string.IsNullOrEmpty(displayPreferences.Id)) { throw new ArgumentNullException("displayPreferences.Id"); } @@ -119,7 +117,7 @@ namespace Emby.Server.Implementations.Data private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) { - var serialized = _jsonSerializer.SerializeToBytes(displayPreferences, _memoryStreamProvider); + var serialized = _jsonSerializer.SerializeToBytes(displayPreferences); using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) { @@ -174,7 +172,7 @@ namespace Emby.Server.Implementations.Data /// item public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) { - if (string.IsNullOrWhiteSpace(displayPreferencesId)) + if (string.IsNullOrEmpty(displayPreferencesId)) { throw new ArgumentNullException("displayPreferencesId"); } @@ -236,7 +234,7 @@ namespace Emby.Server.Implementations.Data private DisplayPreferences Get(IReadOnlyList row) { - using (var stream = _memoryStreamProvider.CreateNew(row[0].ToBlob())) + using (var stream = new MemoryStream(row[0].ToBlob())) { stream.Position = 0; return _jsonSerializer.DeserializeFromStream(stream); diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index d2c851b3c6..a755c65f4a 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using SQLitePCL.pretty; +using System.IO; namespace Emby.Server.Implementations.Data { @@ -110,19 +111,33 @@ namespace Emby.Server.Implementations.Data DateTimeStyles.None).ToUniversalTime(); } + public static DateTime? TryReadDateTime(this IResultSetValue result) + { + var dateText = result.ToString(); + + DateTime dateTimeResult; + + if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out dateTimeResult)) + { + return dateTimeResult.ToUniversalTime(); + } + + return null; + } + /// /// Serializes to bytes. /// /// System.Byte[][]. /// obj - public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamFactory streamProvider) + public static byte[] SerializeToBytes(this IJsonSerializer json, object obj) { if (obj == null) { throw new ArgumentNullException("obj"); } - using (var stream = streamProvider.CreateNew()) + using (var stream = new MemoryStream()) { json.SerializeToStream(obj, stream); return stream.ToArray(); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 830d6447ec..bde28c923d 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -32,6 +32,9 @@ using SQLitePCL.pretty; using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Library; namespace Emby.Server.Implementations.Data { @@ -65,18 +68,16 @@ namespace Emby.Server.Implementations.Data /// private readonly IServerConfigurationManager _config; - private readonly string _criticReviewsPath; - - private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly IFileSystem _fileSystem; private readonly IEnvironmentInfo _environmentInfo; - private readonly ITimerFactory _timerFactory; - private ITimer _shrinkMemoryTimer; + private IServerApplicationHost _appHost; + + public IImageProcessor ImageProcessor { get; set; } /// /// Initializes a new instance of the class. /// - public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogger logger, IMemoryStreamFactory memoryStreamProvider, IAssemblyInfo assemblyInfo, IFileSystem fileSystem, IEnvironmentInfo environmentInfo, ITimerFactory timerFactory) + public SqliteItemRepository(IServerConfigurationManager config, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, ILogger logger, IAssemblyInfo assemblyInfo, IFileSystem fileSystem, IEnvironmentInfo environmentInfo, ITimerFactory timerFactory) : base(logger) { if (config == null) @@ -88,15 +89,13 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException("jsonSerializer"); } + _appHost = appHost; _config = config; _jsonSerializer = jsonSerializer; - _memoryStreamProvider = memoryStreamProvider; _fileSystem = fileSystem; _environmentInfo = environmentInfo; - _timerFactory = timerFactory; _typeMapper = new TypeMapper(assemblyInfo); - _criticReviewsPath = Path.Combine(_config.ApplicationPaths.DataPath, "critic-reviews"); DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); } @@ -118,28 +117,17 @@ namespace Emby.Server.Implementations.Data } } - protected override void CloseConnection() - { - if (_shrinkMemoryTimer != null) - { - _shrinkMemoryTimer.Dispose(); - _shrinkMemoryTimer = null; - } - - base.CloseConnection(); - } - /// /// Opens the connection to the database /// - public void Initialize(SqliteUserDataRepository userDataRepo) + public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { using (var connection = CreateConnection()) { RunDefaultInitialization(connection); var createMediaStreamsTableCommand - = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; + = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; string[] queries = { "PRAGMA locking_mode=EXCLUSIVE", @@ -162,8 +150,6 @@ namespace Emby.Server.Implementations.Data createMediaStreamsTableCommand, - "create index if not exists idx_mediastreams1 on mediastreams(ItemId)", - "pragma shrink_memory" }; @@ -182,8 +168,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "EndDate", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "ChannelId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsMovie", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsSports", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsKids", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "CommunityRating", "Float", existingColumnNames); AddColumn(db, "TypedBaseItems", "CustomRating", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "IndexNumber", "INT", existingColumnNames); @@ -200,19 +184,13 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "SortName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "HomePageUrl", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsLive", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsNews", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsPremiere", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "EpisodeTitle", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsRepeat", "BIT", existingColumnNames); AddColumn(db, "TypedBaseItems", "PreferredMetadataLanguage", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "PreferredMetadataCountryCode", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "IsHD", "BIT", existingColumnNames); - AddColumn(db, "TypedBaseItems", "ExternalEtag", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateLastRefreshed", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "DateLastSaved", "DATETIME", existingColumnNames); AddColumn(db, "TypedBaseItems", "IsInMixedFolder", "BIT", existingColumnNames); @@ -244,8 +222,7 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Images", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ProductionLocations", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "ThemeSongIds", "Text", existingColumnNames); - AddColumn(db, "TypedBaseItems", "ThemeVideoIds", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "ExtraIds", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "TotalBitrate", "INT", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExtraType", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Artists", "Text", existingColumnNames); @@ -254,6 +231,8 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "Width", "INT", existingColumnNames); + AddColumn(db, "TypedBaseItems", "Height", "INT", existingColumnNames); existingColumnNames = GetColumnNames(db, "ItemValues"); AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames); @@ -274,6 +253,11 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "MediaStreams", "RefFrames", "INT", existingColumnNames); AddColumn(db, "MediaStreams", "KeyFrames", "TEXT", existingColumnNames); AddColumn(db, "MediaStreams", "IsAnamorphic", "BIT", existingColumnNames); + + AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames); + AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames); + AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames); + }, TransactionMode); string[] postQueries = @@ -282,6 +266,7 @@ namespace Emby.Server.Implementations.Data // obsolete "drop index if exists idx_TypedBaseItems", "drop index if exists idx_mediastreams", + "drop index if exists idx_mediastreams1", "drop index if exists idx_"+ChaptersTableName, "drop index if exists idx_UserDataKeys1", "drop index if exists idx_UserDataKeys2", @@ -316,7 +301,6 @@ namespace Emby.Server.Implementations.Data "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", - //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", // covering index @@ -359,32 +343,7 @@ namespace Emby.Server.Implementations.Data //await Vacuum(_connection).ConfigureAwait(false); } - userDataRepo.Initialize(WriteLock, _connection); - - _shrinkMemoryTimer = _timerFactory.Create(OnShrinkMemoryTimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(30)); - } - - private void OnShrinkMemoryTimerCallback(object state) - { - try - { - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunQueries(new string[] - { - "pragma shrink_memory" - }); - } - } - - GC.Collect(); - } - catch (Exception ex) - { - Logger.ErrorException("Error running shrink memory", ex); - } + userDataRepo.Initialize(WriteLock, _connection, userManager); } private readonly string[] _retriveItemColumns = @@ -395,12 +354,7 @@ namespace Emby.Server.Implementations.Data "EndDate", "ChannelId", "IsMovie", - "IsSports", - "IsKids", "IsSeries", - "IsLive", - "IsNews", - "IsPremiere", "EpisodeTitle", "IsRepeat", "CommunityRating", @@ -409,8 +363,8 @@ namespace Emby.Server.Implementations.Data "IsLocked", "PreferredMetadataLanguage", "PreferredMetadataCountryCode", - "IsHD", - "ExternalEtag", + "Width", + "Height", "DateLastRefreshed", "Name", "Path", @@ -419,7 +373,6 @@ namespace Emby.Server.Implementations.Data "ParentIndexNumber", "ProductionYear", "OfficialRating", - "HomePageUrl", "ForcedSortName", "RunTimeTicks", "DateCreated", @@ -452,8 +405,7 @@ namespace Emby.Server.Implementations.Data "ProviderIds", "Images", "ProductionLocations", - "ThemeSongIds", - "ThemeVideoIds", + "ExtraIds", "TotalBitrate", "ExtraType", "Artists", @@ -497,7 +449,10 @@ namespace Emby.Server.Implementations.Data "IsAvc", "Title", "TimeBase", - "CodecTimeBase" + "CodecTimeBase", + "ColorPrimaries", + "ColorSpace", + "ColorTransfer" }; private string GetSaveItemCommandText() @@ -511,13 +466,8 @@ namespace Emby.Server.Implementations.Data "StartDate", "EndDate", "ChannelId", - "IsKids", "IsMovie", - "IsSports", "IsSeries", - "IsLive", - "IsNews", - "IsPremiere", "EpisodeTitle", "IsRepeat", "CommunityRating", @@ -537,13 +487,12 @@ namespace Emby.Server.Implementations.Data "SortName", "ForcedSortName", "RunTimeTicks", - "HomePageUrl", "DateCreated", "DateModified", "PreferredMetadataLanguage", "PreferredMetadataCountryCode", - "IsHD", - "ExternalEtag", + "Width", + "Height", "DateLastRefreshed", "DateLastSaved", "IsInMixedFolder", @@ -574,8 +523,7 @@ namespace Emby.Server.Implementations.Data "ProviderIds", "Images", "ProductionLocations", - "ThemeSongIds", - "ThemeVideoIds", + "ExtraIds", "TotalBitrate", "ExtraType", "Artists", @@ -699,46 +647,58 @@ namespace Emby.Server.Implementations.Data var statements = PrepareAllSafe(db, new string[] { GetSaveItemCommandText(), - "delete from AncestorIds where ItemId=@ItemId", - "insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values (@ItemId, @AncestorId, @AncestorIdText)" + "delete from AncestorIds where ItemId=@ItemId" + }).ToList(); using (var saveItemStatement = statements[0]) { using (var deleteAncestorsStatement = statements[1]) { - using (var updateAncestorsStatement = statements[2]) + foreach (var tuple in tuples) { - foreach (var tuple in tuples) + if (requiresReset) { - if (requiresReset) - { - saveItemStatement.Reset(); - } - - var item = tuple.Item1; - var topParent = tuple.Item3; - var userDataKey = tuple.Item4; - - SaveItem(item, topParent, userDataKey, saveItemStatement); - //Logger.Debug(_saveItemCommand.CommandText); - - var inheritedTags = tuple.Item5; - - if (item.SupportsAncestors) - { - UpdateAncestors(item.Id, tuple.Item2, db, deleteAncestorsStatement, updateAncestorsStatement); - } - - UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db); - - requiresReset = true; + saveItemStatement.Reset(); } + + var item = tuple.Item1; + var topParent = tuple.Item3; + var userDataKey = tuple.Item4; + + SaveItem(item, topParent, userDataKey, saveItemStatement); + //Logger.Debug(_saveItemCommand.CommandText); + + var inheritedTags = tuple.Item5; + + if (item.SupportsAncestors) + { + UpdateAncestors(item.Id, tuple.Item2, db, deleteAncestorsStatement); + } + + UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db); + + requiresReset = true; } } } } + private string GetPathToSave(string path) + { + if (path == null) + { + return null; + } + + return _appHost.ReverseVirtualPath(path); + } + + private string RestorePath(string path) + { + return _appHost.ExpandVirtualPath(path); + } + private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement) { saveItemStatement.TryBind("@guid", item.Id); @@ -746,14 +706,14 @@ namespace Emby.Server.Implementations.Data if (TypeRequiresDeserialization(item.GetType())) { - saveItemStatement.TryBind("@data", _jsonSerializer.SerializeToBytes(item, _memoryStreamProvider)); + saveItemStatement.TryBind("@data", _jsonSerializer.SerializeToBytes(item)); } else { saveItemStatement.TryBindNull("@data"); } - saveItemStatement.TryBind("@Path", item.Path); + saveItemStatement.TryBind("@Path", GetPathToSave(item.Path)); var hasStartDate = item as IHasStartDate; if (hasStartDate != null) @@ -774,30 +734,20 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N")); var hasProgramAttributes = item as IHasProgramAttributes; if (hasProgramAttributes != null) { - saveItemStatement.TryBind("@IsKids", hasProgramAttributes.IsKids); saveItemStatement.TryBind("@IsMovie", hasProgramAttributes.IsMovie); - saveItemStatement.TryBind("@IsSports", hasProgramAttributes.IsSports); saveItemStatement.TryBind("@IsSeries", hasProgramAttributes.IsSeries); - saveItemStatement.TryBind("@IsLive", hasProgramAttributes.IsLive); - saveItemStatement.TryBind("@IsNews", hasProgramAttributes.IsNews); - saveItemStatement.TryBind("@IsPremiere", hasProgramAttributes.IsPremiere); saveItemStatement.TryBind("@EpisodeTitle", hasProgramAttributes.EpisodeTitle); saveItemStatement.TryBind("@IsRepeat", hasProgramAttributes.IsRepeat); } else { - saveItemStatement.TryBindNull("@IsKids"); saveItemStatement.TryBindNull("@IsMovie"); - saveItemStatement.TryBindNull("@IsSports"); saveItemStatement.TryBindNull("@IsSeries"); - saveItemStatement.TryBindNull("@IsLive"); - saveItemStatement.TryBindNull("@IsNews"); - saveItemStatement.TryBindNull("@IsPremiere"); saveItemStatement.TryBindNull("@EpisodeTitle"); saveItemStatement.TryBindNull("@IsRepeat"); } @@ -815,7 +765,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); var parentId = item.ParentId; - if (parentId == Guid.Empty) + if (parentId.Equals(Guid.Empty)) { saveItemStatement.TryBindNull("@ParentId"); } @@ -824,9 +774,9 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ParentId", parentId); } - if (item.Genres.Count > 0) + if (item.Genres.Length > 0) { - saveItemStatement.TryBind("@Genres", string.Join("|", item.Genres.ToArray())); + saveItemStatement.TryBind("@Genres", string.Join("|", item.Genres)); } else { @@ -841,14 +791,28 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@RunTimeTicks", item.RunTimeTicks); - saveItemStatement.TryBind("@HomePageUrl", item.HomePageUrl); saveItemStatement.TryBind("@DateCreated", item.DateCreated); saveItemStatement.TryBind("@DateModified", item.DateModified); saveItemStatement.TryBind("@PreferredMetadataLanguage", item.PreferredMetadataLanguage); saveItemStatement.TryBind("@PreferredMetadataCountryCode", item.PreferredMetadataCountryCode); - saveItemStatement.TryBind("@IsHD", item.IsHD); - saveItemStatement.TryBind("@ExternalEtag", item.ExternalEtag); + + if (item.Width > 0) + { + saveItemStatement.TryBind("@Width", item.Width); + } + else + { + saveItemStatement.TryBindNull("@Width"); + } + if (item.Height > 0) + { + saveItemStatement.TryBind("@Height", item.Height); + } + else + { + saveItemStatement.TryBindNull("@Height"); + } if (item.DateLastRefreshed != default(DateTime)) { @@ -897,7 +861,15 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@Audio"); } - saveItemStatement.TryBind("@ExternalServiceId", item.ServiceName); + var livetvChannel = item as LiveTvChannel; + if (livetvChannel != null) + { + saveItemStatement.TryBind("@ExternalServiceId", livetvChannel.ServiceName); + } + else + { + saveItemStatement.TryBindNull("@ExternalServiceId"); + } if (item.Tags.Length > 0) { @@ -924,7 +896,7 @@ namespace Emby.Server.Implementations.Data } var trailer = item as Trailer; - if (trailer != null && trailer.TrailerTypes.Count > 0) + if (trailer != null && trailer.TrailerTypes.Length > 0) { saveItemStatement.TryBind("@TrailerTypes", string.Join("|", trailer.TrailerTypes.Select(i => i.ToString()).ToArray())); } @@ -970,10 +942,10 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@Album", item.Album); saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem); - var hasSeries = item as IHasSeries; - if (hasSeries != null) + var hasSeriesName = item as IHasSeriesName; + if (hasSeriesName != null) { - saveItemStatement.TryBind("@SeriesName", hasSeries.SeriesName); + saveItemStatement.TryBind("@SeriesName", hasSeriesName.SeriesName); } else { @@ -993,7 +965,10 @@ namespace Emby.Server.Implementations.Data if (episode != null) { saveItemStatement.TryBind("@SeasonName", episode.SeasonName); - saveItemStatement.TryBind("@SeasonId", episode.SeasonId); + + var nullableSeasonId = episode.SeasonId.Equals(Guid.Empty) ? (Guid?)null : episode.SeasonId; + + saveItemStatement.TryBind("@SeasonId", nullableSeasonId); } else { @@ -1001,9 +976,12 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@SeasonId"); } + var hasSeries = item as IHasSeries; if (hasSeries != null) { - saveItemStatement.TryBind("@SeriesId", hasSeries.SeriesId); + var nullableSeriesId = hasSeries.SeriesId.Equals(Guid.Empty) ? (Guid?)null : hasSeries.SeriesId; + + saveItemStatement.TryBind("@SeriesId", nullableSeriesId); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); } else @@ -1027,22 +1005,13 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@ProductionLocations"); } - if (item.ThemeSongIds.Length > 0) + if (item.ExtraIds.Length > 0) { - saveItemStatement.TryBind("@ThemeSongIds", string.Join("|", item.ThemeSongIds.ToArray())); + saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray())); } else { - saveItemStatement.TryBindNull("@ThemeSongIds"); - } - - if (item.ThemeVideoIds.Length > 0) - { - saveItemStatement.TryBind("@ThemeVideoIds", string.Join("|", item.ThemeVideoIds.ToArray())); - } - else - { - saveItemStatement.TryBindNull("@ThemeVideoIds"); + saveItemStatement.TryBindNull("@ExtraIds"); } saveItemStatement.TryBind("@TotalBitrate", item.TotalBitrate); @@ -1089,7 +1058,7 @@ namespace Emby.Server.Implementations.Data } var ownerId = item.OwnerId; - if (ownerId != Guid.Empty) + if (!ownerId.Equals(Guid.Empty)) { saveItemStatement.TryBind("@OwnerId", ownerId); } @@ -1193,7 +1162,7 @@ namespace Emby.Server.Implementations.Data path = string.Empty; } - return path + + return GetPathToSave(path) + delimeter + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + delimeter + @@ -1215,7 +1184,7 @@ namespace Emby.Server.Implementations.Data var image = new ItemImageInfo(); - image.Path = parts[0]; + image.Path = RestorePath(parts[0]); long ticks; if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out ticks)) @@ -1255,7 +1224,7 @@ namespace Emby.Server.Implementations.Data /// public BaseItem RetrieveItem(Guid id) { - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -1324,15 +1293,6 @@ namespace Emby.Server.Implementations.Data { return false; } - - if (type == typeof(ManualCollectionsFolder)) - { - return false; - } - if (type == typeof(CameraUploadsFolder)) - { - return false; - } if (type == typeof(PlaylistsFolder)) { return false; @@ -1351,22 +1311,10 @@ namespace Emby.Server.Implementations.Data { return false; } - if (type == typeof(RecordingGroup)) - { - return false; - } if (type == typeof(LiveTvProgram)) { return false; } - if (type == typeof(LiveTvAudioRecording)) - { - return false; - } - if (type == typeof(AudioPodcast)) - { - return false; - } if (type == typeof(AudioBook)) { return false; @@ -1386,10 +1334,10 @@ namespace Emby.Server.Implementations.Data private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query) { - return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query)); + return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query)); } - private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) + private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) { var typeString = reader.GetString(0); @@ -1406,7 +1354,7 @@ namespace Emby.Server.Implementations.Data if (TypeRequiresDeserialization(type)) { - using (var stream = _memoryStreamProvider.CreateNew(reader[1].ToBlob())) + using (var stream = new MemoryStream(reader[1].ToBlob())) { stream.Position = 0; @@ -1454,13 +1402,13 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { - item.EndDate = reader[index].ReadDateTime(); + item.EndDate = reader[index].TryReadDateTime(); } index++; if (!reader.IsDBNull(index)) { - item.ChannelId = reader.GetString(index); + item.ChannelId = new Guid(reader.GetString(index)); } index++; @@ -1475,42 +1423,12 @@ namespace Emby.Server.Implementations.Data } index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsSports = reader.GetBoolean(index); - } - index++; - - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsKids = reader.GetBoolean(index); - } - index++; - if (!reader.IsDBNull(index)) { hasProgramAttributes.IsSeries = reader.GetBoolean(index); } index++; - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsLive = reader.GetBoolean(index); - } - index++; - - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsNews = reader.GetBoolean(index); - } - index++; - - if (!reader.IsDBNull(index)) - { - hasProgramAttributes.IsPremiere = reader.GetBoolean(index); - } - index++; - if (!reader.IsDBNull(index)) { hasProgramAttributes.EpisodeTitle = reader.GetString(index); @@ -1525,7 +1443,7 @@ namespace Emby.Server.Implementations.Data } else { - index += 9; + index += 4; } } @@ -1571,17 +1489,20 @@ namespace Emby.Server.Implementations.Data index++; } - if (!reader.IsDBNull(index)) - { - item.IsHD = reader.GetBoolean(index); - } - index++; - - if (HasField(query, ItemFields.ExternalEtag)) + if (HasField(query, ItemFields.Width)) { if (!reader.IsDBNull(index)) { - item.ExternalEtag = reader.GetString(index); + item.Width = reader.GetInt32(index); + } + index++; + } + + if (HasField(query, ItemFields.Height)) + { + if (!reader.IsDBNull(index)) + { + item.Height = reader.GetInt32(index); } index++; } @@ -1603,13 +1524,13 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { - item.Path = reader.GetString(index); + item.Path = RestorePath(reader.GetString(index)); } index++; if (!reader.IsDBNull(index)) { - item.PremiereDate = reader[index].ReadDateTime(); + item.PremiereDate = reader[index].TryReadDateTime(); } index++; @@ -1640,15 +1561,6 @@ namespace Emby.Server.Implementations.Data } index++; - if (HasField(query, ItemFields.HomePageUrl)) - { - if (!reader.IsDBNull(index)) - { - item.HomePageUrl = reader.GetString(index); - } - index++; - } - if (HasField(query, ItemFields.SortName)) { if (!reader.IsDBNull(index)) @@ -1686,7 +1598,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.Genres = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + item.Genres = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } index++; } @@ -1709,11 +1621,18 @@ namespace Emby.Server.Implementations.Data // TODO: Even if not needed by apps, the server needs it internally // But get this excluded from contexts where it is not needed - if (!reader.IsDBNull(index)) + if (hasServiceName) { - item.ServiceName = reader.GetString(index); + var livetvChannel = item as LiveTvChannel; + if (livetvChannel != null) + { + if (!reader.IsDBNull(index)) + { + livetvChannel.ServiceName = reader.GetString(index); + } + } + index++; } - index++; if (!reader.IsDBNull(index)) { @@ -1785,7 +1704,7 @@ namespace Emby.Server.Implementations.Data } return (TrailerType?)null; - }).Where(i => i.HasValue).Select(i => i.Value).ToList(); + }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } } index++; @@ -1815,7 +1734,7 @@ namespace Emby.Server.Implementations.Data var folder = item as Folder; if (folder != null && !reader.IsDBNull(index)) { - folder.DateLastMediaAdded = reader[index].ReadDateTime(); + folder.DateLastMediaAdded = reader[index].TryReadDateTime(); } index++; } @@ -1838,18 +1757,15 @@ namespace Emby.Server.Implementations.Data } index++; - var hasSeries = item as IHasSeries; - if (hasSeriesFields) + var hasSeriesName = item as IHasSeriesName; + if (hasSeriesName != null) { - if (hasSeries != null) + if (!reader.IsDBNull(index)) { - if (!reader.IsDBNull(index)) - { - hasSeries.SeriesName = reader.GetString(index); - } + hasSeriesName.SeriesName = reader.GetString(index); } - index++; } + index++; if (hasEpisodeAttributes) { @@ -1873,6 +1789,7 @@ namespace Emby.Server.Implementations.Data index++; } + var hasSeries = item as IHasSeries; if (hasSeriesFields) { if (hasSeries != null) @@ -1945,20 +1862,11 @@ namespace Emby.Server.Implementations.Data index++; } - if (HasField(query, ItemFields.ThemeSongIds)) + if (HasField(query, ItemFields.ExtraIds)) { if (!reader.IsDBNull(index)) { - item.ThemeSongIds = SplitToGuids(reader.GetString(index)); - } - index++; - } - - if (HasField(query, ItemFields.ThemeVideoIds)) - { - if (!reader.IsDBNull(index)) - { - item.ThemeVideoIds = SplitToGuids(reader.GetString(index)); + item.ExtraIds = SplitToGuids(reader.GetString(index)); } index++; } @@ -2054,37 +1962,15 @@ namespace Emby.Server.Implementations.Data return result; } - /// - /// Gets the critic reviews. - /// - /// The item id. - public List GetCriticReviews(Guid itemId) - { - return new List(); - } - - /// - /// Saves the critic reviews. - /// - /// The item id. - /// The critic reviews. - public void SaveCriticReviews(Guid itemId, IEnumerable criticReviews) - { - } - /// /// Gets chapters for an item /// /// The id. /// IEnumerable{ChapterInfo}. /// id - public List GetChapters(Guid id) + public List GetChapters(BaseItem item) { CheckDisposed(); - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } using (WriteLock.Read()) { @@ -2094,11 +1980,11 @@ namespace Emby.Server.Implementations.Data using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) { - statement.TryBind("@ItemId", id); + statement.TryBind("@ItemId", item.Id); foreach (var row in statement.ExecuteQuery()) { - list.Add(GetChapter(row)); + list.Add(GetChapter(row, item)); } } @@ -2114,13 +2000,9 @@ namespace Emby.Server.Implementations.Data /// The index. /// ChapterInfo. /// id - public ChapterInfo GetChapter(Guid id, int index) + public ChapterInfo GetChapter(BaseItem item, int index) { CheckDisposed(); - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } using (WriteLock.Read()) { @@ -2128,12 +2010,12 @@ namespace Emby.Server.Implementations.Data { using (var statement = PrepareStatementSafe(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) { - statement.TryBind("@ItemId", id); + statement.TryBind("@ItemId", item.Id); statement.TryBind("@ChapterIndex", index); foreach (var row in statement.ExecuteQuery()) { - return GetChapter(row); + return GetChapter(row, item); } } } @@ -2146,7 +2028,7 @@ namespace Emby.Server.Implementations.Data /// /// The reader. /// ChapterInfo. - private ChapterInfo GetChapter(IReadOnlyList reader) + private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item) { var chapter = new ChapterInfo { @@ -2161,6 +2043,11 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(2)) { chapter.ImagePath = reader.GetString(2); + + if (!string.IsNullOrEmpty(chapter.ImagePath)) + { + chapter.ImageTag = ImageProcessor.GetImageCacheTag(item, chapter); + } } if (!reader.IsDBNull(3)) @@ -2178,7 +2065,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -2188,43 +2075,75 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException("chapters"); } - var index = 0; - using (WriteLock.Write()) { using (var connection = CreateConnection()) { connection.RunInTransaction(db => { + var idBlob = id.ToGuidBlob(); + // First delete chapters - db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", id.ToGuidBlob()); + db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); - using (var saveChapterStatement = PrepareStatement(db, "replace into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values (@ItemId, @ChapterIndex, @StartPositionTicks, @Name, @ImagePath, @ImageDateModified)")) - { - foreach (var chapter in chapters) - { - if (index > 0) - { - saveChapterStatement.Reset(); - } + InsertChapters(idBlob, chapters, db); - saveChapterStatement.TryBind("@ItemId", id.ToGuidBlob()); - saveChapterStatement.TryBind("@ChapterIndex", index); - saveChapterStatement.TryBind("@StartPositionTicks", chapter.StartPositionTicks); - saveChapterStatement.TryBind("@Name", chapter.Name); - saveChapterStatement.TryBind("@ImagePath", chapter.ImagePath); - saveChapterStatement.TryBind("@ImageDateModified", chapter.ImageDateModified); - - saveChapterStatement.MoveNext(); - - index++; - } - } }, TransactionMode); } } } + private void InsertChapters(byte[] idBlob, List chapters, IDatabaseConnection db) + { + var startIndex = 0; + var limit = 100; + var chapterIndex = 0; + + while (startIndex < chapters.Count) + { + var insertText = new StringBuilder("insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values "); + + var endIndex = Math.Min(chapters.Count, startIndex + limit); + var isSubsequentRow = false; + + for (var i = startIndex; i < endIndex; i++) + { + if (isSubsequentRow) + { + insertText.Append(","); + } + + insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0})", i.ToString(CultureInfo.InvariantCulture)); + isSubsequentRow = true; + } + + using (var statement = PrepareStatementSafe(db, insertText.ToString())) + { + statement.TryBind("@ItemId", idBlob); + + for (var i = startIndex; i < endIndex; i++) + { + var index = i.ToString(CultureInfo.InvariantCulture); + + var chapter = chapters[i]; + + statement.TryBind("@ChapterIndex" + index, chapterIndex); + statement.TryBind("@StartPositionTicks" + index, chapter.StartPositionTicks); + statement.TryBind("@Name" + index, chapter.Name); + statement.TryBind("@ImagePath" + index, chapter.ImagePath); + statement.TryBind("@ImageDateModified" + index, chapter.ImageDateModified); + + chapterIndex++; + } + + statement.Reset(); + statement.MoveNext(); + } + + startIndex += limit; + } + } + private bool EnableJoinUserData(InternalItemsQuery query) { if (query.User == null) @@ -2232,11 +2151,6 @@ namespace Emby.Server.Implementations.Data return false; } - if (query.SimilarTo != null && query.User != null) - { - //return true; - } - var sortingFields = query.OrderBy.Select(i => i.Item1).ToList(); if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) @@ -2296,7 +2210,7 @@ namespace Emby.Server.Implementations.Data .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) .ToList(); - private IEnumerable GetColumnNamesFromField(ItemFields field) + private string[] GetColumnNamesFromField(ItemFields field) { if (field == ItemFields.Settings) { @@ -2318,17 +2232,20 @@ namespace Emby.Server.Implementations.Data { return new[] { "Tags" }; } + if (field == ItemFields.IsHD) + { + return Array.Empty(); + } return new[] { field.ToString() }; } private bool HasField(InternalItemsQuery query, ItemFields name) { - var fields = query.DtoOptions.Fields; - switch (name) { - case ItemFields.HomePageUrl: + case ItemFields.Tags: + return query.DtoOptions.ContainsField(name) || HasProgramAttributes(query); case ItemFields.CustomRating: case ItemFields.ProductionLocations: case ItemFields.Settings: @@ -2336,23 +2253,20 @@ namespace Emby.Server.Implementations.Data case ItemFields.Taglines: case ItemFields.SortName: case ItemFields.Studios: - case ItemFields.Tags: - case ItemFields.ThemeSongIds: - case ItemFields.ThemeVideoIds: + case ItemFields.ExtraIds: case ItemFields.DateCreated: case ItemFields.Overview: case ItemFields.Genres: case ItemFields.DateLastMediaAdded: - case ItemFields.ExternalEtag: case ItemFields.PresentationUniqueKey: case ItemFields.InheritedParentalRatingValue: case ItemFields.ExternalSeriesId: case ItemFields.SeriesPresentationUniqueKey: case ItemFields.DateLastRefreshed: case ItemFields.DateLastSaved: - return fields.Contains(name); + return query.DtoOptions.ContainsField(name); case ItemFields.ServiceName: - return true; + return HasServiceName(query); default: return true; } @@ -2382,10 +2296,7 @@ namespace Emby.Server.Implementations.Data var types = new string[] { "Program", - "Recording", "TvChannel", - "LiveTvAudioRecording", - "LiveTvVideoRecording", "LiveTvProgram", "LiveTvTvChannel" }; @@ -2393,6 +2304,36 @@ namespace Emby.Server.Implementations.Data return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); } + private bool HasServiceName(InternalItemsQuery query) + { + var excludeParentTypes = new string[] + { + "Series", + "Season", + "MusicAlbum", + "MusicArtist", + "PhotoAlbum" + }; + + if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + if (query.IncludeItemTypes.Length == 0) + { + return true; + } + + var types = new string[] + { + "TvChannel", + "LiveTvTvChannel" + }; + + return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); + } + private bool HasStartDate(InternalItemsQuery query) { var excludeParentTypes = new string[] @@ -2417,9 +2358,6 @@ namespace Emby.Server.Implementations.Data var types = new string[] { "Program", - "Recording", - "LiveTvAudioRecording", - "LiveTvVideoRecording", "LiveTvProgram" }; @@ -2481,9 +2419,7 @@ namespace Emby.Server.Implementations.Data "MusicAlbum", "MusicVideo", "AudioBook", - "AudioPodcast", - "LiveTvAudioRecording", - "Recording" + "AudioPodcast" }; return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)); @@ -2534,13 +2470,8 @@ namespace Emby.Server.Implementations.Data if (!HasProgramAttributes(query)) { - list.Remove("IsKids"); list.Remove("IsMovie"); - list.Remove("IsSports"); list.Remove("IsSeries"); - list.Remove("IsLive"); - list.Remove("IsNews"); - list.Remove("IsPremiere"); list.Remove("EpisodeTitle"); list.Remove("IsRepeat"); list.Remove("ShowId"); @@ -2571,7 +2502,6 @@ namespace Emby.Server.Implementations.Data if (!HasSeriesFields(query)) { list.Remove("SeriesId"); - list.Remove("SeriesName"); } if (!HasEpisodeAttributes(query)) @@ -2587,13 +2517,13 @@ namespace Emby.Server.Implementations.Data if (EnableJoinUserData(query)) { - list.Add("UserData.UserId"); - list.Add("UserData.lastPlayedDate"); - list.Add("UserData.playbackPositionTicks"); - list.Add("UserData.playcount"); - list.Add("UserData.isFavorite"); - list.Add("UserData.played"); - list.Add("UserData.rating"); + list.Add("UserDatas.UserId"); + list.Add("UserDatas.lastPlayedDate"); + list.Add("UserDatas.playbackPositionTicks"); + list.Add("UserDatas.playcount"); + list.Add("UserDatas.isFavorite"); + list.Add("UserDatas.played"); + list.Add("UserDatas.rating"); } if (query.SimilarTo != null) @@ -2603,14 +2533,24 @@ namespace Emby.Server.Implementations.Data var builder = new StringBuilder(); builder.Append("("); - builder.Append("((OfficialRating=@ItemOfficialRating) * 10)"); - //builder.Append("+ ((ProductionYear=@ItemProductionYear) * 10)"); + if (string.IsNullOrEmpty(item.OfficialRating)) + { + builder.Append("((OfficialRating is null) * 10)"); + } + else + { + builder.Append("((OfficialRating=@ItemOfficialRating) * 10)"); + } - builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 2 Else 0 End )"); - builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 2 Else 0 End )"); + if (item.ProductionYear.HasValue) + { + //builder.Append("+ ((ProductionYear=@ItemProductionYear) * 10)"); + builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 10 Else 0 End )"); + builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 5 Else 0 End )"); + } //// genres, tags - builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type in (2,3,4,5) and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and Type in (2,3,4,5))) * 10)"); + builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId)) * 10)"); //builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=3 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=3)) * 3)"); @@ -2623,24 +2563,56 @@ namespace Emby.Server.Implementations.Data list.Add(builder.ToString()); var excludeIds = query.ExcludeItemIds.ToList(); - excludeIds.Add(item.Id.ToString("N")); - - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) - { - var hasTrailers = item as IHasTrailers; - if (hasTrailers != null) - { - excludeIds.AddRange(hasTrailers.GetTrailerIds().Select(i => i.ToString("N"))); - } - } + excludeIds.Add(item.Id); + excludeIds.AddRange(item.ExtraIds); query.ExcludeItemIds = excludeIds.ToArray(excludeIds.Count); query.ExcludeProviderIds = item.ProviderIds; } + if (!string.IsNullOrEmpty(query.SearchTerm)) + { + var builder = new StringBuilder(); + builder.Append("("); + + builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)"); + + if (query.SearchTerm.Length > 1) + { + builder.Append("+ ((CleanName like @SearchTermContains or (OriginalTitle not null and OriginalTitle like @SearchTermContains)) * 10)"); + } + + builder.Append(") as SearchScore"); + + list.Add(builder.ToString()); + } + return list.ToArray(list.Count); } + private void BindSearchParams(InternalItemsQuery query, IStatement statement) + { + var searchTerm = query.SearchTerm; + + if (string.IsNullOrEmpty(searchTerm)) + { + return; + } + + searchTerm = FixUnicodeChars(searchTerm); + searchTerm = GetCleanValue(searchTerm); + + var commandText = statement.SQL; + if (commandText.IndexOf("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase) != -1) + { + statement.TryBind("@SearchTermStartsWith", searchTerm + "%"); + } + if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1) + { + statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); + } + } + private void BindSimilarParams(InternalItemsQuery query, IStatement statement) { var item = query.SimilarTo; @@ -2650,9 +2622,22 @@ namespace Emby.Server.Implementations.Data return; } - statement.TryBind("@ItemOfficialRating", item.OfficialRating); - statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0); - statement.TryBind("@SimilarItemId", item.Id); + var commandText = statement.SQL; + + if (commandText.IndexOf("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase) != -1) + { + statement.TryBind("@ItemOfficialRating", item.OfficialRating); + } + + if (commandText.IndexOf("@ItemProductionYear", StringComparison.OrdinalIgnoreCase) != -1) + { + statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0); + } + + if (commandText.IndexOf("@SimilarItemId", StringComparison.OrdinalIgnoreCase) != -1) + { + statement.TryBind("@SimilarItemId", item.Id); + } } private string GetJoinUserDataText(InternalItemsQuery query) @@ -2662,7 +2647,7 @@ namespace Emby.Server.Implementations.Data return string.Empty; } - return " left join UserData on UserDataKey=UserData.Key And (UserId=@UserId)"; + return " left join UserDatas on UserDataKey=UserDatas.Key And (UserId=@UserId)"; } private string GetGroupBy(InternalItemsQuery query) @@ -2732,10 +2717,11 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); @@ -2808,15 +2794,17 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasServiceName = HasServiceName(query); var hasProgramAttributes = HasProgramAttributes(query); var hasStartDate = HasStartDate(query); var hasTrailerTypes = HasTrailerTypes(query); @@ -2825,7 +2813,7 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { list.Add(item); @@ -2860,6 +2848,28 @@ namespace Emby.Server.Implementations.Data } } + private string FixUnicodeChars(string buffer) + { + if (buffer.IndexOf('\u2013') > -1) buffer = buffer.Replace('\u2013', '-'); // en dash + if (buffer.IndexOf('\u2014') > -1) buffer = buffer.Replace('\u2014', '-'); // em dash + if (buffer.IndexOf('\u2015') > -1) buffer = buffer.Replace('\u2015', '-'); // horizontal bar + if (buffer.IndexOf('\u2017') > -1) buffer = buffer.Replace('\u2017', '_'); // double low line + if (buffer.IndexOf('\u2018') > -1) buffer = buffer.Replace('\u2018', '\''); // left single quotation mark + if (buffer.IndexOf('\u2019') > -1) buffer = buffer.Replace('\u2019', '\''); // right single quotation mark + if (buffer.IndexOf('\u201a') > -1) buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark + if (buffer.IndexOf('\u201b') > -1) buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark + if (buffer.IndexOf('\u201c') > -1) buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark + if (buffer.IndexOf('\u201d') > -1) buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark + if (buffer.IndexOf('\u201e') > -1) buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark + if (buffer.IndexOf('\u2026') > -1) buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis + if (buffer.IndexOf('\u2032') > -1) buffer = buffer.Replace('\u2032', '\''); // prime + if (buffer.IndexOf('\u2033') > -1) buffer = buffer.Replace('\u2033', '\"'); // double prime + if (buffer.IndexOf('\u0060') > -1) buffer = buffer.Replace('\u0060', '\''); // grave accent + if (buffer.IndexOf('\u00B4') > -1) buffer = buffer.Replace('\u00B4', '\''); // acute accent + + return buffer; + } + private void AddItem(List items, BaseItem newItem) { var providerIds = newItem.ProviderIds.ToList(); @@ -2989,15 +2999,15 @@ namespace Emby.Server.Implementations.Data if (EnableGroupByPresentationUniqueKey(query)) { - commandText += " select count (distinct PresentationUniqueKey)" + GetFromText(); + commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); } else if (query.GroupBySeriesPresentationUniqueKey) { - commandText += " select count (distinct SeriesPresentationUniqueKey)" + GetFromText(); + commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText(); } else { - commandText += " select count (guid)" + GetFromText(); + commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText(); } commandText += GetJoinUserDataText(query); @@ -3020,15 +3030,17 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); var hasEpisodeAttributes = HasEpisodeAttributes(query); + var hasServiceName = HasServiceName(query); var hasProgramAttributes = HasProgramAttributes(query); var hasStartDate = HasStartDate(query); var hasTrailerTypes = HasTrailerTypes(query); @@ -3037,7 +3049,7 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { list.Add(item); @@ -3052,10 +3064,11 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); @@ -3083,12 +3096,19 @@ namespace Emby.Server.Implementations.Data { if (orderBy.Count == 0) { - orderBy.Add(new Tuple("SimilarityScore", SortOrder.Descending)); - orderBy.Add(new Tuple(ItemSortBy.Random, SortOrder.Ascending)); + orderBy.Add(new ValueTuple("SimilarityScore", SortOrder.Descending)); + orderBy.Add(new ValueTuple(ItemSortBy.Random, SortOrder.Ascending)); //orderBy.Add(new Tuple(ItemSortBy.Random, SortOrder.Ascending)); } } + if (!string.IsNullOrEmpty(query.SearchTerm)) + { + orderBy = new List<(string, SortOrder)>(); + orderBy.Add(new ValueTuple("SearchScore", SortOrder.Descending)); + orderBy.Add(new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending)); + } + query.OrderBy = orderBy.ToArray(); if (orderBy.Count == 0) @@ -3111,81 +3131,81 @@ namespace Emby.Server.Implementations.Data }).ToArray()); } - private Tuple MapOrderByField(string name, InternalItemsQuery query) + private ValueTuple MapOrderByField(string name, InternalItemsQuery query) { if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase)) { // TODO - return new Tuple("SortName", false); + return new ValueTuple("SortName", false); } if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("RuntimeTicks", false); + return new ValueTuple("RuntimeTicks", false); } if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("RANDOM()", false); + return new ValueTuple("RANDOM()", false); } if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase)) { if (query.GroupBySeriesPresentationUniqueKey) { - return new Tuple("MAX(LastPlayedDate)", false); + return new ValueTuple("MAX(LastPlayedDate)", false); } - return new Tuple("LastPlayedDate", false); + return new ValueTuple("LastPlayedDate", false); } if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("PlayCount", false); + return new ValueTuple("PlayCount", false); } if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase)) { // (Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 2 Else 0 End ) - return new Tuple("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", true); + return new ValueTuple("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", true); } if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("IsFolder", true); + return new ValueTuple("IsFolder", true); } if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("played", true); + return new ValueTuple("played", true); } if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("played", false); + return new ValueTuple("played", false); } if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("DateLastMediaAdded", false); + return new ValueTuple("DateLastMediaAdded", false); } if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false); + return new ValueTuple("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false); } if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false); + return new ValueTuple("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false); } if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("InheritedParentalRatingValue", false); + return new ValueTuple("InheritedParentalRatingValue", false); } if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", false); + return new ValueTuple("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", false); } if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false); + return new ValueTuple("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false); } if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("SeriesName", false); + return new ValueTuple("SeriesName", false); } - return new Tuple(name, false); + return new ValueTuple(name, false); } public List GetItemIdsList(InternalItemsQuery query) @@ -3240,10 +3260,11 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); @@ -3311,7 +3332,7 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } // Running this again will bind the params @@ -3405,15 +3426,15 @@ namespace Emby.Server.Implementations.Data if (EnableGroupByPresentationUniqueKey(query)) { - commandText += " select count (distinct PresentationUniqueKey)" + GetFromText(); + commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); } else if (query.GroupBySeriesPresentationUniqueKey) { - commandText += " select count (distinct SeriesPresentationUniqueKey)" + GetFromText(); + commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText(); } else { - commandText += " select count (guid)" + GetFromText(); + commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText(); } commandText += GetJoinUserDataText(query); @@ -3437,10 +3458,11 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); @@ -3458,10 +3480,11 @@ namespace Emby.Server.Implementations.Data { if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); // Running this again will bind the params GetWhereClauses(query, statement); @@ -3527,14 +3550,69 @@ namespace Emby.Server.Implementations.Data { //whereClauses.Add("(UserId is null or UserId=@UserId)"); } + + var minWidth = query.MinWidth; + var maxWidth = query.MaxWidth; + if (query.IsHD.HasValue) { - whereClauses.Add("IsHD=@IsHD"); - if (statement != null) + var threshold = 1200; + if (query.IsHD.Value) { - statement.TryBind("@IsHD", query.IsHD); + minWidth = threshold; + } + else + { + maxWidth = threshold - 1; } } + + if (query.Is4K.HasValue) + { + var threshold = 3800; + if (query.Is4K.Value) + { + minWidth = threshold; + } + else + { + maxWidth = threshold - 1; + } + } + + if (minWidth.HasValue) + { + whereClauses.Add("Width>=@MinWidth"); + if (statement != null) + { + statement.TryBind("@MinWidth", minWidth); + } + } + if (query.MinHeight.HasValue) + { + whereClauses.Add("Height>=@MinHeight"); + if (statement != null) + { + statement.TryBind("@MinHeight", query.MinHeight); + } + } + if (maxWidth.HasValue) + { + whereClauses.Add("Width<=@MaxWidth"); + if (statement != null) + { + statement.TryBind("@MaxWidth", maxWidth); + } + } + if (query.MaxHeight.HasValue) + { + whereClauses.Add("Height<=@MaxHeight"); + if (statement != null) + { + statement.TryBind("@MaxHeight", query.MaxHeight); + } + } + if (query.IsLocked.HasValue) { whereClauses.Add("IsLocked=@IsLocked"); @@ -3544,140 +3622,127 @@ namespace Emby.Server.Implementations.Data } } - var exclusiveProgramAttribtues = !(query.IsMovie ?? true) || - !(query.IsSports ?? true) || - !(query.IsKids ?? true) || - !(query.IsNews ?? true) || - !(query.IsSeries ?? true); + var tags = query.Tags.ToList(); + var excludeTags = query.ExcludeTags.ToList(); - if (exclusiveProgramAttribtues) - { - if (query.IsMovie.HasValue) - { - var alternateTypes = new List(); - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) - { - alternateTypes.Add(typeof(Movie).FullName); - } - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) - { - alternateTypes.Add(typeof(Trailer).FullName); - } + //if (!(query.IsMovie ?? true) || !(query.IsSeries ?? true)) + //{ + // if (query.IsMovie.HasValue) + // { + // var alternateTypes = new List(); + // if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) + // { + // alternateTypes.Add(typeof(Movie).FullName); + // } + // if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) + // { + // alternateTypes.Add(typeof(Trailer).FullName); + // } - if (alternateTypes.Count == 0) - { - whereClauses.Add("IsMovie=@IsMovie"); - if (statement != null) - { - statement.TryBind("@IsMovie", query.IsMovie); - } - } - else - { - whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); - if (statement != null) - { - statement.TryBind("@IsMovie", query.IsMovie); - } - } - } - if (query.IsSeries.HasValue) - { - whereClauses.Add("IsSeries=@IsSeries"); - if (statement != null) - { - statement.TryBind("@IsSeries", query.IsSeries); - } - } - if (query.IsNews.HasValue) - { - whereClauses.Add("IsNews=@IsNews"); - if (statement != null) - { - statement.TryBind("@IsNews", query.IsNews); - } - } - if (query.IsKids.HasValue) - { - whereClauses.Add("IsKids=@IsKids"); - if (statement != null) - { - statement.TryBind("@IsKids", query.IsKids); - } - } - if (query.IsSports.HasValue) - { - whereClauses.Add("IsSports=@IsSports"); - if (statement != null) - { - statement.TryBind("@IsSports", query.IsSports); - } - } - } - else + // if (alternateTypes.Count == 0) + // { + // whereClauses.Add("IsMovie=@IsMovie"); + // if (statement != null) + // { + // statement.TryBind("@IsMovie", query.IsMovie); + // } + // } + // else + // { + // whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); + // if (statement != null) + // { + // statement.TryBind("@IsMovie", query.IsMovie); + // } + // } + // } + //} + //else + //{ + + //} + + if (query.IsMovie ?? false) { var programAttribtues = new List(); - if (query.IsMovie ?? false) - { - var alternateTypes = new List(); - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) - { - alternateTypes.Add(typeof(Movie).FullName); - } - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) - { - alternateTypes.Add(typeof(Trailer).FullName); - } - if (alternateTypes.Count == 0) - { - programAttribtues.Add("IsMovie=@IsMovie"); - } - else - { - programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)"); - } + var alternateTypes = new List(); + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) + { + alternateTypes.Add(typeof(Movie).FullName); + } + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) + { + alternateTypes.Add(typeof(Trailer).FullName); + } - if (statement != null) - { - statement.TryBind("@IsMovie", true); - } - } - if (query.IsSports ?? false) + if (alternateTypes.Count == 0) { - programAttribtues.Add("IsSports=@IsSports"); - if (statement != null) - { - statement.TryBind("@IsSports", query.IsSports); - } + programAttribtues.Add("IsMovie=@IsMovie"); } - if (query.IsNews ?? false) + else { - programAttribtues.Add("IsNews=@IsNews"); - if (statement != null) - { - statement.TryBind("@IsNews", query.IsNews); - } + programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)"); } - if (query.IsSeries ?? false) + + if (statement != null) { - programAttribtues.Add("IsSeries=@IsSeries"); - if (statement != null) - { - statement.TryBind("@IsSeries", query.IsSeries); - } + statement.TryBind("@IsMovie", true); } - if (query.IsKids ?? false) + + whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray(programAttribtues.Count)) + ")"); + } + else if (query.IsMovie.HasValue) + { + whereClauses.Add("IsMovie=@IsMovie"); + if (statement != null) { - programAttribtues.Add("IsKids=@IsKids"); - if (statement != null) - { - statement.TryBind("@IsKids", query.IsKids); - } + statement.TryBind("@IsMovie", query.IsMovie); } - if (programAttribtues.Count > 0) + } + + if (query.IsSeries.HasValue) + { + whereClauses.Add("IsSeries=@IsSeries"); + if (statement != null) { - whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray(programAttribtues.Count)) + ")"); + statement.TryBind("@IsSeries", query.IsSeries); + } + } + + if (query.IsSports.HasValue) + { + if (query.IsSports.Value) + { + tags.Add("Sports"); + } + else + { + excludeTags.Add("Sports"); + } + } + + if (query.IsNews.HasValue) + { + if (query.IsNews.Value) + { + tags.Add("News"); + } + else + { + excludeTags.Add("News"); + } + } + + if (query.IsKids.HasValue) + { + if (query.IsKids.Value) + { + tags.Add("Kids"); + } + else + { + excludeTags.Add("Kids"); } } @@ -3686,6 +3751,11 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("SimilarityScore > " + (query.MinSimilarityScore - 1).ToString(CultureInfo.InvariantCulture)); } + if (!string.IsNullOrEmpty(query.SearchTerm)) + { + whereClauses.Add("SearchScore > 0"); + } + if (query.IsFolder.HasValue) { whereClauses.Add("IsFolder=@IsFolder"); @@ -3710,19 +3780,23 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(string.Format("type in ({0})", inClause)); } - var excludeTypes = query.ExcludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); - if (excludeTypes.Length == 1) + // Only specify excluded types if no included types are specified + if (includeTypes.Length == 0) { - whereClauses.Add("type<>@type"); - if (statement != null) + var excludeTypes = query.ExcludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); + if (excludeTypes.Length == 1) { - statement.TryBind("@type", excludeTypes[0]); + whereClauses.Add("type<>@type"); + if (statement != null) + { + statement.TryBind("@type", excludeTypes[0]); + } + } + else if (excludeTypes.Length > 1) + { + var inClause = string.Join(",", excludeTypes.Select(i => "'" + i + "'").ToArray()); + whereClauses.Add(string.Format("type not in ({0})", inClause)); } - } - else if (excludeTypes.Length > 1) - { - var inClause = string.Join(",", excludeTypes.Select(i => "'" + i + "'").ToArray()); - whereClauses.Add(string.Format("type not in ({0})", inClause)); } if (query.ChannelIds.Length == 1) @@ -3730,30 +3804,31 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("ChannelId=@ChannelId"); if (statement != null) { - statement.TryBind("@ChannelId", query.ChannelIds[0]); + statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N")); } } else if (query.ChannelIds.Length > 1) { - var inClause = string.Join(",", query.ChannelIds.Where(IsValidId).Select(i => "'" + i + "'").ToArray()); + var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N") + "'").ToArray()); whereClauses.Add(string.Format("ChannelId in ({0})", inClause)); } - if (query.ParentId.HasValue) + if (!query.ParentId.Equals(Guid.Empty)) { whereClauses.Add("ParentId=@ParentId"); if (statement != null) { - statement.TryBind("@ParentId", query.ParentId.Value); + statement.TryBind("@ParentId", query.ParentId); } } if (!string.IsNullOrWhiteSpace(query.Path)) { + //whereClauses.Add("(Path=@Path COLLATE NOCASE)"); whereClauses.Add("Path=@Path"); if (statement != null) { - statement.TryBind("@Path", query.Path); + statement.TryBind("@Path", GetPathToSave(query.Path)); } } @@ -3847,21 +3922,37 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value); } } - if (query.MinEndDate.HasValue) + + var minEndDate = query.MinEndDate; + var maxEndDate = query.MaxEndDate; + + if (query.HasAired.HasValue) + { + if (query.HasAired.Value) + { + maxEndDate = DateTime.UtcNow; + } + else + { + minEndDate = DateTime.UtcNow; + } + } + + if (minEndDate.HasValue) { whereClauses.Add("EndDate>=@MinEndDate"); if (statement != null) { - statement.TryBind("@MinEndDate", query.MinEndDate.Value); + statement.TryBind("@MinEndDate", minEndDate.Value); } } - if (query.MaxEndDate.HasValue) + if (maxEndDate.HasValue) { whereClauses.Add("EndDate<=@MaxEndDate"); if (statement != null) { - statement.TryBind("@MaxEndDate", query.MaxEndDate.Value); + statement.TryBind("@MaxEndDate", maxEndDate.Value); } } @@ -3955,7 +4046,8 @@ namespace Emby.Server.Implementations.Data { var paramName = "@PersonId" + index; - clauses.Add("(select Name from TypedBaseItems where guid=" + paramName + ") in (select Name from People where ItemId=Guid)"); + clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))"); + if (statement != null) { statement.TryBind(paramName, personId.ToGuidBlob()); @@ -4012,14 +4104,19 @@ namespace Emby.Server.Implementations.Data } } - if (!string.IsNullOrWhiteSpace(query.NameContains)) + // These are the same, for now + var nameContains = query.NameContains; + if (!string.IsNullOrWhiteSpace(nameContains)) { - whereClauses.Add("CleanName like @NameContains"); + whereClauses.Add("(CleanName like @NameContains or OriginalTitle like @NameContains)"); if (statement != null) { - statement.TryBind("@NameContains", "%" + GetCleanValue(query.NameContains) + "%"); + nameContains = FixUnicodeChars(nameContains); + + statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%"); } } + if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) { whereClauses.Add("SortName like @NameStartsWith"); @@ -4111,17 +4208,32 @@ namespace Emby.Server.Implementations.Data { if (query.IsPlayed.HasValue) { - if (query.IsPlayed.Value) + // We should probably figure this out for all folders, but for right now, this is the only place where we need it + if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], typeof(Series).Name, StringComparison.OrdinalIgnoreCase)) { - whereClauses.Add("(played=@IsPlayed)"); + if (query.IsPlayed.Value) + { + whereClauses.Add("PresentationUniqueKey not in (select S.SeriesPresentationUniqueKey from TypedBaseitems S left join UserDatas UD on S.UserDataKey=UD.Key And UD.UserId=@UserId where Coalesce(UD.Played, 0)=0 and S.IsFolder=0 and S.IsVirtualItem=0 and S.SeriesPresentationUniqueKey not null)"); + } + else + { + whereClauses.Add("PresentationUniqueKey in (select S.SeriesPresentationUniqueKey from TypedBaseitems S left join UserDatas UD on S.UserDataKey=UD.Key And UD.UserId=@UserId where Coalesce(UD.Played, 0)=0 and S.IsFolder=0 and S.IsVirtualItem=0 and S.SeriesPresentationUniqueKey not null)"); + } } else { - whereClauses.Add("(played is null or played=@IsPlayed)"); - } - if (statement != null) - { - statement.TryBind("@IsPlayed", query.IsPlayed.Value); + if (query.IsPlayed.Value) + { + whereClauses.Add("(played=@IsPlayed)"); + } + else + { + whereClauses.Add("(played is null or played=@IsPlayed)"); + } + if (statement != null) + { + statement.TryBind("@IsPlayed", query.IsPlayed.Value); + } } } } @@ -4146,7 +4258,45 @@ namespace Emby.Server.Implementations.Data { var paramName = "@ArtistIds" + index; - clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type<=1)"); + clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); + if (statement != null) + { + statement.TryBind(paramName, artistId.ToGuidBlob()); + } + index++; + } + var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; + whereClauses.Add(clause); + } + + if (query.AlbumArtistIds.Length > 0) + { + var clauses = new List(); + var index = 0; + foreach (var artistId in query.AlbumArtistIds) + { + var paramName = "@ArtistIds" + index; + + clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))"); + if (statement != null) + { + statement.TryBind(paramName, artistId.ToGuidBlob()); + } + index++; + } + var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; + whereClauses.Add(clause); + } + + if (query.ContributingArtistIds.Length > 0) + { + var clauses = new List(); + var index = 0; + foreach (var artistId in query.ContributingArtistIds) + { + var paramName = "@ArtistIds" + index; + + clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))"); if (statement != null) { statement.TryBind(paramName, artistId.ToGuidBlob()); @@ -4184,7 +4334,7 @@ namespace Emby.Server.Implementations.Data { var paramName = "@ExcludeArtistId" + index; - clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type<=1)"); + clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { statement.TryBind(paramName, artistId.ToGuidBlob()); @@ -4203,7 +4353,7 @@ namespace Emby.Server.Implementations.Data { var paramName = "@GenreId" + index; - clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=2)"); + clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))"); if (statement != null) { statement.TryBind(paramName, genreId.ToGuidBlob()); @@ -4231,11 +4381,11 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(clause); } - if (query.Tags.Length > 0) + if (tags.Count > 0) { var clauses = new List(); var index = 0; - foreach (var item in query.Tags) + foreach (var item in tags) { clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)"); if (statement != null) @@ -4248,6 +4398,23 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(clause); } + if (excludeTags.Count > 0) + { + var clauses = new List(); + var index = 0; + foreach (var item in excludeTags) + { + clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type=4)"); + if (statement != null) + { + statement.TryBind("@ExcludeTag" + index, GetCleanValue(item)); + } + index++; + } + var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; + whereClauses.Add(clause); + } + if (query.StudioIds.Length > 0) { var clauses = new List(); @@ -4256,7 +4423,8 @@ namespace Emby.Server.Implementations.Data { var paramName = "@StudioId" + index; - clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=3)"); + clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))"); + if (statement != null) { statement.TryBind(paramName, studioId.ToGuidBlob()); @@ -4314,6 +4482,18 @@ namespace Emby.Server.Implementations.Data } } + if (query.HasOfficialRating.HasValue) + { + if (query.HasOfficialRating.Value) + { + whereClauses.Add("(OfficialRating not null AND OfficialRating<>'')"); + } + else + { + whereClauses.Add("(OfficialRating is null OR OfficialRating='')"); + } + } + if (query.HasOverview.HasValue) { if (query.HasOverview.Value) @@ -4326,6 +4506,18 @@ namespace Emby.Server.Implementations.Data } } + if (query.HasOwnerId.HasValue) + { + if (query.HasOwnerId.Value) + { + whereClauses.Add("OwnerId not null"); + } + else + { + whereClauses.Add("OwnerId is null"); + } + } + if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage)) { whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)"); @@ -4362,6 +4554,18 @@ namespace Emby.Server.Implementations.Data } } + if (query.HasSubtitles.HasValue) + { + if (query.HasSubtitles.Value) + { + whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) not null)"); + } + else + { + whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) is null)"); + } + } + if (query.HasChapterImages.HasValue) { if (query.HasChapterImages.Value) @@ -4379,6 +4583,21 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)"); } + if (query.IsDeadArtist.HasValue && query.IsDeadArtist.Value) + { + whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type in (0,1))"); + } + + if (query.IsDeadStudio.HasValue && query.IsDeadStudio.Value) + { + whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type = 3)"); + } + + if (query.IsDeadPerson.HasValue && query.IsDeadPerson.Value) + { + whereClauses.Add("Name not in (Select Name From People)"); + } + if (query.Years.Length == 1) { whereClauses.Add("ProductionYear=@Years"); @@ -4450,7 +4669,7 @@ namespace Emby.Server.Implementations.Data includeIds.Add("Guid = @IncludeId" + index); if (statement != null) { - statement.TryBind("@IncludeId" + index, new Guid(id)); + statement.TryBind("@IncludeId" + index, id); } index++; } @@ -4467,7 +4686,7 @@ namespace Emby.Server.Implementations.Data excludeIds.Add("Guid <> @ExcludeId" + index); if (statement != null) { - statement.TryBind("@ExcludeId" + index, new Guid(id)); + statement.TryBind("@ExcludeId" + index, id); } index++; } @@ -4499,7 +4718,40 @@ namespace Emby.Server.Implementations.Data break; } - whereClauses.Add(string.Join(" AND ", excludeIds.ToArray())); + if (excludeIds.Count > 0) + { + whereClauses.Add(string.Join(" AND ", excludeIds.ToArray())); + } + } + + if (query.HasAnyProviderId.Count > 0) + { + var hasProviderIds = new List(); + + var index = 0; + foreach (var pair in query.HasAnyProviderId) + { + if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var paramName = "@HasAnyProviderId" + index; + //hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")"); + hasProviderIds.Add("ProviderIds like " + paramName + ""); + if (statement != null) + { + statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); + } + index++; + + break; + } + + if (hasProviderIds.Count > 0) + { + whereClauses.Add("(" + string.Join(" OR ", hasProviderIds.ToArray()) + ")"); + } } if (query.HasImdbId.HasValue) @@ -4516,33 +4768,11 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("ProviderIds like '%tvdb=%'"); } - if (query.HasThemeSong.HasValue) - { - if (query.HasThemeSong.Value) - { - whereClauses.Add("ThemeSongIds not null"); - } - else - { - whereClauses.Add("ThemeSongIds is null"); - } - } - if (query.HasThemeVideo.HasValue) - { - if (query.HasThemeVideo.Value) - { - whereClauses.Add("ThemeVideoIds not null"); - } - else - { - whereClauses.Add("ThemeVideoIds is null"); - } - } var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; - var queryTopParentIds = query.TopParentIds.Where(IsValidId).ToArray(); + var queryTopParentIds = query.TopParentIds; if (queryTopParentIds.Length == 1) { @@ -4565,12 +4795,12 @@ namespace Emby.Server.Implementations.Data } if (statement != null) { - statement.TryBind("@TopParentId", queryTopParentIds[0]); + statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N")); } } else if (queryTopParentIds.Length > 1) { - var val = string.Join(",", queryTopParentIds.Select(i => "'" + i + "'").ToArray()); + var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N") + "'").ToArray()); if (enableItemsByName && includedItemByNameTypes.Count == 1) { @@ -4597,12 +4827,12 @@ namespace Emby.Server.Implementations.Data if (statement != null) { - statement.TryBind("@AncestorId", new Guid(query.AncestorIds[0])); + statement.TryBind("@AncestorId", query.AncestorIds[0]); } } if (query.AncestorIds.Length > 1) { - var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + new Guid(i).ToString("N") + "'").ToArray()); + var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N") + "'").ToArray()); whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause)); } if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) @@ -4647,6 +4877,114 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + tagValuesList + ")) is null)"); } + if (query.SeriesStatuses.Length > 0) + { + var statuses = new List(); + + foreach (var seriesStatus in query.SeriesStatuses) + { + statuses.Add("data like '%" + seriesStatus + "%'"); + } + + whereClauses.Add("(" + string.Join(" OR ", statuses.ToArray()) + ")"); + } + + if (query.BoxSetLibraryFolders.Length > 0) + { + var folderIdQueries = new List(); + + foreach (var folderId in query.BoxSetLibraryFolders) + { + folderIdQueries.Add("data like '%" + folderId.ToString("N") + "%'"); + } + + whereClauses.Add("(" + string.Join(" OR ", folderIdQueries.ToArray()) + ")"); + } + + if (query.VideoTypes.Length > 0) + { + var videoTypes = new List(); + + foreach (var videoType in query.VideoTypes) + { + videoTypes.Add("data like '%\"VideoType\":\"" + videoType.ToString() + "\"%'"); + } + + whereClauses.Add("(" + string.Join(" OR ", videoTypes.ToArray()) + ")"); + } + + if (query.Is3D.HasValue) + { + if (query.Is3D.Value) + { + whereClauses.Add("data like '%Video3DFormat%'"); + } + else + { + whereClauses.Add("data not like '%Video3DFormat%'"); + } + } + + if (query.IsPlaceHolder.HasValue) + { + if (query.IsPlaceHolder.Value) + { + whereClauses.Add("data like '%\"IsPlaceHolder\":true%'"); + } + else + { + whereClauses.Add("(data is null or data not like '%\"IsPlaceHolder\":true%')"); + } + } + + if (query.HasSpecialFeature.HasValue) + { + if (query.HasSpecialFeature.Value) + { + whereClauses.Add("ExtraIds not null"); + } + else + { + whereClauses.Add("ExtraIds is null"); + } + } + + if (query.HasTrailer.HasValue) + { + if (query.HasTrailer.Value) + { + whereClauses.Add("ExtraIds not null"); + } + else + { + whereClauses.Add("ExtraIds is null"); + } + } + + if (query.HasThemeSong.HasValue) + { + if (query.HasThemeSong.Value) + { + whereClauses.Add("ExtraIds not null"); + } + else + { + whereClauses.Add("ExtraIds is null"); + } + } + + if (query.HasThemeVideo.HasValue) + { + if (query.HasThemeVideo.Value) + { + whereClauses.Add("ExtraIds not null"); + } + else + { + whereClauses.Add("ExtraIds is null"); + } + } + return whereClauses; } @@ -4749,8 +5087,6 @@ namespace Emby.Server.Implementations.Data { typeof(LiveTvProgram), typeof(LiveTvChannel), - typeof(LiveTvVideoRecording), - typeof(LiveTvAudioRecording), typeof(Series), typeof(Audio), typeof(MusicAlbum), @@ -4759,7 +5095,6 @@ namespace Emby.Server.Implementations.Data typeof(MusicVideo), typeof(Movie), typeof(Playlist), - typeof(AudioPodcast), typeof(AudioBook), typeof(Trailer), typeof(BoxSet), @@ -4825,7 +5160,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type dict[t.Name] = new[] { t.FullName }; } - dict["Recording"] = new[] { typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName }; dict["Program"] = new[] { typeof(LiveTvProgram).FullName }; dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName }; @@ -4848,7 +5182,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type public void DeleteItem(Guid id, CancellationToken cancellationToken) { - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -4861,23 +5195,25 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { connection.RunInTransaction(db => { + var idBlob = id.ToGuidBlob(); + // Delete people - ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", id.ToGuidBlob()); + ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); // Delete chapters - ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", id.ToGuidBlob()); + ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", idBlob); // Delete media streams - ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", id.ToGuidBlob()); + ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", idBlob); // Delete ancestors - ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", id.ToGuidBlob()); + ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", idBlob); // Delete item values - ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", id.ToGuidBlob()); + ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", idBlob); // Delete the item - ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", id.ToGuidBlob()); + ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob); }, TransactionMode); } } @@ -4979,7 +5315,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var whereClauses = new List(); - if (query.ItemId != Guid.Empty) + if (!query.ItemId.Equals(Guid.Empty)) { whereClauses.Add("ItemId=@ItemId"); if (statement != null) @@ -4987,7 +5323,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); } } - if (query.AppearsInItemId != Guid.Empty) + if (!query.AppearsInItemId.Equals(Guid.Empty)) { whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)"); if (statement != null) @@ -5047,9 +5383,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return whereClauses; } - private void UpdateAncestors(Guid itemId, List ancestorIds, IDatabaseConnection db, IStatement deleteAncestorsStatement, IStatement updateAncestorsStatement) + private void UpdateAncestors(Guid itemId, List ancestorIds, IDatabaseConnection db, IStatement deleteAncestorsStatement) { - if (itemId == Guid.Empty) + if (itemId.Equals(Guid.Empty)) { throw new ArgumentNullException("itemId"); } @@ -5061,18 +5397,46 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); + var itemIdBlob = itemId.ToGuidBlob(); + // First delete deleteAncestorsStatement.Reset(); - deleteAncestorsStatement.TryBind("@ItemId", itemId.ToGuidBlob()); + deleteAncestorsStatement.TryBind("@ItemId", itemIdBlob); deleteAncestorsStatement.MoveNext(); - foreach (var ancestorId in ancestorIds) + if (ancestorIds.Count == 0) { - updateAncestorsStatement.Reset(); - updateAncestorsStatement.TryBind("@ItemId", itemId.ToGuidBlob()); - updateAncestorsStatement.TryBind("@AncestorId", ancestorId.ToGuidBlob()); - updateAncestorsStatement.TryBind("@AncestorIdText", ancestorId.ToString("N")); - updateAncestorsStatement.MoveNext(); + return; + } + + var insertText = new StringBuilder("insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values "); + + for (var i = 0; i < ancestorIds.Count; i++) + { + if (i > 0) + { + insertText.Append(","); + } + + insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture)); + } + + using (var statement = PrepareStatementSafe(db, insertText.ToString())) + { + statement.TryBind("@ItemId", itemIdBlob); + + for (var i = 0; i < ancestorIds.Count; i++) + { + var index = i.ToString(CultureInfo.InvariantCulture); + + var ancestorId = ancestorIds[i]; + + statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob()); + statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N")); + } + + statement.Reset(); + statement.MoveNext(); } } @@ -5249,18 +5613,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var columns = _retriveItemColumns.ToList(); columns.AddRange(itemCountColumns.Select(i => i.Item2).ToArray()); - columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); - - var commandText = "select " + string.Join(",", columns.ToArray()) + GetFromText(); - commandText += GetJoinUserDataText(query); - + // do this first before calling GetFinalColumnsToSelect, otherwise ExcludeItemIds will be set by SimilarTo var innerQuery = new InternalItemsQuery(query.User) { ExcludeItemTypes = query.ExcludeItemTypes, IncludeItemTypes = query.IncludeItemTypes, MediaTypes = query.MediaTypes, AncestorIds = query.AncestorIds, - ExcludeItemIds = query.ExcludeItemIds, ItemIds = query.ItemIds, TopParentIds = query.TopParentIds, ParentId = query.ParentId, @@ -5273,6 +5632,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type IsSeries = query.IsSeries }; + columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList(); + + var commandText = "select " + string.Join(",", columns.ToArray()) + GetFromText(); + commandText += GetJoinUserDataText(query); + var innerWhereClauses = GetWhereClauses(innerQuery, null); var innerWhereText = innerWhereClauses.Count == 0 ? @@ -5305,7 +5669,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type GenreIds = query.GenreIds, Genres = query.Genres, Years = query.Years, - NameContains = query.NameContains + NameContains = query.NameContains, + SearchTerm = query.SearchTerm, + SimilarTo = query.SimilarTo, + ExcludeItemIds = query.ExcludeItemIds }; var outerWhereClauses = GetWhereClauses(outerQuery, null); @@ -5318,7 +5685,14 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type commandText += whereText; commandText += " group by PresentationUniqueKey"; - commandText += " order by SortName"; + if (query.SimilarTo != null || !string.IsNullOrEmpty(query.SearchTerm)) + { + commandText += GetOrderByText(query); + } + else + { + commandText += " order by SortName"; + } if (query.Limit.HasValue || query.StartIndex.HasValue) { @@ -5344,7 +5718,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } if (query.EnableTotalRecordCount) { - var countText = "select count (distinct PresentationUniqueKey)" + GetFromText(); + var countText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); countText += GetJoinUserDataText(query); countText += whereText; @@ -5360,6 +5734,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var list = new List>(); var result = new QueryResult>(); + //Logger.Info("GetItemValues {0}", string.Join(";", statementTexts.ToArray())); var statements = PrepareAllSafe(db, statementTexts); if (!isReturningZeroItems) @@ -5369,7 +5744,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@SelectType", returnType); if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } if (typeSubQuery != null) @@ -5377,11 +5752,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type GetWhereClauses(typeSubQuery, null); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); GetWhereClauses(innerQuery, statement); GetWhereClauses(outerQuery, statement); var hasEpisodeAttributes = HasEpisodeAttributes(query); var hasProgramAttributes = HasProgramAttributes(query); + var hasServiceName = HasServiceName(query); var hasStartDate = HasStartDate(query); var hasTrailerTypes = HasTrailerTypes(query); var hasArtistFields = HasArtistFields(query); @@ -5389,7 +5766,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type foreach (var row in statement.ExecuteQuery()) { - var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); + var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); if (item != null) { var countStartColumn = columns.Count - 1; @@ -5404,7 +5781,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (query.EnableTotalRecordCount) { - commandText = "select count (distinct PresentationUniqueKey)" + GetFromText(); + commandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); commandText += GetJoinUserDataText(query); commandText += whereText; @@ -5414,7 +5791,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@SelectType", returnType); if (EnableJoinUserData(query)) { - statement.TryBind("@UserId", query.User.Id); + statement.TryBind("@UserId", query.User.InternalId); } if (typeSubQuery != null) @@ -5422,6 +5799,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type GetWhereClauses(typeSubQuery, null); } BindSimilarParams(query, statement); + BindSearchParams(query, statement); GetWhereClauses(innerQuery, statement); GetWhereClauses(outerQuery, statement); @@ -5535,7 +5913,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void UpdateItemValues(Guid itemId, List> values, IDatabaseConnection db) { - if (itemId == Guid.Empty) + if (itemId.Equals(Guid.Empty)) { throw new ArgumentNullException("itemId"); } @@ -5552,41 +5930,66 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type // First delete db.Execute("delete from ItemValues where ItemId=@Id", guidBlob); - using (var statement = PrepareStatement(db, "insert into ItemValues (ItemId, Type, Value, CleanValue) values (@ItemId, @Type, @Value, @CleanValue)")) - { - foreach (var pair in values) - { - var itemValue = pair.Item2; + InsertItemValues(guidBlob, values, db); + } - // Don't save if invalid - if (string.IsNullOrWhiteSpace(itemValue)) + private void InsertItemValues(byte[] idBlob, List> values, IDatabaseConnection db) + { + var startIndex = 0; + var limit = 100; + + while (startIndex < values.Count) + { + var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values "); + + var endIndex = Math.Min(values.Count, startIndex + limit); + var isSubsequentRow = false; + + for (var i = startIndex; i < endIndex; i++) + { + if (isSubsequentRow) { - continue; + insertText.Append(","); + } + + insertText.AppendFormat("(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", i.ToString(CultureInfo.InvariantCulture)); + isSubsequentRow = true; + } + + using (var statement = PrepareStatementSafe(db, insertText.ToString())) + { + statement.TryBind("@ItemId", idBlob); + + for (var i = startIndex; i < endIndex; i++) + { + var index = i.ToString(CultureInfo.InvariantCulture); + + var currentValueInfo = values[i]; + + var itemValue = currentValueInfo.Item2; + + // Don't save if invalid + if (string.IsNullOrWhiteSpace(itemValue)) + { + continue; + } + + statement.TryBind("@Type" + index, currentValueInfo.Item1); + statement.TryBind("@Value" + index, itemValue); + statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue)); } statement.Reset(); - - statement.TryBind("@ItemId", guidBlob); - statement.TryBind("@Type", pair.Item1); - statement.TryBind("@Value", itemValue); - - if (pair.Item2 == null) - { - statement.TryBindNull("@CleanValue"); - } - else - { - statement.TryBind("@CleanValue", GetCleanValue(pair.Item2)); - } - statement.MoveNext(); } + + startIndex += limit; } } public void UpdatePeople(Guid itemId, List people) { - if (itemId == Guid.Empty) + if (itemId.Equals(Guid.Empty)) { throw new ArgumentNullException("itemId"); } @@ -5602,37 +6005,72 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { using (var connection = CreateConnection()) { - // First delete - // "delete from People where ItemId=?" - connection.Execute("delete from People where ItemId=?", itemId.ToGuidBlob()); - - var listIndex = 0; - - using (var statement = PrepareStatement(connection, - "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)")) + connection.RunInTransaction(db => { - foreach (var person in people) - { - if (listIndex > 0) - { - statement.Reset(); - } + var itemIdBlob = itemId.ToGuidBlob(); - statement.TryBind("@ItemId", itemId.ToGuidBlob()); - statement.TryBind("@Name", person.Name); - statement.TryBind("@Role", person.Role); - statement.TryBind("@PersonType", person.Type); - statement.TryBind("@SortOrder", person.SortOrder); - statement.TryBind("@ListOrder", listIndex); + // First delete chapters + db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); + + InsertPeople(itemIdBlob, people, db); + + }, TransactionMode); - statement.MoveNext(); - listIndex++; - } - } } } } + private void InsertPeople(byte[] idBlob, List people, IDatabaseConnection db) + { + var startIndex = 0; + var limit = 100; + var listIndex = 0; + + while (startIndex < people.Count) + { + var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values "); + + var endIndex = Math.Min(people.Count, startIndex + limit); + var isSubsequentRow = false; + + for (var i = startIndex; i < endIndex; i++) + { + if (isSubsequentRow) + { + insertText.Append(","); + } + + insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture)); + isSubsequentRow = true; + } + + using (var statement = PrepareStatementSafe(db, insertText.ToString())) + { + statement.TryBind("@ItemId", idBlob); + + for (var i = startIndex; i < endIndex; i++) + { + var index = i.ToString(CultureInfo.InvariantCulture); + + var person = people[i]; + + statement.TryBind("@Name" + index, person.Name); + statement.TryBind("@Role" + index, person.Role); + statement.TryBind("@PersonType" + index, person.Type); + statement.TryBind("@SortOrder" + index, person.SortOrder); + statement.TryBind("@ListOrder" + index, listIndex); + + listIndex++; + } + + statement.Reset(); + statement.MoveNext(); + } + + startIndex += limit; + } + } + private PersonInfo GetPerson(IReadOnlyList reader) { var item = new PersonInfo(); @@ -5718,7 +6156,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { CheckDisposed(); - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -5734,62 +6172,111 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { using (var connection = CreateConnection()) { - // First delete chapters - connection.Execute("delete from mediastreams where ItemId=@ItemId", id.ToGuidBlob()); - - using (var statement = PrepareStatement(connection, string.Format("replace into mediastreams ({0}) values ({1})", - string.Join(",", _mediaStreamSaveColumns), - string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray())))) + connection.RunInTransaction(db => { - foreach (var stream in streams) - { - var paramList = new List(); + var itemIdBlob = id.ToGuidBlob(); - paramList.Add(id.ToGuidBlob()); - paramList.Add(stream.Index); - paramList.Add(stream.Type.ToString()); - paramList.Add(stream.Codec); - paramList.Add(stream.Language); - paramList.Add(stream.ChannelLayout); - paramList.Add(stream.Profile); - paramList.Add(stream.AspectRatio); - paramList.Add(stream.Path); + // First delete chapters + db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); - paramList.Add(stream.IsInterlaced); - paramList.Add(stream.BitRate); - paramList.Add(stream.Channels); - paramList.Add(stream.SampleRate); + InsertMediaStreams(itemIdBlob, streams, db); - paramList.Add(stream.IsDefault); - paramList.Add(stream.IsForced); - paramList.Add(stream.IsExternal); - - paramList.Add(stream.Width); - paramList.Add(stream.Height); - paramList.Add(stream.AverageFrameRate); - paramList.Add(stream.RealFrameRate); - paramList.Add(stream.Level); - paramList.Add(stream.PixelFormat); - paramList.Add(stream.BitDepth); - paramList.Add(stream.IsExternal); - paramList.Add(stream.RefFrames); - - paramList.Add(stream.CodecTag); - paramList.Add(stream.Comment); - paramList.Add(stream.NalLengthSize); - paramList.Add(stream.IsAVC); - paramList.Add(stream.Title); - - paramList.Add(stream.TimeBase); - paramList.Add(stream.CodecTimeBase); - - statement.Execute(paramList.ToArray()); - } - } + }, TransactionMode); } } } + private void InsertMediaStreams(byte[] idBlob, List streams, IDatabaseConnection db) + { + var startIndex = 0; + var limit = 10; + + while (startIndex < streams.Count) + { + var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns))); + + var endIndex = Math.Min(streams.Count, startIndex + limit); + var isSubsequentRow = false; + + for (var i = startIndex; i < endIndex; i++) + { + if (isSubsequentRow) + { + insertText.Append(","); + } + + var index = i.ToString(CultureInfo.InvariantCulture); + + var mediaStreamSaveColumns = string.Join(",", _mediaStreamSaveColumns.Skip(1).Select(m => "@" + m + index).ToArray()); + + insertText.AppendFormat("(@ItemId, {0})", mediaStreamSaveColumns); + isSubsequentRow = true; + } + + using (var statement = PrepareStatementSafe(db, insertText.ToString())) + { + statement.TryBind("@ItemId", idBlob); + + for (var i = startIndex; i < endIndex; i++) + { + var index = i.ToString(CultureInfo.InvariantCulture); + + var stream = streams[i]; + + statement.TryBind("@StreamIndex" + index, stream.Index); + statement.TryBind("@StreamType" + index, stream.Type.ToString()); + statement.TryBind("@Codec" + index, stream.Codec); + statement.TryBind("@Language" + index, stream.Language); + statement.TryBind("@ChannelLayout" + index, stream.ChannelLayout); + statement.TryBind("@Profile" + index, stream.Profile); + statement.TryBind("@AspectRatio" + index, stream.AspectRatio); + statement.TryBind("@Path" + index, GetPathToSave(stream.Path)); + + statement.TryBind("@IsInterlaced" + index, stream.IsInterlaced); + statement.TryBind("@BitRate" + index, stream.BitRate); + statement.TryBind("@Channels" + index, stream.Channels); + statement.TryBind("@SampleRate" + index, stream.SampleRate); + + statement.TryBind("@IsDefault" + index, stream.IsDefault); + statement.TryBind("@IsForced" + index, stream.IsForced); + statement.TryBind("@IsExternal" + index, stream.IsExternal); + + // Yes these are backwards due to a mistake + statement.TryBind("@Width" + index, stream.Height); + statement.TryBind("@Height" + index, stream.Width); + + statement.TryBind("@AverageFrameRate" + index, stream.AverageFrameRate); + statement.TryBind("@RealFrameRate" + index, stream.RealFrameRate); + statement.TryBind("@Level" + index, stream.Level); + + statement.TryBind("@PixelFormat" + index, stream.PixelFormat); + statement.TryBind("@BitDepth" + index, stream.BitDepth); + statement.TryBind("@IsExternal" + index, stream.IsExternal); + statement.TryBind("@RefFrames" + index, stream.RefFrames); + + statement.TryBind("@CodecTag" + index, stream.CodecTag); + statement.TryBind("@Comment" + index, stream.Comment); + statement.TryBind("@NalLengthSize" + index, stream.NalLengthSize); + statement.TryBind("@IsAvc" + index, stream.IsAVC); + statement.TryBind("@Title" + index, stream.Title); + + statement.TryBind("@TimeBase" + index, stream.TimeBase); + statement.TryBind("@CodecTimeBase" + index, stream.CodecTimeBase); + + statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries); + statement.TryBind("@ColorSpace" + index, stream.ColorSpace); + statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer); + } + + statement.Reset(); + statement.MoveNext(); + } + + startIndex += limit; + } + } + + /// /// Gets the chapter. /// @@ -5831,7 +6318,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (reader[8].SQLiteType != SQLiteType.Null) { - item.Path = reader[8].ToString(); + item.Path = RestorePath(reader[8].ToString()); } item.IsInterlaced = reader.GetBoolean(9); @@ -5935,6 +6422,21 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type item.CodecTimeBase = reader[31].ToString(); } + if (reader[32].SQLiteType != SQLiteType.Null) + { + item.ColorPrimaries = reader[32].ToString(); + } + + if (reader[33].SQLiteType != SQLiteType.Null) + { + item.ColorSpace = reader[33].ToString(); + } + + if (reader[34].SQLiteType != SQLiteType.Null) + { + item.ColorTransfer = reader[34].ToString(); + } + return item; } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index ad5c60edeb..07d64a2b0c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -9,12 +9,12 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using SQLitePCL.pretty; +using MediaBrowser.Controller.Library; namespace Emby.Server.Implementations.Data { public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository { - private readonly string _importFile; private readonly IFileSystem _fileSystem; public SqliteUserDataRepository(ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem) @@ -22,7 +22,6 @@ namespace Emby.Server.Implementations.Data { _fileSystem = fileSystem; DbFilePath = Path.Combine(appPaths.DataPath, "library.db"); - _importFile = Path.Combine(appPaths.DataPath, "userdata_v2.db"); } /// @@ -41,7 +40,7 @@ namespace Emby.Server.Implementations.Data /// Opens the connection to the database /// /// Task. - public void Initialize(ReaderWriterLockSlim writeLock, ManagedConnection managedConnection) + public void Initialize(ReaderWriterLockSlim writeLock, ManagedConnection managedConnection, IUserManager userManager) { _connection = managedConnection; @@ -50,46 +49,93 @@ namespace Emby.Server.Implementations.Data using (var connection = CreateConnection()) { - string[] queries = { + var userDatasTableExists = TableExists(connection, "UserDatas"); + var userDataTableExists = TableExists(connection, "userdata"); - "create table if not exists userdata (key nvarchar not null, userId GUID not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null)", - - "create table if not exists DataSettings (IsUserDataImported bit)", - - "drop index if exists idx_userdata", - "drop index if exists idx_userdata1", - "drop index if exists idx_userdata2", - "drop index if exists userdataindex1", - - "create unique index if not exists userdataindex on userdata (key, userId)", - "create index if not exists userdataindex2 on userdata (key, userId, played)", - "create index if not exists userdataindex3 on userdata (key, userId, playbackPositionTicks)", - "create index if not exists userdataindex4 on userdata (key, userId, isFavorite)", - - "pragma shrink_memory" - }; - - connection.RunQueries(queries); + var users = userDatasTableExists ? null : userManager.Users.ToArray(); connection.RunInTransaction(db => { - var existingColumnNames = GetColumnNames(db, "userdata"); + db.ExecuteAll(string.Join(";", new[] { - AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames); - AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames); + "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)", + + "drop index if exists idx_userdata", + "drop index if exists idx_userdata1", + "drop index if exists idx_userdata2", + "drop index if exists userdataindex1", + "drop index if exists userdataindex", + "drop index if exists userdataindex3", + "drop index if exists userdataindex4", + "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)", + "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)", + "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)", + "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)" + })); + + if (userDataTableExists) + { + var existingColumnNames = GetColumnNames(db, "userdata"); + + AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames); + AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames); + AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames); + + if (!userDatasTableExists) + { + ImportUserIds(db, users); + + db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null"); + } + } }, TransactionMode); + } + } - try + private void ImportUserIds(IDatabaseConnection db, User[] users) + { + var userIdsWithUserData = GetAllUserIdsWithUserData(db); + + using (var statement = db.PrepareStatement("update userdata set InternalUserId=@InternalUserId where UserId=@UserId")) + { + foreach (var user in users) { - ImportUserDataIfNeeded(connection); - } - catch (Exception ex) - { - Logger.ErrorException("Error in ImportUserDataIfNeeded", ex); + if (!userIdsWithUserData.Contains(user.Id)) + { + continue; + } + + statement.TryBind("@UserId", user.Id.ToGuidBlob()); + statement.TryBind("@InternalUserId", user.InternalId); + + statement.MoveNext(); + statement.Reset(); } } } + private List GetAllUserIdsWithUserData(IDatabaseConnection db) + { + List list = new List(); + + using (var statement = PrepareStatement(db, "select DISTINCT UserId from UserData where UserId not null")) + { + foreach (var row in statement.ExecuteQuery()) + { + try + { + list.Add(row[0].ReadGuidFromBlob()); + } + catch + { + + } + } + } + + return list; + } + protected override bool EnableTempStoreMemory { get @@ -98,90 +144,39 @@ namespace Emby.Server.Implementations.Data } } - private void ImportUserDataIfNeeded(ManagedConnection connection) - { - if (!_fileSystem.FileExists(_importFile)) - { - return; - } - - var fileToImport = _importFile; - var isImported = connection.Query("select IsUserDataImported from DataSettings").SelectScalarBool().FirstOrDefault(); - - if (isImported) - { - return; - } - - ImportUserData(connection, fileToImport); - - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("replace into DataSettings (IsUserDataImported) values (@IsUserDataImported)")) - { - statement.TryBind("@IsUserDataImported", true); - statement.MoveNext(); - } - }, TransactionMode); - } - - private void ImportUserData(ManagedConnection connection, string file) - { - SqliteExtensions.Attach(connection, file, "UserDataBackup"); - - var columns = "key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex"; - - connection.RunInTransaction(db => - { - db.Execute("REPLACE INTO userdata(" + columns + ") SELECT " + columns + " FROM UserDataBackup.userdata;"); - }, TransactionMode); - } - /// /// Saves the user data. /// - /// The user id. - /// The key. - /// The user data. - /// The cancellation token. - /// Task. - /// userData - /// or - /// cancellationToken - /// or - /// userId - /// or - /// userDataId - public void SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken) + public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken) { if (userData == null) { throw new ArgumentNullException("userData"); } - if (userId == Guid.Empty) + if (internalUserId <= 0) { - throw new ArgumentNullException("userId"); + throw new ArgumentNullException("internalUserId"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } - PersistUserData(userId, key, userData, cancellationToken); + PersistUserData(internalUserId, key, userData, cancellationToken); } - public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken) + public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken) { if (userData == null) { throw new ArgumentNullException("userData"); } - if (userId == Guid.Empty) + if (internalUserId <= 0) { - throw new ArgumentNullException("userId"); + throw new ArgumentNullException("internalUserId"); } - PersistAllUserData(userId, userData, cancellationToken); + PersistAllUserData(internalUserId, userData, cancellationToken); } /// @@ -192,7 +187,7 @@ namespace Emby.Server.Implementations.Data /// The user data. /// The cancellation token. /// Task. - public void PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken) + public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -202,17 +197,17 @@ namespace Emby.Server.Implementations.Data { connection.RunInTransaction(db => { - SaveUserData(db, userId, key, userData); + SaveUserData(db, internalUserId, key, userData); }, TransactionMode); } } } - private void SaveUserData(IDatabaseConnection db, Guid userId, string key, UserItemData userData) + private void SaveUserData(IDatabaseConnection db, long internalUserId, string key, UserItemData userData) { - using (var statement = db.PrepareStatement("replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)")) + using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)")) { - statement.TryBind("@userId", userId.ToGuidBlob()); + statement.TryBind("@userId", internalUserId); statement.TryBind("@key", key); if (userData.Rating.HasValue) @@ -263,7 +258,7 @@ namespace Emby.Server.Implementations.Data /// /// Persist all user data for the specified user /// - private void PersistAllUserData(Guid userId, UserItemData[] userDataList, CancellationToken cancellationToken) + private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -275,7 +270,7 @@ namespace Emby.Server.Implementations.Data { foreach (var userItemData in userDataList) { - SaveUserData(db, userId, userItemData.Key, userItemData); + SaveUserData(db, internalUserId, userItemData.Key, userItemData); } }, TransactionMode); } @@ -293,11 +288,11 @@ namespace Emby.Server.Implementations.Data /// or /// key /// - public UserItemData GetUserData(Guid userId, string key) + public UserItemData GetUserData(long internalUserId, string key) { - if (userId == Guid.Empty) + if (internalUserId <= 0) { - throw new ArgumentNullException("userId"); + throw new ArgumentNullException("internalUserId"); } if (string.IsNullOrEmpty(key)) { @@ -308,9 +303,9 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId")) + using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) { - statement.TryBind("@UserId", userId.ToGuidBlob()); + statement.TryBind("@UserId", internalUserId); statement.TryBind("@Key", key); foreach (var row in statement.ExecuteQuery()) @@ -324,12 +319,8 @@ namespace Emby.Server.Implementations.Data } } - public UserItemData GetUserData(Guid userId, List keys) + public UserItemData GetUserData(long internalUserId, List keys) { - if (userId == Guid.Empty) - { - throw new ArgumentNullException("userId"); - } if (keys == null) { throw new ArgumentNullException("keys"); @@ -340,7 +331,7 @@ namespace Emby.Server.Implementations.Data return null; } - return GetUserData(userId, keys[0]); + return GetUserData(internalUserId, keys[0]); } /// @@ -348,11 +339,11 @@ namespace Emby.Server.Implementations.Data /// /// /// - public List GetAllUserData(Guid userId) + public List GetAllUserData(long internalUserId) { - if (userId == Guid.Empty) + if (internalUserId <= 0) { - throw new ArgumentNullException("userId"); + throw new ArgumentNullException("internalUserId"); } var list = new List(); @@ -361,9 +352,9 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection()) { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@UserId")) + using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) { - statement.TryBind("@UserId", userId.ToGuidBlob()); + statement.TryBind("@UserId", internalUserId); foreach (var row in statement.ExecuteQuery()) { @@ -385,7 +376,7 @@ namespace Emby.Server.Implementations.Data var userData = new UserItemData(); userData.Key = reader[0].ToString(); - userData.UserId = reader[1].ReadGuidFromBlob(); + //userData.UserId = reader[1].ReadGuidFromBlob(); if (reader[2].SQLiteType != SQLiteType.Null) { @@ -399,7 +390,7 @@ namespace Emby.Server.Implementations.Data if (reader[7].SQLiteType != SQLiteType.Null) { - userData.LastPlayedDate = reader[7].ReadDateTime(); + userData.LastPlayedDate = reader[7].TryReadDateTime(); } if (reader[8].SQLiteType != SQLiteType.Null) diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index e89de11c6d..da828aa117 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -18,13 +18,11 @@ namespace Emby.Server.Implementations.Data public class SqliteUserRepository : BaseSqliteRepository, IUserRepository { private readonly IJsonSerializer _jsonSerializer; - private readonly IMemoryStreamFactory _memoryStreamProvider; - public SqliteUserRepository(ILogger logger, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamProvider) + public SqliteUserRepository(ILogger logger, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer) : base(logger) { _jsonSerializer = jsonSerializer; - _memoryStreamProvider = memoryStreamProvider; DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); } @@ -51,37 +49,46 @@ namespace Emby.Server.Implementations.Data { RunDefaultInitialization(connection); - string[] queries = { + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - "create table if not exists users (guid GUID primary key NOT NULL, data BLOB NOT NULL)", - "create index if not exists idx_users on users(guid)", + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); - "pragma shrink_memory" - }; + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); + } + } + } - connection.RunQueries(queries); + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.ErrorException("Error migrating users database", ex); } } /// /// Save a user in the repo /// - /// The user. - /// The cancellation token. - /// Task. - /// user - public void SaveUser(User user, CancellationToken cancellationToken) + public void CreateUser(User user) { if (user == null) { throw new ArgumentNullException("user"); } - cancellationToken.ThrowIfCancellationRequested(); - - var serialized = _jsonSerializer.SerializeToBytes(user, _memoryStreamProvider); - - cancellationToken.ThrowIfCancellationRequested(); + var serialized = _jsonSerializer.SerializeToBytes(user); using (WriteLock.Write()) { @@ -89,22 +96,96 @@ namespace Emby.Server.Implementations.Data { connection.RunInTransaction(db => { - using (var statement = db.PrepareStatement("replace into users (guid, data) values (@guid, @data)")) + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) { statement.TryBind("@guid", user.Id.ToGuidBlob()); statement.TryBind("@data", serialized); + statement.MoveNext(); } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + }, TransactionMode); } } } + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) + { + statement.TryBind("@InternalId", user.InternalId); + statement.TryBind("@data", serialized); + statement.MoveNext(); + } + + }, TransactionMode); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + /// /// Retrieve all users from the database /// /// IEnumerable{User}. - public IEnumerable RetrieveAllUsers() + public List RetrieveAllUsers() { var list = new List(); @@ -112,17 +193,9 @@ namespace Emby.Server.Implementations.Data { using (var connection = CreateConnection(true)) { - foreach (var row in connection.Query("select guid,data from users")) + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) { - var id = row[0].ReadGuidFromBlob(); - - using (var stream = _memoryStreamProvider.CreateNew(row[1].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.Id = id; - list.Add(user); - } + list.Add(GetUser(row)); } } } @@ -137,24 +210,22 @@ namespace Emby.Server.Implementations.Data /// The cancellation token. /// Task. /// user - public void DeleteUser(User user, CancellationToken cancellationToken) + public void DeleteUser(User user) { if (user == null) { throw new ArgumentNullException("user"); } - cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) { using (var connection = CreateConnection()) { connection.RunInTransaction(db => { - using (var statement = db.PrepareStatement("delete from users where guid=@id")) + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) { - statement.TryBind("@id", user.Id.ToGuidBlob()); + statement.TryBind("@id", user.InternalId); statement.MoveNext(); } }, TransactionMode); diff --git a/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs b/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs deleted file mode 100644 index bb9ef157c4..0000000000 --- a/Emby.Server.Implementations/Devices/CameraUploadsDynamicFolder.cs +++ /dev/null @@ -1,40 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Serialization; - -namespace Emby.Server.Implementations.Devices -{ - public class CameraUploadsDynamicFolder : IVirtualFolderCreator - { - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - public CameraUploadsDynamicFolder(IApplicationPaths appPaths, IFileSystem fileSystem) - { - _appPaths = appPaths; - _fileSystem = fileSystem; - } - - public BasePluginFolder GetFolder() - { - var path = Path.Combine(_appPaths.DataPath, "camerauploads"); - - _fileSystem.CreateDirectory(path); - - return new CameraUploadsFolder - { - Path = path - }; - } - } - -} diff --git a/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs b/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs deleted file mode 100644 index 5c205dd199..0000000000 --- a/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Serialization; - -namespace Emby.Server.Implementations.Devices -{ - public class CameraUploadsFolder : BasePluginFolder, ISupportsUserSpecificView - { - public CameraUploadsFolder() - { - Name = "Camera Uploads"; - } - - public override bool IsVisible(User user) - { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - return base.IsVisible(user) && HasChildren(); - } - - [IgnoreDataMember] - public override string CollectionType - { - get { return MediaBrowser.Model.Entities.CollectionType.HomeVideos; } - } - - [IgnoreDataMember] - public override bool SupportsInheritedParentImages - { - get - { - return false; - } - } - - public override string GetClientTypeName() - { - return typeof(CollectionFolder).Name; - } - - private bool? _hasChildren; - private bool HasChildren() - { - if (!_hasChildren.HasValue) - { - _hasChildren = LibraryManager.GetItemIds(new InternalItemsQuery { Parent = this }).Count > 0; - } - - return _hasChildren.Value; - } - - protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) - { - _hasChildren = null; - return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService); - } - - [IgnoreDataMember] - public bool EnableUserSpecificView - { - get { return true; } - } - } -} diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index ee4c4bb265..0fac886ef3 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -18,114 +18,150 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Common.Extensions; namespace Emby.Server.Implementations.Devices { public class DeviceManager : IDeviceManager { - private readonly IDeviceRepository _repo; + private readonly IJsonSerializer _json; private readonly IUserManager _userManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _libraryMonitor; private readonly IServerConfigurationManager _config; private readonly ILogger _logger; private readonly INetworkManager _network; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localizationManager; + private readonly IAuthenticationRepository _authRepo; + + public event EventHandler>> DeviceOptionsUpdated; public event EventHandler> CameraImageUploaded; - /// - /// Occurs when [device options updated]. - /// - public event EventHandler> DeviceOptionsUpdated; + private readonly object _cameraUploadSyncLock = new object(); + private readonly object _capabilitiesSyncLock = new object(); - public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IServerConfigurationManager config, ILogger logger, INetworkManager network) + public DeviceManager(IAuthenticationRepository authRepo, IJsonSerializer json, ILibraryManager libraryManager, ILocalizationManager localizationManager, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IServerConfigurationManager config, ILogger logger, INetworkManager network) { - _repo = repo; + _json = json; _userManager = userManager; _fileSystem = fileSystem; _libraryMonitor = libraryMonitor; _config = config; _logger = logger; _network = network; + _libraryManager = libraryManager; + _localizationManager = localizationManager; + _authRepo = authRepo; } - public DeviceInfo RegisterDevice(string reportedId, string name, string appName, string appVersion, string usedByUserId) + + private Dictionary _capabilitiesCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) { - if (string.IsNullOrWhiteSpace(reportedId)) + var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json"); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); + + lock (_capabilitiesSyncLock) { - throw new ArgumentNullException("reportedId"); + _capabilitiesCache[deviceId] = capabilities; + + _json.SerializeToFile(capabilities, path); + } + } + + public void UpdateDeviceOptions(string deviceId, DeviceOptions options) + { + _authRepo.UpdateDeviceOptions(deviceId, options); + + if (DeviceOptionsUpdated != null) + { + DeviceOptionsUpdated(this, new GenericEventArgs>() + { + Argument = new Tuple(deviceId, options) + }); + } + } + + public DeviceOptions GetDeviceOptions(string deviceId) + { + return _authRepo.GetDeviceOptions(deviceId); + } + + public ClientCapabilities GetCapabilities(string id) + { + lock (_capabilitiesSyncLock) + { + ClientCapabilities result; + if (_capabilitiesCache.TryGetValue(id, out result)) + { + return result; + } + + var path = Path.Combine(GetDevicePath(id), "capabilities.json"); + try + { + return _json.DeserializeFromFile(path) ?? new ClientCapabilities(); + } + catch + { + } } - var device = GetDevice(reportedId) ?? new DeviceInfo - { - Id = reportedId - }; - - device.ReportedName = name; - device.AppName = appName; - device.AppVersion = appVersion; - - if (!string.IsNullOrWhiteSpace(usedByUserId)) - { - var user = _userManager.GetUserById(usedByUserId); - - device.LastUserId = user.Id.ToString("N"); - device.LastUserName = user.Name; - } - - device.DateLastModified = DateTime.UtcNow; - - device.Name = string.IsNullOrWhiteSpace(device.CustomName) ? device.ReportedName : device.CustomName; - - _repo.SaveDevice(device); - - return device; - } - - public void SaveCapabilities(string reportedId, ClientCapabilities capabilities) - { - _repo.SaveCapabilities(reportedId, capabilities); - } - - public ClientCapabilities GetCapabilities(string reportedId) - { - return _repo.GetCapabilities(reportedId); + return new ClientCapabilities(); } public DeviceInfo GetDevice(string id) { - return _repo.GetDevice(id); + return GetDevice(id, true); + } + + private DeviceInfo GetDevice(string id, bool includeCapabilities) + { + var session = _authRepo.Get(new AuthenticationInfoQuery + { + DeviceId = id + + }).Items.FirstOrDefault(); + + var device = session == null ? null : ToDeviceInfo(session); + + return device; } public QueryResult GetDevices(DeviceQuery query) { - IEnumerable devices = _repo.GetDevices(); + var sessions = _authRepo.Get(new AuthenticationInfoQuery + { + //UserId = query.UserId + HasUser = true + + }).Items; if (query.SupportsSync.HasValue) { var val = query.SupportsSync.Value; - devices = devices.Where(i => i.Capabilities.SupportsSync == val); + sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray(); } - if (query.SupportsPersistentIdentifier.HasValue) + if (!query.UserId.Equals(Guid.Empty)) { - var val = query.SupportsPersistentIdentifier.Value; + var user = _userManager.GetUserById(query.UserId); - devices = devices.Where(i => - { - var deviceVal = i.Capabilities.SupportsPersistentIdentifier; - return deviceVal == val; - }); + sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray(); } - if (!string.IsNullOrWhiteSpace(query.UserId)) - { - devices = devices.Where(i => CanAccessDevice(query.UserId, i.Id)); - } + var array = sessions.Select(ToDeviceInfo).ToArray(); - var array = devices.ToArray(); return new QueryResult { Items = array, @@ -133,20 +169,59 @@ namespace Emby.Server.Implementations.Devices }; } - public void DeleteDevice(string id) + private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo) { - _repo.DeleteDevice(id); + var caps = GetCapabilities(authInfo.DeviceId); + + return new DeviceInfo + { + AppName = authInfo.AppName, + AppVersion = authInfo.AppVersion, + Id = authInfo.DeviceId, + LastUserId = authInfo.UserId, + LastUserName = authInfo.UserName, + Name = authInfo.DeviceName, + DateLastActivity = authInfo.DateLastActivity, + IconUrl = caps == null ? null : caps.IconUrl + }; + } + + private string GetDevicesPath() + { + return Path.Combine(_config.ApplicationPaths.DataPath, "devices"); + } + + private string GetDevicePath(string id) + { + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); } public ContentUploadHistory GetCameraUploadHistory(string deviceId) { - return _repo.GetCameraUploadHistory(deviceId); + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + + lock (_cameraUploadSyncLock) + { + try + { + return _json.DeserializeFromFile(path); + } + catch (IOException) + { + return new ContentUploadHistory + { + DeviceId = deviceId + }; + } + } } public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file) { - var device = GetDevice(deviceId); - var path = GetUploadPath(device); + var device = GetDevice(deviceId, false); + var uploadPathInfo = GetUploadPath(device); + + var path = uploadPathInfo.Item1; if (!string.IsNullOrWhiteSpace(file.Album)) { @@ -156,10 +231,12 @@ namespace Emby.Server.Implementations.Devices path = Path.Combine(path, file.Name); path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg"); - _libraryMonitor.ReportFileSystemChangeBeginning(path); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); + await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false); + + _libraryMonitor.ReportFileSystemChangeBeginning(path); + try { using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) @@ -167,7 +244,7 @@ namespace Emby.Server.Implementations.Devices await stream.CopyToAsync(fs).ConfigureAwait(false); } - _repo.AddCameraUpload(deviceId, file); + AddCameraUpload(deviceId, file); } finally { @@ -187,25 +264,99 @@ namespace Emby.Server.Implementations.Devices } } - private string GetUploadPath(DeviceInfo device) + private void AddCameraUpload(string deviceId, LocalFileInfo file) { - if (!string.IsNullOrWhiteSpace(device.CameraUploadPath)) + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); + + lock (_cameraUploadSyncLock) { - return device.CameraUploadPath; + ContentUploadHistory history; + + try + { + history = _json.DeserializeFromFile(path); + } + catch (IOException) + { + history = new ContentUploadHistory + { + DeviceId = deviceId + }; + } + + history.DeviceId = deviceId; + + var list = history.FilesUploaded.ToList(); + list.Add(file); + history.FilesUploaded = list.ToArray(list.Count); + + _json.SerializeToFile(history, path); + } + } + + internal Task EnsureLibraryFolder(string path, string name) + { + var existingFolders = _libraryManager + .RootFolder + .Children + .OfType() + .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path)) + .ToList(); + + if (existingFolders.Count > 0) + { + return Task.CompletedTask; } + _fileSystem.CreateDirectory(path); + + var libraryOptions = new LibraryOptions + { + PathInfos = new[] { new MediaPathInfo { Path = path } }, + EnablePhotos = true, + EnableRealtimeMonitor = false, + SaveLocalMetadata = true + }; + + if (string.IsNullOrWhiteSpace(name)) + { + name = _localizationManager.GetLocalizedString("HeaderCameraUploads"); + } + + return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true); + } + + private Tuple GetUploadPath(DeviceInfo device) + { var config = _config.GetUploadOptions(); var path = config.CameraUploadPath; + if (string.IsNullOrWhiteSpace(path)) { path = DefaultCameraUploadsPath; } + var topLibraryPath = path; + if (config.EnableCameraUploadSubfolders) { path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name)); } + return new Tuple(path, topLibraryPath, null); + } + + internal string GetUploadsPath() + { + var config = _config.GetUploadOptions(); + var path = config.CameraUploadPath; + + if (string.IsNullOrWhiteSpace(path)) + { + path = DefaultCameraUploadsPath; + } + return path; } @@ -214,37 +365,16 @@ namespace Emby.Server.Implementations.Devices get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads"); } } - public void UpdateDeviceInfo(string id, DeviceOptions options) + public bool CanAccessDevice(User user, string deviceId) { - var device = GetDevice(id); - - device.CustomName = options.CustomName; - device.CameraUploadPath = options.CameraUploadPath; - - device.Name = string.IsNullOrWhiteSpace(device.CustomName) ? device.ReportedName : device.CustomName; - - _repo.SaveDevice(device); - - EventHelper.FireEventIfNotNull(DeviceOptionsUpdated, this, new GenericEventArgs(device), _logger); - } - - public bool CanAccessDevice(string userId, string deviceId) - { - if (string.IsNullOrWhiteSpace(userId)) - { - throw new ArgumentNullException("userId"); - } - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentNullException("deviceId"); - } - - var user = _userManager.GetUserById(userId); - if (user == null) { throw new ArgumentException("user not found"); } + if (string.IsNullOrEmpty(deviceId)) + { + throw new ArgumentNullException("deviceId"); + } if (!CanAccessDevice(user.Policy, deviceId)) { @@ -271,15 +401,89 @@ namespace Emby.Server.Implementations.Devices return true; } - return ListHelper.ContainsIgnoreCase(policy.EnabledDevices, id); + return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase); } } + public class DeviceManagerEntryPoint : IServerEntryPoint + { + private readonly DeviceManager _deviceManager; + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private ILogger _logger; + + public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger) + { + _deviceManager = (DeviceManager)deviceManager; + _config = config; + _fileSystem = fileSystem; + _logger = logger; + } + + public async void Run() + { + if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted) + { + var path = _deviceManager.GetUploadsPath(); + + if (_fileSystem.DirectoryExists(path)) + { + try + { + await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error creating camera uploads library", ex); + } + + _config.Configuration.CameraUploadUpgraded = true; + _config.SaveConfiguration(); + } + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~DeviceManagerEntryPoint() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } + public class DevicesConfigStore : IConfigurationFactory { public IEnumerable GetConfigurations() { - return new List + return new ConfigurationStore[] { new ConfigurationStore { diff --git a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs deleted file mode 100644 index d7817b17ab..0000000000 --- a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs +++ /dev/null @@ -1,451 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using Emby.Server.Implementations.Data; -using MediaBrowser.Controller; -using MediaBrowser.Model.Logging; -using SQLitePCL.pretty; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Session; -using MediaBrowser.Controller.Configuration; - -namespace Emby.Server.Implementations.Devices -{ - public class SqliteDeviceRepository : BaseSqliteRepository, IDeviceRepository - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - protected IFileSystem FileSystem { get; private set; } - private readonly object _syncLock = new object(); - private readonly IJsonSerializer _json; - private IServerApplicationPaths _appPaths; - - public SqliteDeviceRepository(ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IJsonSerializer json) - : base(logger) - { - var appPaths = config.ApplicationPaths; - - DbFilePath = Path.Combine(appPaths.DataPath, "devices.db"); - FileSystem = fileSystem; - _json = json; - _appPaths = appPaths; - } - - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.ErrorException("Error loading database file. Will reset and retry.", ex); - - FileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - private void InitializeInternal() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - "create table if not exists Devices (Id TEXT PRIMARY KEY, Name TEXT NOT NULL, ReportedName TEXT NOT NULL, CustomName TEXT, CameraUploadPath TEXT, LastUserName TEXT, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, LastUserId TEXT, DateLastModified DATETIME NOT NULL, Capabilities TEXT NOT NULL)", - "create index if not exists idx_id on Devices(Id)" - }; - - connection.RunQueries(queries); - - MigrateDevices(); - } - } - - private void MigrateDevices() - { - List files; - try - { - files = FileSystem - .GetFilePaths(GetDevicesPath(), true) - .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - catch (IOException) - { - return; - } - - foreach (var file in files) - { - try - { - var device = _json.DeserializeFromFile(file); - - device.Name = string.IsNullOrWhiteSpace(device.CustomName) ? device.ReportedName : device.CustomName; - - SaveDevice(device); - } - catch (Exception ex) - { - Logger.ErrorException("Error reading {0}", ex, file); - } - finally - { - try - { - FileSystem.DeleteFile(file); - } - catch (IOException) - { - try - { - FileSystem.MoveFile(file, Path.ChangeExtension(file, ".old")); - } - catch (IOException) - { - } - } - } - } - } - - private const string BaseSelectText = "select Id, Name, ReportedName, CustomName, CameraUploadPath, LastUserName, AppName, AppVersion, LastUserId, DateLastModified, Capabilities from Devices"; - - public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) - { - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update devices set Capabilities=@Capabilities where Id=@Id")) - { - statement.TryBind("@Id", deviceId); - - if (capabilities == null) - { - statement.TryBindNull("@Capabilities"); - } - else - { - statement.TryBind("@Capabilities", _json.SerializeToString(capabilities)); - } - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public void SaveDevice(DeviceInfo entry) - { - if (entry == null) - { - throw new ArgumentNullException("entry"); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("replace into Devices (Id, Name, ReportedName, CustomName, CameraUploadPath, LastUserName, AppName, AppVersion, LastUserId, DateLastModified, Capabilities) values (@Id, @Name, @ReportedName, @CustomName, @CameraUploadPath, @LastUserName, @AppName, @AppVersion, @LastUserId, @DateLastModified, @Capabilities)")) - { - statement.TryBind("@Id", entry.Id); - statement.TryBind("@Name", entry.Name); - statement.TryBind("@ReportedName", entry.ReportedName); - statement.TryBind("@CustomName", entry.CustomName); - statement.TryBind("@CameraUploadPath", entry.CameraUploadPath); - statement.TryBind("@LastUserName", entry.LastUserName); - statement.TryBind("@AppName", entry.AppName); - statement.TryBind("@AppVersion", entry.AppVersion); - statement.TryBind("@DateLastModified", entry.DateLastModified); - - if (entry.Capabilities == null) - { - statement.TryBindNull("@Capabilities"); - } - else - { - statement.TryBind("@Capabilities", _json.SerializeToString(entry.Capabilities)); - } - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public DeviceInfo GetDevice(string id) - { - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var statementTexts = new List(); - statementTexts.Add(BaseSelectText + " where Id=@Id"); - - return connection.RunInTransaction(db => - { - var statements = PrepareAllSafe(db, statementTexts).ToList(); - - using (var statement = statements[0]) - { - statement.TryBind("@Id", id); - - foreach (var row in statement.ExecuteQuery()) - { - return GetEntry(row); - } - } - - return null; - - }, ReadTransactionMode); - } - } - } - - public List GetDevices() - { - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var statementTexts = new List(); - statementTexts.Add(BaseSelectText + " order by DateLastModified desc"); - - return connection.RunInTransaction(db => - { - var list = new List(); - - var statements = PrepareAllSafe(db, statementTexts).ToList(); - - using (var statement = statements[0]) - { - foreach (var row in statement.ExecuteQuery()) - { - list.Add(GetEntry(row)); - } - } - - return list; - - }, ReadTransactionMode); - } - } - } - - public ClientCapabilities GetCapabilities(string id) - { - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var statementTexts = new List(); - statementTexts.Add("Select Capabilities from Devices where Id=@Id"); - - return connection.RunInTransaction(db => - { - var statements = PrepareAllSafe(db, statementTexts).ToList(); - - using (var statement = statements[0]) - { - statement.TryBind("@Id", id); - - foreach (var row in statement.ExecuteQuery()) - { - if (row[0].SQLiteType != SQLiteType.Null) - { - return _json.DeserializeFromString(row.GetString(0)); - } - } - } - - return null; - - }, ReadTransactionMode); - } - } - } - - private DeviceInfo GetEntry(IReadOnlyList reader) - { - var index = 0; - - var info = new DeviceInfo - { - Id = reader.GetString(index) - }; - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Name = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.ReportedName = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.CustomName = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.CameraUploadPath = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.LastUserName = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.AppName = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.AppVersion = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.LastUserId = reader.GetString(index); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.DateLastModified = reader[index].ReadDateTime(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Capabilities = _json.DeserializeFromString(reader.GetString(index)); - } - - return info; - } - - private string GetDevicesPath() - { - return Path.Combine(_appPaths.DataPath, "devices"); - } - - private string GetDevicePath(string id) - { - return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); - } - - public ContentUploadHistory GetCameraUploadHistory(string deviceId) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - - lock (_syncLock) - { - try - { - return _json.DeserializeFromFile(path); - } - catch (IOException) - { - return new ContentUploadHistory - { - DeviceId = deviceId - }; - } - } - } - - public void AddCameraUpload(string deviceId, LocalFileInfo file) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); - - lock (_syncLock) - { - ContentUploadHistory history; - - try - { - history = _json.DeserializeFromFile(path); - } - catch (IOException) - { - history = new ContentUploadHistory - { - DeviceId = deviceId - }; - } - - history.DeviceId = deviceId; - - var list = history.FilesUploaded.ToList(); - list.Add(file); - history.FilesUploaded = list.ToArray(list.Count); - - _json.SerializeToFile(history, path); - } - } - - public void DeleteDevice(string id) - { - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from devices where Id=@Id")) - { - statement.TryBind("@Id", id); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - var path = GetDevicePath(id); - - lock (_syncLock) - { - try - { - FileSystem.DeleteDirectory(path, true); - } - catch (IOException) - { - } - } - } - } -} diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index a0a5f32ef6..a709607bde 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; using MediaBrowser.Model.Diagnostics; +using System.Threading; namespace Emby.Server.Implementations.Diagnostics { @@ -48,8 +49,32 @@ namespace Emby.Server.Implementations.Diagnostics } } + private bool _hasExited; + private bool HasExited + { + get + { + if (_hasExited) + { + return true; + } + + try + { + _hasExited = _process.HasExited; + } + catch (InvalidOperationException) + { + _hasExited = true; + } + + return _hasExited; + } + } + private void _process_Exited(object sender, EventArgs e) { + _hasExited = true; if (Exited != null) { Exited(this, e); @@ -98,13 +123,33 @@ namespace Emby.Server.Implementations.Diagnostics public Task WaitForExitAsync(int timeMs) { - return Task.FromResult(_process.WaitForExit(timeMs)); + //if (_process.WaitForExit(100)) + //{ + // return Task.FromResult(true); + //} + + //timeMs -= 100; + timeMs = Math.Max(0, timeMs); + + var tcs = new TaskCompletionSource(); + + var cancellationToken = new CancellationTokenSource(timeMs).Token; + + if (HasExited) + { + return Task.FromResult(true); + } + + _process.Exited += (sender, args) => tcs.TrySetResult(true); + + cancellationToken.Register(() => tcs.TrySetResult(HasExited)); + + return tcs.Task; } public void Dispose() { _process.Dispose(); - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 0a316fcf1a..437917c45c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -18,16 +18,14 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Sync; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Playlists; namespace Emby.Server.Implementations.Dto { @@ -44,13 +42,12 @@ namespace Emby.Server.Implementations.Dto private readonly IProviderManager _providerManager; private readonly Func _channelManagerFactory; - private readonly ISyncManager _syncManager; private readonly IApplicationHost _appHost; private readonly Func _deviceManager; private readonly Func _mediaSourceManager; private readonly Func _livetvManager; - public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func channelManagerFactory, ISyncManager syncManager, IApplicationHost appHost, Func deviceManager, Func mediaSourceManager, Func livetvManager) + public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func channelManagerFactory, IApplicationHost appHost, Func deviceManager, Func mediaSourceManager, Func livetvManager) { _logger = logger; _libraryManager = libraryManager; @@ -61,7 +58,6 @@ namespace Emby.Server.Implementations.Dto _fileSystem = fileSystem; _providerManager = providerManager; _channelManagerFactory = channelManagerFactory; - _syncManager = syncManager; _appHost = appHost; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; @@ -99,18 +95,6 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto[] GetBaseItemDtos(IEnumerable items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) { - if (items == null) - { - throw new ArgumentNullException("items"); - } - - if (options == null) - { - throw new ArgumentNullException("options"); - } - - var syncDictionary = GetSyncedItemProgress(options); - var returnItems = new BaseItemDto[itemCount]; var programTuples = new List>(); var channelTuples = new List>(); @@ -136,7 +120,7 @@ namespace Emby.Server.Implementations.Dto if (byName != null) { - if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.ContainsField(ItemFields.ItemCounts)) { var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user) { @@ -147,12 +131,10 @@ namespace Emby.Server.Implementations.Dto } }); - SetItemByNameInfo(item, dto, libraryItems.ToList(), user); + SetItemByNameInfo(item, dto, libraryItems, user); } } - FillSyncInfo(dto, item, options, user, syncDictionary); - returnItems[index] = dto; index++; } @@ -173,8 +155,6 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { - var syncDictionary = GetSyncedItemProgress(options); - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); var tvChannel = item as LiveTvChannel; @@ -194,7 +174,7 @@ namespace Emby.Server.Implementations.Dto if (byName != null) { - if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.ContainsField(ItemFields.ItemCounts)) { SetItemByNameInfo(item, dto, GetTaggedItems(byName, user, new DtoOptions(false) { @@ -203,123 +183,24 @@ namespace Emby.Server.Implementations.Dto }), user); } - FillSyncInfo(dto, item, options, user, syncDictionary); return dto; } - FillSyncInfo(dto, item, options, user, syncDictionary); - return dto; } - private List GetTaggedItems(IItemByName byName, User user, DtoOptions options) + private IList GetTaggedItems(IItemByName byName, User user, DtoOptions options) { - var items = byName.GetTaggedItems(new InternalItemsQuery(user) + return byName.GetTaggedItems(new InternalItemsQuery(user) { Recursive = true, DtoOptions = options - }).ToList(); - - return items; - } - - public Dictionary GetSyncedItemProgress(DtoOptions options) - { - if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && - !options.Fields.Contains(ItemFields.SyncInfo)) - { - return new Dictionary(); - } - - var deviceId = options.DeviceId; - if (string.IsNullOrWhiteSpace(deviceId)) - { - return new Dictionary(); - } - - var caps = _deviceManager().GetCapabilities(deviceId); - if (caps == null || !caps.SupportsSync) - { - return new Dictionary(); - } - - return _syncManager.GetSyncedItemProgresses(new SyncJobItemQuery - { - TargetId = deviceId, - Statuses = new[] - { - SyncJobItemStatus.Converting, - SyncJobItemStatus.Queued, - SyncJobItemStatus.Transferring, - SyncJobItemStatus.ReadyToTransfer, - SyncJobItemStatus.Synced - } }); } - public void FillSyncInfo(IEnumerable> tuples, DtoOptions options, User user) - { - if (options.Fields.Contains(ItemFields.BasicSyncInfo) || - options.Fields.Contains(ItemFields.SyncInfo)) - { - var syncProgress = GetSyncedItemProgress(options); - - foreach (var tuple in tuples) - { - var item = tuple.Item1; - - FillSyncInfo(tuple.Item2, item, options, user, syncProgress); - } - } - } - - private void FillSyncInfo(IHasSyncInfo dto, BaseItem item, DtoOptions options, User user, Dictionary syncProgress) - { - var hasFullSyncInfo = options.Fields.Contains(ItemFields.SyncInfo); - - if (!hasFullSyncInfo && !options.Fields.Contains(ItemFields.BasicSyncInfo)) - { - return; - } - - if (dto.SupportsSync ?? false) - { - SyncedItemProgress syncStatus; - if (syncProgress.TryGetValue(dto.Id, out syncStatus)) - { - if (syncStatus.Status == SyncJobItemStatus.Synced) - { - dto.SyncPercent = 100; - } - else - { - dto.SyncPercent = syncStatus.Progress; - } - - if (hasFullSyncInfo) - { - dto.HasSyncJob = true; - dto.SyncStatus = syncStatus.Status; - } - } - } - } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, List allCollectionFolders, User user = null, BaseItem owner = null) { - var fields = options.Fields; - - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (fields == null) - { - throw new ArgumentNullException("fields"); - } - var dto = new BaseItemDto { ServerId = _appHost.SystemId @@ -330,12 +211,12 @@ namespace Emby.Server.Implementations.Dto dto.SourceType = item.SourceType.ToString(); } - if (fields.Contains(ItemFields.People)) + if (options.ContainsField(ItemFields.People)) { AttachPeople(dto, item); } - if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) + if (options.ContainsField(ItemFields.PrimaryImageAspectRatio)) { try { @@ -348,7 +229,7 @@ namespace Emby.Server.Implementations.Dto } } - if (fields.Contains(ItemFields.DisplayPreferencesId)) + if (options.ContainsField(ItemFields.DisplayPreferencesId)) { dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N"); } @@ -361,74 +242,54 @@ namespace Emby.Server.Implementations.Dto var hasMediaSources = item as IHasMediaSources; if (hasMediaSources != null) { - if (fields.Contains(ItemFields.MediaSources)) + if (options.ContainsField(ItemFields.MediaSources)) { - if (user == null) - { - dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true); - } - else - { - dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true, user); - } + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(item, true, user).ToArray(); NormalizeMediaSourceContainers(dto); } } - if (fields.Contains(ItemFields.Studios)) + if (options.ContainsField(ItemFields.Studios)) { AttachStudios(dto, item); } AttachBasicFields(dto, item, owner, options); - var collectionFolder = item as ICollectionFolder; - if (collectionFolder != null) - { - dto.CollectionType = collectionFolder.CollectionType; - } - - if (fields.Contains(ItemFields.CanDelete)) + if (options.ContainsField(ItemFields.CanDelete)) { dto.CanDelete = user == null ? item.CanDelete() : item.CanDelete(user); } - if (fields.Contains(ItemFields.CanDownload)) + if (options.ContainsField(ItemFields.CanDownload)) { dto.CanDownload = user == null ? item.CanDownload() : item.CanDownload(user); } - if (fields.Contains(ItemFields.Etag)) + if (options.ContainsField(ItemFields.Etag)) { dto.Etag = item.GetEtag(user); } var liveTvManager = _livetvManager(); - if (item is ILiveTvRecording) + var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path); + if (activeRecording != null) { - liveTvManager.AddInfoToRecordingDto(item, dto, user); - } - else - { - var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path); - if (activeRecording != null) - { - dto.Type = "Recording"; - dto.CanDownload = false; - dto.RunTimeTicks = null; + dto.Type = "Recording"; + dto.CanDownload = false; + dto.RunTimeTicks = null; - if (!string.IsNullOrWhiteSpace(dto.SeriesName)) - { - dto.EpisodeTitle = dto.Name; - dto.Name = dto.SeriesName; - } - liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user); + if (!string.IsNullOrEmpty(dto.SeriesName)) + { + dto.EpisodeTitle = dto.Name; + dto.Name = dto.SeriesName; } + liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user); } return dto; @@ -439,7 +300,7 @@ namespace Emby.Server.Implementations.Dto foreach (var mediaSource in dto.MediaSources) { var container = mediaSource.Container; - if (string.IsNullOrWhiteSpace(container)) + if (string.IsNullOrEmpty(container)) { continue; } @@ -452,17 +313,17 @@ namespace Emby.Server.Implementations.Dto var path = mediaSource.Path; string fileExtensionContainer = null; - if (!string.IsNullOrWhiteSpace(path)) + if (!string.IsNullOrEmpty(path)) { path = Path.GetExtension(path); - if (!string.IsNullOrWhiteSpace(path)) + if (!string.IsNullOrEmpty(path)) { path = Path.GetExtension(path); - if (!string.IsNullOrWhiteSpace(path)) + if (!string.IsNullOrEmpty(path)) { path = path.TrimStart('.'); } - if (!string.IsNullOrWhiteSpace(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) { fileExtensionContainer = path; } @@ -473,22 +334,20 @@ namespace Emby.Server.Implementations.Dto } } - public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, Dictionary syncProgress, User user = null) + public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null) { var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user); - if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts)) + if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts)) { SetItemByNameInfo(item, dto, taggedItems, user); } - FillSyncInfo(dto, item, options, user, syncProgress); - return dto; } - private void SetItemByNameInfo(BaseItem item, BaseItemDto dto, List taggedItems, User user = null) + private void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList taggedItems, User user = null) { if (item is MusicArtist) { @@ -529,39 +388,37 @@ namespace Emby.Server.Implementations.Dto /// /// Attaches the user specific info. /// - private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions) + private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options) { - var fields = dtoOptions.Fields; - if (item.IsFolder) { var folder = (Folder)item; - if (dtoOptions.EnableUserData) + if (options.EnableUserData) { - dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, dtoOptions.Fields); + dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options); } if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) { // For these types we can try to optimize and assume these values will be equal - if (item is MusicAlbum || item is Season) + if (item is MusicAlbum || item is Season || item is Playlist) { dto.ChildCount = dto.RecursiveItemCount; } - if (dtoOptions.Fields.Contains(ItemFields.ChildCount)) + if (options.ContainsField(ItemFields.ChildCount)) { dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user); } } - if (fields.Contains(ItemFields.CumulativeRunTimeTicks)) + if (options.ContainsField(ItemFields.CumulativeRunTimeTicks)) { dto.CumulativeRunTimeTicks = item.RunTimeTicks; } - if (fields.Contains(ItemFields.DateLastMediaAdded)) + if (options.ContainsField(ItemFields.DateLastMediaAdded)) { dto.DateLastMediaAdded = folder.DateLastMediaAdded; } @@ -569,21 +426,21 @@ namespace Emby.Server.Implementations.Dto else { - if (dtoOptions.EnableUserData) + if (options.EnableUserData) { dto.UserData = _userDataRepository.GetUserDataDto(item, user); } } - if (/*!(item is LiveTvProgram) ||*/ fields.Contains(ItemFields.PlayAccess)) + if (options.ContainsField(ItemFields.PlayAccess)) { dto.PlayAccess = item.GetPlayAccess(user); } - if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo)) + if (options.ContainsField(ItemFields.BasicSyncInfo)) { var userCanSync = user != null && user.Policy.EnableContentDownloading; - if (userCanSync && _syncManager.SupportsSync(item)) + if (userCanSync && item.SupportsExternalTransfer) { dto.SupportsSync = true; } @@ -610,47 +467,15 @@ namespace Emby.Server.Implementations.Dto /// item public string GetDtoId(BaseItem item) { - if (item == null) - { - throw new ArgumentNullException("item"); - } - return item.Id.ToString("N"); } - /// - /// Converts a UserItemData to a DTOUserItemData - /// - /// The data. - /// DtoUserItemData. - /// - public UserItemDataDto GetUserItemDataDto(UserItemData data) - { - if (data == null) - { - throw new ArgumentNullException("data"); - } - - return new UserItemDataDto - { - IsFavorite = data.IsFavorite, - Likes = data.Likes, - PlaybackPositionTicks = data.PlaybackPositionTicks, - PlayCount = data.PlayCount, - Rating = data.Rating, - Played = data.Played, - LastPlayedDate = data.LastPlayedDate, - Key = data.Key - }; - } private void SetBookProperties(BaseItemDto dto, Book item) { dto.SeriesName = item.SeriesName; } private void SetPhotoProperties(BaseItemDto dto, Photo item) { - dto.Width = item.Width; - dto.Height = item.Height; dto.CameraMake = item.CameraMake; dto.CameraModel = item.CameraModel; dto.Software = item.Software; @@ -670,7 +495,7 @@ namespace Emby.Server.Implementations.Dto if (album != null) { dto.Album = album.Name; - dto.AlbumId = album.Id.ToString("N"); + dto.AlbumId = album.Id; } } @@ -688,7 +513,7 @@ namespace Emby.Server.Implementations.Dto if (parentAlbumIds.Count > 0) { - dto.AlbumId = parentAlbumIds[0].ToString("N"); + dto.AlbumId = parentAlbumIds[0]; } } @@ -835,11 +660,11 @@ namespace Emby.Server.Implementations.Dto private void AttachStudios(BaseItemDto dto, BaseItem item) { dto.Studios = item.Studios - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new NameIdPair + .Where(i => !string.IsNullOrEmpty(i)) + .Select(i => new NameGuidPair { Name = i, - Id = _libraryManager.GetStudioId(i).ToString("N") + Id = _libraryManager.GetStudioId(i) }) .ToArray(); } @@ -847,8 +672,8 @@ namespace Emby.Server.Implementations.Dto private void AttachGenreItems(BaseItemDto dto, BaseItem item) { dto.GenreItems = item.Genres - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new NameIdPair + .Where(i => !string.IsNullOrEmpty(i)) + .Select(i => new NameGuidPair { Name = i, Id = GetGenreId(i, item) @@ -856,53 +681,19 @@ namespace Emby.Server.Implementations.Dto .ToArray(); } - private string GetGenreId(string name, BaseItem owner) + private Guid GetGenreId(string name, BaseItem owner) { if (owner is IHasMusicGenres) { - return _libraryManager.GetMusicGenreId(name).ToString("N"); + return _libraryManager.GetMusicGenreId(name); } if (owner is Game || owner is GameSystem) { - return _libraryManager.GetGameGenreId(name).ToString("N"); + return _libraryManager.GetGameGenreId(name); } - return _libraryManager.GetGenreId(name).ToString("N"); - } - - /// - /// Gets the chapter info dto. - /// - /// The chapter info. - /// The item. - /// ChapterInfoDto. - private ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item) - { - var dto = new ChapterInfoDto - { - Name = chapterInfo.Name, - StartPositionTicks = chapterInfo.StartPositionTicks - }; - - if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) - { - dto.ImageTag = GetImageCacheTag(item, new ItemImageInfo - { - Path = chapterInfo.ImagePath, - Type = ImageType.Chapter, - DateModified = chapterInfo.ImageDateModified - }); - } - - return dto; - } - - public List GetChapterInfoDtos(BaseItem item) - { - return _itemRepo.GetChapters(item.Id) - .Select(c => GetChapterInfoDto(c, item)) - .ToList(); + return _libraryManager.GetGenreId(name); } /// @@ -914,14 +705,12 @@ namespace Emby.Server.Implementations.Dto /// The options. private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem owner, DtoOptions options) { - var fields = options.Fields; - - if (fields.Contains(ItemFields.DateCreated)) + if (options.ContainsField(ItemFields.DateCreated)) { dto.DateCreated = item.DateCreated; } - if (fields.Contains(ItemFields.Settings)) + if (options.ContainsField(ItemFields.Settings)) { dto.LockedFields = item.LockedFields; dto.LockData = item.IsLocked; @@ -931,17 +720,12 @@ namespace Emby.Server.Implementations.Dto dto.EndDate = item.EndDate; - if (fields.Contains(ItemFields.HomePageUrl)) - { - dto.HomePageUrl = item.HomePageUrl; - } - - if (fields.Contains(ItemFields.ExternalUrls)) + if (options.ContainsField(ItemFields.ExternalUrls)) { dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray(); } - if (fields.Contains(ItemFields.Tags)) + if (options.ContainsField(ItemFields.Tags)) { dto.Tags = item.Tags; } @@ -958,7 +742,7 @@ namespace Emby.Server.Implementations.Dto dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList()); } - if (fields.Contains(ItemFields.ScreenshotImageTags)) + if (options.ContainsField(ItemFields.ScreenshotImageTags)) { var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); if (screenshotLimit > 0) @@ -967,7 +751,7 @@ namespace Emby.Server.Implementations.Dto } } - if (fields.Contains(ItemFields.Genres)) + if (options.ContainsField(ItemFields.Genres)) { dto.Genres = item.Genres; AttachGenreItems(dto, item); @@ -994,7 +778,7 @@ namespace Emby.Server.Implementations.Dto } } - dto.Id = GetDtoId(item); + dto.Id = item.Id; dto.IndexNumber = item.IndexNumber; dto.ParentIndexNumber = item.ParentIndexNumber; @@ -1014,13 +798,9 @@ namespace Emby.Server.Implementations.Dto dto.LocationType = item.LocationType; } - if (item.IsHD.HasValue && item.IsHD.Value) - { - dto.IsHD = item.IsHD; - } dto.Audio = item.Audio; - if (fields.Contains(ItemFields.Settings)) + if (options.ContainsField(ItemFields.Settings)) { dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode; dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage; @@ -1028,90 +808,83 @@ namespace Emby.Server.Implementations.Dto dto.CriticRating = item.CriticRating; - var hasTrailers = item as IHasTrailers; - if (hasTrailers != null) - { - dto.LocalTrailerCount = hasTrailers.GetTrailerIds().Count; - } - var hasDisplayOrder = item as IHasDisplayOrder; if (hasDisplayOrder != null) { dto.DisplayOrder = hasDisplayOrder.DisplayOrder; } - var userView = item as UserView; - if (userView != null) + var hasCollectionType = item as IHasCollectionType; + if (hasCollectionType != null) { - dto.CollectionType = userView.ViewType; + dto.CollectionType = hasCollectionType.CollectionType; } - if (fields.Contains(ItemFields.RemoteTrailers)) + if (options.ContainsField(ItemFields.RemoteTrailers)) { - dto.RemoteTrailers = hasTrailers != null ? - hasTrailers.RemoteTrailers : - new MediaUrl[] { }; + dto.RemoteTrailers = item.RemoteTrailers; } dto.Name = item.Name; dto.OfficialRating = item.OfficialRating; - if (fields.Contains(ItemFields.Overview)) + if (options.ContainsField(ItemFields.Overview)) { dto.Overview = item.Overview; } - if (fields.Contains(ItemFields.OriginalTitle)) + if (options.ContainsField(ItemFields.OriginalTitle)) { dto.OriginalTitle = item.OriginalTitle; } - if (fields.Contains(ItemFields.ParentId)) + if (options.ContainsField(ItemFields.ParentId)) { - var displayParentId = item.DisplayParentId; - if (displayParentId.HasValue) - { - dto.ParentId = displayParentId.Value.ToString("N"); - } + dto.ParentId = item.DisplayParentId; } AddInheritedImages(dto, item, options, owner); - if (fields.Contains(ItemFields.Path)) + if (options.ContainsField(ItemFields.Path)) { dto.Path = GetMappedPath(item, owner); } + if (options.ContainsField(ItemFields.EnableMediaSourceDisplay)) + { + dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay; + } + dto.PremiereDate = item.PremiereDate; dto.ProductionYear = item.ProductionYear; - if (fields.Contains(ItemFields.ProviderIds)) + if (options.ContainsField(ItemFields.ProviderIds)) { dto.ProviderIds = item.ProviderIds; } dto.RunTimeTicks = item.RunTimeTicks; - if (fields.Contains(ItemFields.SortName)) + if (options.ContainsField(ItemFields.SortName)) { dto.SortName = item.SortName; } - if (fields.Contains(ItemFields.CustomRating)) + if (options.ContainsField(ItemFields.CustomRating)) { dto.CustomRating = item.CustomRating; } - if (fields.Contains(ItemFields.Taglines)) + if (options.ContainsField(ItemFields.Taglines)) { - if (!string.IsNullOrWhiteSpace(item.Tagline)) + if (!string.IsNullOrEmpty(item.Tagline)) { dto.Taglines = new string[] { item.Tagline }; } if (dto.Taglines == null) { - dto.Taglines = new string[] { }; + dto.Taglines = Array.Empty(); } } @@ -1141,12 +914,12 @@ namespace Emby.Server.Implementations.Dto if (albumParent != null) { - dto.AlbumId = GetDtoId(albumParent); + dto.AlbumId = albumParent.Id; dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); } - //if (fields.Contains(ItemFields.MediaSourceCount)) + //if (options.ContainsField(ItemFields.MediaSourceCount)) //{ // Songs always have one //} @@ -1182,7 +955,7 @@ namespace Emby.Server.Implementations.Dto .Select(i => { // This should not be necessary but we're seeing some cases of it - if (string.IsNullOrWhiteSpace(i)) + if (string.IsNullOrEmpty(i)) { return null; } @@ -1193,10 +966,10 @@ namespace Emby.Server.Implementations.Dto }); if (artist != null) { - return new NameIdPair + return new NameGuidPair { Name = artist.Name, - Id = artist.Id.ToString("N") + Id = artist.Id }; } @@ -1233,7 +1006,7 @@ namespace Emby.Server.Implementations.Dto .Select(i => { // This should not be necessary but we're seeing some cases of it - if (string.IsNullOrWhiteSpace(i)) + if (string.IsNullOrEmpty(i)) { return null; } @@ -1244,10 +1017,10 @@ namespace Emby.Server.Implementations.Dto }); if (artist != null) { - return new NameIdPair + return new NameGuidPair { Name = artist.Name, - Id = artist.Id.ToString("N") + Id = artist.Id }; } @@ -1274,7 +1047,7 @@ namespace Emby.Server.Implementations.Dto dto.PartCount = video.AdditionalParts.Length + 1; } - if (fields.Contains(ItemFields.MediaSourceCount)) + if (options.ContainsField(ItemFields.MediaSourceCount)) { var mediaSourceCount = video.MediaSourceCount; if (mediaSourceCount != 1) @@ -1283,9 +1056,9 @@ namespace Emby.Server.Implementations.Dto } } - if (fields.Contains(ItemFields.Chapters)) + if (options.ContainsField(ItemFields.Chapters)) { - dto.Chapters = GetChapterInfoDtos(item); + dto.Chapters = _itemRepo.GetChapters(item); } if (video.ExtraType.HasValue) @@ -1294,7 +1067,7 @@ namespace Emby.Server.Implementations.Dto } } - if (fields.Contains(ItemFields.MediaStreams)) + if (options.ContainsField(ItemFields.MediaStreams)) { // Add VideoInfo var iHasMediaSources = item as IHasMediaSources; @@ -1303,30 +1076,48 @@ namespace Emby.Server.Implementations.Dto { MediaStream[] mediaStreams; - if (dto.MediaSources != null && dto.MediaSources.Count > 0) + if (dto.MediaSources != null && dto.MediaSources.Length > 0) { - mediaStreams = dto.MediaSources.Where(i => new Guid(i.Id) == item.Id) - .SelectMany(i => i.MediaStreams) - .ToArray(); + if (item.SourceType == SourceType.Channel) + { + mediaStreams = dto.MediaSources[0].MediaStreams.ToArray(); + } + else + { + mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, item.Id.ToString("N"), StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.MediaStreams) + .ToArray(); + } } else { - mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams.ToArray(); + mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true).First().MediaStreams.ToArray(); } dto.MediaStreams = mediaStreams; } } - var hasSpecialFeatures = item as IHasSpecialFeatures; - if (hasSpecialFeatures != null) - { - var specialFeatureCount = hasSpecialFeatures.SpecialFeatureIds.Length; + BaseItem[] allExtras = null; - if (specialFeatureCount > 0) + if (options.ContainsField(ItemFields.SpecialFeatureCount)) + { + if (allExtras == null) { - dto.SpecialFeatureCount = specialFeatureCount; + allExtras = item.GetExtras().ToArray(); } + + dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value)); + } + + if (options.ContainsField(ItemFields.LocalTrailerCount)) + { + if (allExtras == null) + { + allExtras = item.GetExtras().ToArray(); + } + + dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer); } // Add EpisodeInfo @@ -1336,37 +1127,20 @@ namespace Emby.Server.Implementations.Dto dto.IndexNumberEnd = episode.IndexNumberEnd; dto.SeriesName = episode.SeriesName; - if (fields.Contains(ItemFields.AlternateEpisodeNumbers)) - { - dto.DvdSeasonNumber = episode.DvdSeasonNumber; - dto.DvdEpisodeNumber = episode.DvdEpisodeNumber; - dto.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber; - } - - if (fields.Contains(ItemFields.SpecialEpisodeNumbers)) + if (options.ContainsField(ItemFields.SpecialEpisodeNumbers)) { dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber; dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber; dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber; } - var seasonId = episode.SeasonId; - if (seasonId.HasValue) - { - dto.SeasonId = seasonId.Value.ToString("N"); - } - dto.SeasonName = episode.SeasonName; - - var seriesId = episode.SeriesId; - if (seriesId.HasValue) - { - dto.SeriesId = seriesId.Value.ToString("N"); - } + dto.SeasonId = episode.SeasonId; + dto.SeriesId = episode.SeriesId; Series episodeSeries = null; - //if (fields.Contains(ItemFields.SeriesPrimaryImage)) + //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) @@ -1375,7 +1149,7 @@ namespace Emby.Server.Implementations.Dto } } - if (fields.Contains(ItemFields.SeriesStudio)) + if (options.ContainsField(ItemFields.SeriesStudio)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) @@ -1399,16 +1173,11 @@ namespace Emby.Server.Implementations.Dto if (season != null) { dto.SeriesName = season.SeriesName; - - var seriesId = season.SeriesId; - if (seriesId.HasValue) - { - dto.SeriesId = seriesId.Value.ToString("N"); - } + dto.SeriesId = season.SeriesId; series = null; - if (fields.Contains(ItemFields.SeriesStudio)) + if (options.ContainsField(ItemFields.SeriesStudio)) { series = series ?? season.Series; if (series != null) @@ -1417,7 +1186,7 @@ namespace Emby.Server.Implementations.Dto } } - //if (fields.Contains(ItemFields.SeriesPrimaryImage)) + //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { series = series ?? season.Series; if (series != null) @@ -1453,7 +1222,7 @@ namespace Emby.Server.Implementations.Dto SetBookProperties(dto, book); } - if (fields.Contains(ItemFields.ProductionLocations)) + if (options.ContainsField(ItemFields.ProductionLocations)) { if (item.ProductionLocations.Length > 0 || item is Movie) { @@ -1461,6 +1230,33 @@ namespace Emby.Server.Implementations.Dto } } + if (options.ContainsField(ItemFields.Width)) + { + var width = item.Width; + if (width > 0) + { + dto.Width = width; + } + } + + if (options.ContainsField(ItemFields.Height)) + { + var height = item.Height; + if (height > 0) + { + dto.Height = height; + } + } + + if (options.ContainsField(ItemFields.IsHD)) + { + // Compatibility + if (item.IsHD) + { + dto.IsHD = true; + } + } + var photo = item as Photo; if (photo != null) { @@ -1469,7 +1265,7 @@ namespace Emby.Server.Implementations.Dto dto.ChannelId = item.ChannelId; - if (item.SourceType == SourceType.Channel && !string.IsNullOrWhiteSpace(item.ChannelId)) + if (item.SourceType == SourceType.Channel) { var channel = _libraryManager.GetItemById(item.ChannelId); if (channel != null) @@ -1491,7 +1287,7 @@ namespace Emby.Server.Implementations.Dto } } - var parent = currentItem.DisplayParent ?? (currentItem.IsOwnedItem ? currentItem.GetOwner() : currentItem.GetParent()); + var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent(); if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel)) { @@ -1592,9 +1388,7 @@ namespace Emby.Server.Implementations.Dto { var path = item.Path; - var locationType = item.LocationType; - - if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) + if (item.IsFileProtocol) { path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item); } @@ -1628,15 +1422,15 @@ namespace Emby.Server.Implementations.Dto var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio(); - if (defaultAspectRatio.HasValue) + if (defaultAspectRatio > 0) { - if (supportedEnhancers.Count == 0) + if (supportedEnhancers.Length == 0) { - return defaultAspectRatio.Value; + return defaultAspectRatio; } double dummyWidth = 200; - double dummyHeight = dummyWidth / defaultAspectRatio.Value; + double dummyHeight = dummyWidth / defaultAspectRatio; size = new ImageSize(dummyWidth, dummyHeight); } else diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index cef37910ee..66dd80dbe3 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -1,679 +1,44 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Debug - AnyCPU - {E383961B-9356-4D5D-8233-9A1079D03055} - Library - Properties - Emby.Server.Implementations - Emby.Server.Implementations - 512 - - v4.6 + netcoreapp2.1 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - Properties\SharedVersion.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - {805844ab-e92f-45e6-9d99-4f6d48d129a5} - Emby.Dlna - - - {08fff49b-f175-4807-a2b5-73b0ebd9f716} - Emby.Drawing - - - {89ab4548-770d-41fd-a891-8daff44f452c} - Emby.Photos - - - {4fd51ac5-2c16-4308-a993-c3a84f3b4582} - MediaBrowser.Api - - - {9142eefa-7570-41e1-bfcc-468bb571af2f} - MediaBrowser.Common - - - {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} - MediaBrowser.Controller - - - {7ef9f3e0-697d-42f3-a08f-19deb5f84392} - MediaBrowser.LocalMetadata - - - {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} - MediaBrowser.Model - - - {442b5058-dcaf-4263-bb6a-f21e31120a1b} - MediaBrowser.Providers - - - {5624b7b5-b5a7-41d8-9f10-cc5611109619} - MediaBrowser.WebDashboard - - - {23499896-b135-4527-8574-c26e926ea99e} - MediaBrowser.XbmcMetadata - - - {cb7f2326-6497-4a3d-ba03-48513b17a7be} - Mono.Nat - - - {4a4402d4-e910-443b-b8fc-2c18286a2ca0} - OpenSubtitlesHandler - - - {1d74413b-e7cf-455b-b021-f52bdf881542} - SocketHttpListener - - - ..\ThirdParty\emby\Emby.Naming.dll - - - ..\ThirdParty\emby\Emby.Server.MediaEncoding.dll - - - ..\packages\Emby.XmlTv.1.0.10\lib\portable-net45+netstandard2.0+win8\Emby.XmlTv.dll - - - ..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll - True - - - ..\packages\SharpCompress.0.18.2\lib\net45\SharpCompress.dll - - - ..\packages\SimpleInjector.4.0.12\lib\net45\SimpleInjector.dll - - - ..\packages\SQLitePCL.pretty.1.1.0\lib\portable-net45+netcore45+wpa81+wp8\SQLitePCL.pretty.dll - True - - - - - ..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll - - - - - - - - - - - - - @@ -710,128 +75,113 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\ThirdParty\emby\Emby.Server.MediaEncoding.dll + + + + diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index c2cee00c80..561f5ee12e 100644 --- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -112,7 +112,6 @@ namespace Emby.Server.Implementations.EntryPoints _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; DisposeTimer(); - GC.SuppressFinalize(this); } private void DisposeTimer() diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 903bb0ff46..6801b2823b 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -26,9 +26,10 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IDeviceDiscovery _deviceDiscovery; private ITimer _timer; - private bool _isStarted; private readonly ITimerFactory _timerFactory; + private NatManager _natManager; + public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory) { _logger = logmanager.GetLogger("PortMapper"); @@ -37,6 +38,12 @@ namespace Emby.Server.Implementations.EntryPoints _deviceDiscovery = deviceDiscovery; _httpClient = httpClient; _timerFactory = timerFactory; + _config.ConfigurationUpdated += _config_ConfigurationUpdated1; + } + + private void _config_ConfigurationUpdated1(object sender, EventArgs e) + { + _config_ConfigurationUpdated(sender, e); } private string _lastConfigIdentifier; @@ -49,8 +56,8 @@ namespace Emby.Server.Implementations.EntryPoints values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture)); values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture)); values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture)); - values.Add((config.EnableHttps || config.RequireHttps).ToString()); values.Add(_appHost.EnableHttps.ToString()); + values.Add((config.EnableRemoteAccess).ToString()); return string.Join("|", values.ToArray(values.Count)); } @@ -59,10 +66,7 @@ namespace Emby.Server.Implementations.EntryPoints { if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) { - if (_isStarted) - { - DisposeNat(); - } + DisposeNat(); Run(); } @@ -70,10 +74,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Run() { - NatUtility.Logger = _logger; - NatUtility.HttpClient = _httpClient; - - if (_config.Configuration.EnableUPnP) + if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess) { Start(); } @@ -85,26 +86,18 @@ namespace Emby.Server.Implementations.EntryPoints private void Start() { _logger.Debug("Starting NAT discovery"); - NatUtility.EnabledProtocols = new List + if (_natManager == null) { - NatProtocol.Pmp - }; - NatUtility.DeviceFound += NatUtility_DeviceFound; - - // Mono.Nat does never rise this event. The event is there however it is useless. - // You could remove it with no risk. - NatUtility.DeviceLost += NatUtility_DeviceLost; - - - NatUtility.StartDiscovery(); + _natManager = new NatManager(_logger, _httpClient); + _natManager.DeviceFound += NatUtility_DeviceFound; + _natManager.StartDiscovery(); + } _timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; _lastConfigIdentifier = GetConfigIdentifier(); - - _isStarted = true; } private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) @@ -182,8 +175,17 @@ namespace Emby.Server.Implementations.EntryPoints return; } - _logger.Debug("Calling Nat.Handle on " + identifier); - NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp); + // This should never happen, but the Handle method will throw ArgumentNullException if it does + if (localAddress == null) + { + return; + } + + var natManager = _natManager; + if (natManager != null) + { + await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false); + } } } @@ -209,19 +211,11 @@ namespace Emby.Server.Implementations.EntryPoints try { var device = e.Device; - _logger.Debug("NAT device found: {0}", device.LocalAddress.ToString()); CreateRules(device); } catch { - // I think it could be a good idea to log the exception because - // you are using permanent portmapping here (never expire) and that means that next time - // CreatePortMap is invoked it can fails with a 718-ConflictInMappingEntry or not. That depends - // on the router's upnp implementation (specs says it should fail however some routers don't do it) - // It also can fail with others like 727-ExternalPortOnlySupportsWildcard, 728-NoPortMapsAvailable - // and those errors (upnp errors) could be useful for diagnosting. - // Commenting out because users are reporting problems out of our control //_logger.ErrorException("Error creating port forwarding rules", ex); } @@ -238,14 +232,15 @@ namespace Emby.Server.Implementations.EntryPoints // On some systems the device discovered event seems to fire repeatedly // This check will help ensure we're not trying to port map the same device over and over + var address = device.LocalAddress; - var address = device.LocalAddress.ToString(); + var addressString = address.ToString(); lock (_createdRules) { - if (!_createdRules.Contains(address)) + if (!_createdRules.Contains(addressString)) { - _createdRules.Add(address); + _createdRules.Add(addressString); } else { @@ -253,41 +248,32 @@ namespace Emby.Server.Implementations.EntryPoints } } - var success = await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false); - - if (success) - { - await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false); - } - } - - private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort) - { - _logger.Debug("Creating port map on port {0}", privatePort); - try { - await device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) - { - Description = _appHost.Name - - }).ConfigureAwait(false); - - return true; + await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false); } catch (Exception ex) { - _logger.Error("Error creating port map: " + ex.Message); + return; + } - return false; + try + { + await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false); + } + catch (Exception ex) + { } } - // As I said before, this method will be never invoked. You can remove it. - void NatUtility_DeviceLost(object sender, DeviceEventArgs e) + private Task CreatePortMap(INatDevice device, int privatePort, int publicPort) { - var device = e.Device; - _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString()); + _logger.Debug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString()); + + return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) + { + Description = _appHost.Name + }); } private bool _disposed = false; @@ -295,7 +281,6 @@ namespace Emby.Server.Implementations.EntryPoints { _disposed = true; DisposeNat(); - GC.SuppressFinalize(this); } private void DisposeNat() @@ -310,27 +295,24 @@ namespace Emby.Server.Implementations.EntryPoints _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; - try + var natManager = _natManager; + + if (natManager != null) { - // This is not a significant improvement - NatUtility.StopDiscovery(); - NatUtility.DeviceFound -= NatUtility_DeviceFound; - NatUtility.DeviceLost -= NatUtility_DeviceLost; - } - // Statements in try-block will no fail because StopDiscovery is a one-line - // method that was no chances to fail. - // public static void StopDiscovery () - // { - // searching.Reset(); - // } - // IMO you could remove the catch-block - catch (Exception ex) - { - _logger.ErrorException("Error stopping NAT Discovery", ex); - } - finally - { - _isStarted = false; + _natManager = null; + + using (natManager) + { + try + { + natManager.StopDiscovery(); + natManager.DeviceFound -= NatUtility_DeviceFound; + } + catch (Exception ex) + { + _logger.ErrorException("Error stopping NAT Discovery", ex); + } + } } } } diff --git a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs index 221580681a..8ae85e3909 100644 --- a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs +++ b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs @@ -60,7 +60,6 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 0e771cbecc..9a2ae34bcb 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The library update duration /// - private const int LibraryUpdateDuration = 5000; + private const int LibraryUpdateDuration = 30000; private readonly IProviderManager _providerManager; @@ -315,41 +315,39 @@ namespace Emby.Server.Implementations.EntryPoints /// The cancellation token. private async void SendChangeNotifications(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, CancellationToken cancellationToken) { - foreach (var user in _userManager.Users.ToList()) + var userIds = _sessionManager.Sessions + .Select(i => i.UserId) + .Where(i => !i.Equals(Guid.Empty)) + .Distinct() + .ToArray(); + + foreach (var userId in userIds) { - var id = user.Id; - var userSessions = _sessionManager.Sessions - .Where(u => u.UserId.HasValue && u.UserId.Value == id && u.SessionController != null && u.IsActive) - .ToList(); + LibraryUpdateInfo info; - if (userSessions.Count > 0) + try { - LibraryUpdateInfo info; - - try - { - info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, - foldersRemovedFrom, id); - } - catch (Exception ex) - { - _logger.ErrorException("Error in GetLibraryUpdateInfo", ex); - return; - } - - foreach (var userSession in userSessions) - { - try - { - await userSession.SessionController.SendLibraryUpdateInfo(info, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending LibraryChanged message", ex); - } - } + info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId); + } + catch (Exception ex) + { + _logger.ErrorException("Error in GetLibraryUpdateInfo", ex); + return; } + if (info.IsEmpty) + { + continue; + } + + try + { + await _sessionManager.SendMessageToUserSessions(new List { userId }, "LibraryChanged", info, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending LibraryChanged message", ex); + } } } @@ -391,7 +389,7 @@ namespace Emby.Server.Implementations.EntryPoints private bool FilterItem(BaseItem item) { - if (!item.IsFolder && item.LocationType == LocationType.Virtual) + if (!item.IsFolder && !item.HasPathProtocol) { return false; } @@ -440,7 +438,7 @@ namespace Emby.Server.Implementations.EntryPoints // If the physical root changed, return the user root if (item is AggregateFolder) { - return new[] { user.RootFolder as T }; + return new[] { _libraryManager.GetUserRootFolder() as T }; } // Return it only if it's in the user's library @@ -458,7 +456,6 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// @@ -474,10 +471,14 @@ namespace Emby.Server.Implementations.EntryPoints LibraryUpdateTimer.Dispose(); LibraryUpdateTimer = null; } - + _libraryManager.ItemAdded -= libraryManager_ItemAdded; _libraryManager.ItemUpdated -= libraryManager_ItemUpdated; _libraryManager.ItemRemoved -= libraryManager_ItemRemoved; + + _providerManager.RefreshCompleted -= _providerManager_RefreshCompleted; + _providerManager.RefreshStarted -= _providerManager_RefreshStarted; + _providerManager.RefreshProgress -= _providerManager_RefreshProgress; } } } diff --git a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs deleted file mode 100644 index 21e075cf5c..0000000000 --- a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs +++ /dev/null @@ -1,74 +0,0 @@ -using MediaBrowser.Common.Security; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Logging; -using System; -using System.Threading.Tasks; -using MediaBrowser.Model.Threading; - -namespace Emby.Server.Implementations.EntryPoints -{ - /// - /// Class LoadRegistrations - /// - public class LoadRegistrations : IServerEntryPoint - { - /// - /// The _security manager - /// - private readonly ISecurityManager _securityManager; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - private ITimer _timer; - private readonly ITimerFactory _timerFactory; - - /// - /// Initializes a new instance of the class. - /// - /// The security manager. - /// The log manager. - public LoadRegistrations(ISecurityManager securityManager, ILogManager logManager, ITimerFactory timerFactory) - { - _securityManager = securityManager; - _timerFactory = timerFactory; - - _logger = logManager.GetLogger("Registration Loader"); - } - - /// - /// Runs this instance. - /// - public void Run() - { - _timer = _timerFactory.Create(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12)); - } - - private async Task LoadAllRegistrations() - { - try - { - await _securityManager.LoadAllRegistrationInfo().ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error loading registration info", ex); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - GC.SuppressFinalize(this); - } - } -} diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index f73b40b462..d41d76c6b8 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -54,11 +54,15 @@ namespace Emby.Server.Implementations.EntryPoints private async void SendMessage(string name, TimerEventInfo info) { - var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id.ToString("N")).ToList(); + var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); try { await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None); + } + catch (ObjectDisposedException) + { + } catch (Exception ex) { @@ -72,7 +76,6 @@ namespace Emby.Server.Implementations.EntryPoints _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 514321e209..e5748989e7 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -21,11 +21,6 @@ namespace Emby.Server.Implementations.EntryPoints /// public class ServerEventNotifier : IServerEntryPoint { - /// - /// The _server manager - /// - private readonly IServerManager _serverManager; - /// /// The _user manager /// @@ -47,23 +42,21 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ITaskManager _taskManager; private readonly ISessionManager _sessionManager; - private readonly ISyncManager _syncManager; - public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, ISessionManager sessionManager, ISyncManager syncManager) + public ServerEventNotifier(IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, ISessionManager sessionManager) { - _serverManager = serverManager; _userManager = userManager; _installationManager = installationManager; _appHost = appHost; _taskManager = taskManager; _sessionManager = sessionManager; - _syncManager = syncManager; } public void Run() { _userManager.UserDeleted += userManager_UserDeleted; _userManager.UserUpdated += userManager_UserUpdated; + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; _appHost.HasPendingRestartChanged += kernel_HasPendingRestartChanged; @@ -75,43 +68,31 @@ namespace Emby.Server.Implementations.EntryPoints _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; _taskManager.TaskCompleted += _taskManager_TaskCompleted; - _syncManager.SyncJobCreated += _syncManager_SyncJobCreated; - _syncManager.SyncJobCancelled += _syncManager_SyncJobCancelled; - } - - void _syncManager_SyncJobCancelled(object sender, GenericEventArgs e) - { - _sessionManager.SendMessageToUserDeviceSessions(e.Argument.TargetId, "SyncJobCancelled", e.Argument, CancellationToken.None); - } - - void _syncManager_SyncJobCreated(object sender, GenericEventArgs e) - { - _sessionManager.SendMessageToUserDeviceSessions(e.Argument.Job.TargetId, "SyncJobCreated", e.Argument, CancellationToken.None); } void _installationManager_PackageInstalling(object sender, InstallationEventArgs e) { - _serverManager.SendWebSocketMessage("PackageInstalling", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo); } void _installationManager_PackageInstallationCancelled(object sender, InstallationEventArgs e) { - _serverManager.SendWebSocketMessage("PackageInstallationCancelled", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo); } void _installationManager_PackageInstallationCompleted(object sender, InstallationEventArgs e) { - _serverManager.SendWebSocketMessage("PackageInstallationCompleted", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo); } void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) { - _serverManager.SendWebSocketMessage("PackageInstallationFailed", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo); } void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) { - _serverManager.SendWebSocketMessage("ScheduledTaskEnded", e.Result); + SendMessageToAdminSessions("ScheduledTaskEnded", e.Result); } /// @@ -121,7 +102,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The e. void InstallationManager_PluginUninstalled(object sender, GenericEventArgs e) { - _serverManager.SendWebSocketMessage("PluginUninstalled", e.Argument.GetPluginInfo()); + SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()); } /// @@ -156,6 +137,13 @@ namespace Emby.Server.Implementations.EntryPoints SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N")); } + void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + { + var dto = _userManager.GetUserDto(e.Argument); + + SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto); + } + void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); @@ -163,9 +151,36 @@ namespace Emby.Server.Implementations.EntryPoints SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto); } + private async void SendMessageToAdminSessions(string name, T data) + { + try + { + await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None); + } + catch (ObjectDisposedException) + { + + } + catch (Exception) + { + //Logger.ErrorException("Error sending message", ex); + } + } + private async void SendMessageToUserSession(User user, string name, T data) { - await _sessionManager.SendMessageToUserSessions(new List { user.Id.ToString("N") }, name, data, CancellationToken.None); + try + { + await _sessionManager.SendMessageToUserSessions(new List { user.Id }, name, data, CancellationToken.None); + } + catch (ObjectDisposedException) + { + + } + catch (Exception) + { + //Logger.ErrorException("Error sending message", ex); + } } /// @@ -174,7 +189,6 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// @@ -187,6 +201,7 @@ namespace Emby.Server.Implementations.EntryPoints { _userManager.UserDeleted -= userManager_UserDeleted; _userManager.UserUpdated -= userManager_UserUpdated; + _userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated; _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; _installationManager.PluginUninstalled -= InstallationManager_PluginUninstalled; @@ -196,8 +211,6 @@ namespace Emby.Server.Implementations.EntryPoints _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; _appHost.HasPendingRestartChanged -= kernel_HasPendingRestartChanged; - _syncManager.SyncJobCreated -= _syncManager_SyncJobCreated; - _syncManager.SyncJobCancelled -= _syncManager_SyncJobCancelled; } } } diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 103b4b321e..6d73f98ad3 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -1,5 +1,4 @@ -using System; -using Emby.Server.Implementations.Browser; +using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; @@ -40,17 +39,17 @@ namespace Emby.Server.Implementations.EntryPoints return; } - if (_appHost.IsFirstRun) + if (!_config.Configuration.IsStartupWizardCompleted) { - BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost); + BrowserLauncher.OpenWebApp(_appHost); } - else if (_config.Configuration.IsStartupWizardCompleted && _config.Configuration.AutoRunWebApp) + else if (_config.Configuration.AutoRunWebApp) { var options = ((ApplicationHost)_appHost).StartupOptions; if (!options.ContainsOption("-noautorunwebapp")) { - BrowserLauncher.OpenDashboardPage("index.html", _appHost); + BrowserLauncher.OpenWebApp(_appHost); } } } @@ -60,7 +59,6 @@ namespace Emby.Server.Implementations.EntryPoints /// public void Dispose() { - GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs index 08f3edb3d5..e27de89673 100644 --- a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs +++ b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs @@ -34,7 +34,6 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _systemEvents.SystemShutdown -= _systemEvents_SystemShutdown; - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index d04df0d2b2..5edc5fadef 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -45,6 +45,9 @@ namespace Emby.Server.Implementations.EntryPoints /// public void Run() { + // ToDo: Fix This + return; + var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory); try @@ -65,7 +68,6 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs index 11e806b0cf..97feb32c08 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; @@ -61,17 +60,29 @@ namespace Emby.Server.Implementations.EntryPoints var key = string.Join("_", keys.ToArray(keys.Count)).GetMD5(); - _apps.GetOrAdd(key, guid => GetNewClientInfo(session)); + ClientInfo info; + if (!_apps.TryGetValue(key, out info)) + { + info = new ClientInfo + { + AppName = session.Client, + AppVersion = session.ApplicationVersion, + DeviceName = session.DeviceName, + DeviceId = session.DeviceId + }; + + _apps[key] = info; + + if (_config.Configuration.EnableAnonymousUsageReporting) + { + Task.Run(() => ReportNewSession(info)); + } + } } } - private async void ReportNewSession(ClientInfo client) + private async Task ReportNewSession(ClientInfo client) { - if (!_config.Configuration.EnableAnonymousUsageReporting) - { - return; - } - try { await new UsageReporter(_applicationHost, _httpClient, _logger) @@ -80,25 +91,10 @@ namespace Emby.Server.Implementations.EntryPoints } catch (Exception ex) { - _logger.ErrorException("Error sending anonymous usage statistics.", ex); + //_logger.ErrorException("Error sending anonymous usage statistics.", ex); } } - private ClientInfo GetNewClientInfo(SessionInfo session) - { - var info = new ClientInfo - { - AppName = session.Client, - AppVersion = session.ApplicationVersion, - DeviceName = session.DeviceName, - DeviceId = session.DeviceId - }; - - ReportNewSession(info); - - return info; - } - public async void Run() { await Task.Delay(5000).ConfigureAwait(false); @@ -123,14 +119,13 @@ namespace Emby.Server.Implementations.EntryPoints } catch (Exception ex) { - _logger.ErrorException("Error sending anonymous usage statistics.", ex); + //_logger.ErrorException("Error sending anonymous usage statistics.", ex); } } public void Dispose() { _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs index deee8d64bd..86b335b773 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.EntryPoints public async Task ReportAppUsage(ClientInfo app, CancellationToken cancellationToken) { - if (string.IsNullOrWhiteSpace(app.DeviceId)) + if (string.IsNullOrEmpty(app.DeviceId)) { throw new ArgumentException("Client info must have a device Id"); } diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 13c72bf3c7..36e29e46ae 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ITimerFactory _timerFactory; private const int UpdateDuration = 500; - private readonly Dictionary> _changedItems = new Dictionary>(); + private readonly Dictionary> _changedItems = new Dictionary>(); public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager, ITimerFactory timerFactory) { @@ -62,22 +62,22 @@ namespace Emby.Server.Implementations.EntryPoints UpdateTimer.Change(UpdateDuration, Timeout.Infinite); } - List keys; + List keys; if (!_changedItems.TryGetValue(e.UserId, out keys)) { - keys = new List(); + keys = new List(); _changedItems[e.UserId] = keys; } keys.Add(e.Item); - var baseItem = e.Item as BaseItem; + var baseItem = e.Item; // Go up one level for indicators if (baseItem != null) { - var parent = baseItem.IsOwnedItem ? baseItem.GetOwner() : baseItem.GetParent(); + var parent = baseItem.GetOwner() ?? baseItem.GetParent(); if (parent != null) { @@ -105,52 +105,43 @@ namespace Emby.Server.Implementations.EntryPoints } } - private async Task SendNotifications(IEnumerable>> changes, CancellationToken cancellationToken) + private async Task SendNotifications(List>> changes, CancellationToken cancellationToken) { foreach (var pair in changes) { - var userId = pair.Key; - var userSessions = _sessionManager.Sessions - .Where(u => u.ContainsUser(userId) && u.SessionController != null && u.IsActive) - .ToList(); - - if (userSessions.Count > 0) - { - var user = _userManager.GetUserById(userId); - - var dtoList = pair.Value - .DistinctBy(i => i.Id) - .Select(i => - { - var dto = _userDataManager.GetUserDataDto(i, user); - dto.ItemId = i.Id.ToString("N"); - return dto; - }) - .ToArray(); - - var info = new UserDataChangeInfo - { - UserId = userId.ToString("N"), - - UserDataList = dtoList - }; - - foreach (var userSession in userSessions) - { - try - { - await userSession.SessionController.SendUserDataChangeInfo(info, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending UserDataChanged message", ex); - } - } - } - + await SendNotifications(pair.Key, pair.Value, cancellationToken).ConfigureAwait(false); } } + private Task SendNotifications(Guid userId, List changedItems, CancellationToken cancellationToken) + { + return _sessionManager.SendMessageToUserSessions(new List { userId }, "UserDataChanged", () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); + } + + private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List changedItems) + { + var user = _userManager.GetUserById(userId); + + var dtoList = changedItems + .DistinctBy(i => i.Id) + .Select(i => + { + var dto = _userDataManager.GetUserDataDto(i, user); + dto.ItemId = i.Id.ToString("N"); + return dto; + }) + .ToArray(); + + var userIdString = userId.ToString("N"); + + return new UserDataChangeInfo + { + UserId = userIdString, + + UserDataList = dtoList + }; + } + public void Dispose() { if (UpdateTimer != null) @@ -160,7 +151,6 @@ namespace Emby.Server.Implementations.EntryPoints } _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs index f86279f372..583e93706e 100644 --- a/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs +++ b/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs @@ -82,11 +82,6 @@ namespace Emby.Server.Implementations.EnvironmentInfo return Environment.GetEnvironmentVariable(name); } - public virtual string GetUserId() - { - return null; - } - public string StackTrace { get { return Environment.StackTrace; } diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs index 1d769acec1..a1080a839a 100644 --- a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs +++ b/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs @@ -7,11 +7,9 @@ namespace Emby.Server.Implementations.FFMpeg public string FFMpegFilename { get; set; } public string FFProbeFilename { get; set; } public string ArchiveType { get; set; } - public string[] DownloadUrls { get; set; } public FFMpegInstallInfo() { - DownloadUrls = new string[] { }; Version = "Path"; FFMpegFilename = "ffmpeg"; FFProbeFilename = "ffprobe"; diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs index 9f4cd05fa9..fe1df0953b 100644 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs @@ -6,10 +6,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations; -using Emby.Server.Implementations.FFMpeg; namespace Emby.Server.Implementations.FFMpeg { @@ -32,7 +28,7 @@ namespace Emby.Server.Implementations.FFMpeg _ffmpegInstallInfo = ffmpegInstallInfo; } - public async Task GetFFMpegInfo(StartupOptions options, IProgress progress) + public FFMpegInfo GetFFMpegInfo(StartupOptions options) { var customffMpegPath = options.GetOption("-ffmpeg"); var customffProbePath = options.GetOption("-ffprobe"); @@ -49,8 +45,9 @@ namespace Emby.Server.Implementations.FFMpeg var downloadInfo = _ffmpegInstallInfo; - var prebuiltffmpeg = Path.Combine(_appPaths.ProgramSystemPath, downloadInfo.FFMpegFilename); - var prebuiltffprobe = Path.Combine(_appPaths.ProgramSystemPath, downloadInfo.FFProbeFilename); + var prebuiltFolder = _appPaths.ProgramSystemPath; + var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename); + var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename); if (_fileSystem.FileExists(prebuiltffmpeg) && _fileSystem.FileExists(prebuiltffprobe)) { return new FFMpegInfo @@ -90,11 +87,7 @@ namespace Emby.Server.Implementations.FFMpeg // No older version. Need to download and block until complete if (existingVersion == null) { - var success = await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false); - if (!success) - { - return new FFMpegInfo(); - } + return new FFMpegInfo(); } else { @@ -144,99 +137,5 @@ namespace Emby.Server.Implementations.FFMpeg return null; } - - private async Task DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress progress) - { - foreach (var url in downloadinfo.DownloadUrls) - { - progress.Report(0); - - try - { - var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - CancellationToken = CancellationToken.None, - Progress = progress - - }).ConfigureAwait(false); - - ExtractFFMpeg(downloadinfo, tempFile, directory); - return true; - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading {0}", ex, url); - } - } - return false; - } - - private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder) - { - _logger.Info("Extracting ffmpeg from {0}", tempFile); - - var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString()); - - _fileSystem.CreateDirectory(tempFolder); - - try - { - ExtractArchive(downloadinfo, tempFile, tempFolder); - - var files = _fileSystem.GetFilePaths(tempFolder, true) - .ToList(); - - foreach (var file in files.Where(i => - { - var filename = Path.GetFileName(i); - - return - string.Equals(filename, downloadinfo.FFProbeFilename, StringComparison.OrdinalIgnoreCase) || - string.Equals(filename, downloadinfo.FFMpegFilename, StringComparison.OrdinalIgnoreCase); - })) - { - var targetFile = Path.Combine(targetFolder, Path.GetFileName(file)); - _fileSystem.CopyFile(file, targetFile, true); - SetFilePermissions(targetFile); - } - } - finally - { - DeleteFile(tempFile); - } - } - - private void SetFilePermissions(string path) - { - _fileSystem.SetExecutable(path); - } - - private void ExtractArchive(FFMpegInstallInfo downloadinfo, string archivePath, string targetPath) - { - _logger.Info("Extracting {0} to {1}", archivePath, targetPath); - - if (string.Equals(downloadinfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase)) - { - _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); - } - else if (string.Equals(downloadinfo.ArchiveType, "gz", StringComparison.OrdinalIgnoreCase)) - { - _zipClient.ExtractAllFromTar(archivePath, targetPath, true); - } - } - - private void DeleteFile(string path) - { - try - { - _fileSystem.DeleteFile(path); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting temp file {0}", ex, path); - } - } - } } diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 4a9e417f2e..d53606e878 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -41,13 +41,12 @@ namespace Emby.Server.Implementations.HttpClientManager private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly Func _defaultUserAgentFn; /// /// Initializes a new instance of the class. /// - public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider, Func defaultUserAgentFn) + public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, Func defaultUserAgentFn) { if (appPaths == null) { @@ -60,7 +59,6 @@ namespace Emby.Server.Implementations.HttpClientManager _logger = logger; _fileSystem = fileSystem; - _memoryStreamProvider = memoryStreamProvider; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; @@ -310,7 +308,7 @@ namespace Emby.Server.Implementations.HttpClientManager { using (var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true)) { - var memoryStream = _memoryStreamProvider.CreateNew(); + var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; @@ -343,7 +341,7 @@ namespace Emby.Server.Implementations.HttpClientManager using (var responseStream = response.Content) { - var memoryStream = _memoryStreamProvider.CreateNew(); + var memoryStream = new MemoryStream(); await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; @@ -458,7 +456,7 @@ namespace Emby.Server.Implementations.HttpClientManager using (var stream = httpResponse.GetResponseStream()) { - var memoryStream = _memoryStreamProvider.CreateNew(); + var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); @@ -636,7 +634,7 @@ namespace Emby.Server.Implementations.HttpClientManager { using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) { - await StreamHelper.CopyToAsync(httpResponse.GetResponseStream(), fs, StreamDefaults.DefaultCopyToBufferSize, options.Progress, contentLength.Value, options.CancellationToken).ConfigureAwait(false); + await httpResponse.GetResponseStream().CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } } diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index aa679e1b95..353ba52821 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; +using System.Linq; namespace Emby.Server.Implementations.HttpServer { @@ -147,6 +148,13 @@ namespace Emby.Server.Implementations.HttpServer } } + private string[] SkipLogExtensions = new string[] + { + ".js", + ".html", + ".css" + }; + public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken) { try @@ -157,17 +165,24 @@ namespace Emby.Server.Implementations.HttpServer return; } + var path = Path; + if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) { - Logger.Info("Transmit file {0}", Path); + var extension = System.IO.Path.GetExtension(path); + + if (extension == null || !SkipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + Logger.Debug("Transmit file {0}", path); + } //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); return; } - await response.TransmitFile(Path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); } finally { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 937eb80293..0093258e39 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -12,7 +12,6 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer.SocketSharp; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Net; using MediaBrowser.Common.Security; @@ -25,6 +24,10 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; +using System.Net.Sockets; +using Emby.Server.Implementations.Net; +using MediaBrowser.Common.Events; +using MediaBrowser.Model.Events; namespace Emby.Server.Implementations.HttpServer { @@ -35,64 +38,47 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; public string[] UrlPrefixes { get; private set; } - private readonly List _restServices = new List(); - private IHttpListener _listener; - public event EventHandler WebSocketConnected; - public event EventHandler WebSocketConnecting; + public event EventHandler> WebSocketConnected; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly IServerApplicationHost _appHost; private readonly ITextEncoding _textEncoding; - private readonly ISocketFactory _socketFactory; - private readonly ICryptoProvider _cryptoProvider; - private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; - private readonly X509Certificate _certificate; - private readonly IEnvironmentInfo _environment; private readonly Func> _funcParseFn; - private readonly bool _enableDualModeSockets; - public Action[] RequestFilters { get; set; } public Action[] ResponseFilters { get; set; } private readonly Dictionary ServiceOperationsMap = new Dictionary(); public static HttpListenerHost Instance { get; protected set; } + private IWebSocketListener[] _webSocketListeners = Array.Empty(); + private readonly List _webSocketConnections = new List(); + public HttpListenerHost(IServerApplicationHost applicationHost, ILogger logger, IServerConfigurationManager config, - string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, X509Certificate certificate, Func> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) + string defaultRedirectPath, INetworkManager networkManager, ITextEncoding textEncoding, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, Func> funcParseFn) { Instance = this; _appHost = applicationHost; DefaultRedirectPath = defaultRedirectPath; _networkManager = networkManager; - _memoryStreamProvider = memoryStreamProvider; _textEncoding = textEncoding; - _socketFactory = socketFactory; - _cryptoProvider = cryptoProvider; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - _environment = environment; - _certificate = certificate; - _funcParseFn = funcParseFn; - _enableDualModeSockets = enableDualModeSockets; - _fileSystem = fileSystem; _config = config; _logger = logger; + _funcParseFn = funcParseFn; - RequestFilters = new Action[] { }; ResponseFilters = new Action[] { }; } @@ -140,12 +126,6 @@ namespace Emby.Server.Implementations.HttpServer attribute.RequestFilter(req, res, requestDto); } - //Exec global filters - foreach (var requestFilter in RequestFilters) - { - requestFilter(req, res, requestDto); - } - //Exec remaining RequestFilter attributes with Priority >= 0 for (; i < count && attributes[i].Priority >= 0; i++) { @@ -181,45 +161,38 @@ namespace Emby.Server.Implementations.HttpServer return attributes; } - private IHttpListener GetListener() - { - //return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem); - - return new WebSocketSharpListener(_logger, - _certificate, - _memoryStreamProvider, - _textEncoding, - _networkManager, - _socketFactory, - _cryptoProvider, - _enableDualModeSockets, - _fileSystem, - _environment); - } - - private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) + private void OnWebSocketConnected(WebSocketConnectEventArgs e) { if (_disposed) { return; } - if (WebSocketConnecting != null) + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger, _textEncoding) { - WebSocketConnecting(this, args); - } - } + OnReceive = ProcessWebSocketMessageReceived, + Url = e.Url, + QueryString = e.QueryString ?? new QueryParamCollection() + }; - private void OnWebSocketConnected(WebSocketConnectEventArgs args) - { - if (_disposed) + connection.Closed += Connection_Closed; + + lock (_webSocketConnections) { - return; + _webSocketConnections.Add(connection); } if (WebSocketConnected != null) { - WebSocketConnected(this, args); + EventHelper.FireEventIfNotNull(WebSocketConnected, this, new GenericEventArgs(connection), _logger); + } + } + + private void Connection_Closed(object sender, EventArgs e) + { + lock (_webSocketConnections) + { + _webSocketConnections.Remove((IWebSocketConnection)sender); } } @@ -271,16 +244,20 @@ namespace Emby.Server.Implementations.HttpServer return statusCode; } - private void ErrorHandler(Exception ex, IRequest httpReq, bool logException = true) + private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, bool logExceptionMessage) { try { ex = GetActualException(ex); - if (logException) + if (logExceptionStackTrace) { _logger.ErrorException("Error processing request", ex); } + else if (logExceptionMessage) + { + _logger.Error(ex.Message); + } var httpRes = httpReq.Response; @@ -293,7 +270,7 @@ namespace Emby.Server.Implementations.HttpServer httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; - Write(httpRes, ex.Message); + await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false); } catch { @@ -301,11 +278,46 @@ namespace Emby.Server.Implementations.HttpServer } } + private string NormalizeExceptionMessage(string msg) + { + if (msg == null) + { + return string.Empty; + } + + // Strip any information we don't want to reveal + + msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase); + msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase); + + return msg; + } + /// /// Shut down the Web Service /// public void Stop() { + List connections; + + lock (_webSocketConnections) + { + connections = _webSocketConnections.ToList(); + _webSocketConnections.Clear(); + } + + foreach (var connection in connections) + { + try + { + connection.Dispose(); + } + catch + { + + } + } + if (_listener != null) { _logger.Info("Stopping HttpListener..."); @@ -329,9 +341,9 @@ namespace Emby.Server.Implementations.HttpServer { var extension = GetExtension(url); - if (string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension)) + if (string.IsNullOrEmpty(extension) || !_skipLogExtensions.ContainsKey(extension)) { - if (string.IsNullOrWhiteSpace(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) + if (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) { return true; } @@ -422,12 +434,53 @@ namespace Emby.Server.Implementations.HttpServer return true; } + private bool ValidateRequest(string remoteIp, bool isLocal) + { + if (isLocal) + { + return true; + } + + if (_config.Configuration.EnableRemoteAccess) + { + var addressFilter = _config.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + + if (addressFilter.Length > 0 && !_networkManager.IsInLocalNetwork(remoteIp)) + { + if (_config.Configuration.IsRemoteIPFilterBlacklist) + { + return !_networkManager.IsAddressInSubnets(remoteIp, addressFilter); + } + else + { + return _networkManager.IsAddressInSubnets(remoteIp, addressFilter); + } + } + } + else + { + if (!_networkManager.IsInLocalNetwork(remoteIp)) + { + return false; + } + } + + return true; + } + private bool ValidateSsl(string remoteIp, string urlString) { - if (_config.Configuration.RequireHttps && _appHost.EnableHttps) + if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy) { if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1) { + // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected + if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 || + urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1) + { + return true; + } + if (!_networkManager.IsInLocalNetwork(remoteIp)) { return false; @@ -448,7 +501,7 @@ namespace Emby.Server.Implementations.HttpServer bool enableLog = false; bool logHeaders = false; string urlToLog = null; - string remoteIp = null; + string remoteIp = httpReq.RemoteIp; try { @@ -456,7 +509,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/plain"; - Write(httpRes, "Server shutting down"); + await Write(httpRes, "Server shutting down").ConfigureAwait(false); return; } @@ -464,17 +517,21 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; - Write(httpRes, "Invalid host"); + await Write(httpRes, "Invalid host").ConfigureAwait(false); + return; + } + + if (!ValidateRequest(remoteIp, httpReq.IsLocal)) + { + httpRes.StatusCode = 403; + httpRes.ContentType = "text/plain"; + await Write(httpRes, "Forbidden").ConfigureAwait(false); return; } if (!ValidateSsl(httpReq.RemoteIp, urlString)) { - var httpsUrl = urlString - .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) - .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); - - RedirectToUrl(httpRes, httpsUrl); + RedirectToSecureUrl(httpReq, httpRes, urlString); return; } @@ -485,7 +542,7 @@ namespace Emby.Server.Implementations.HttpServer httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); httpRes.ContentType = "text/plain"; - Write(httpRes, string.Empty); + await Write(httpRes, string.Empty).ConfigureAwait(false); return; } @@ -498,7 +555,6 @@ namespace Emby.Server.Implementations.HttpServer if (enableLog) { urlToLog = GetUrlToLog(urlString); - remoteIp = httpReq.RemoteIp; LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null); } @@ -527,9 +583,9 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - Write(httpRes, + await Write(httpRes, "EmbyPlease update your Emby bookmark to " + newUrl + ""); + newUrl + "\">" + newUrl + "").ConfigureAwait(false); return; } } @@ -544,9 +600,9 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - Write(httpRes, + await Write(httpRes, "EmbyPlease update your Emby bookmark to " + newUrl + ""); + newUrl + "\">" + newUrl + "").ConfigureAwait(false); return; } } @@ -572,18 +628,29 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(httpReq.QueryString["r"], "0", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, "web/pin.html"); - return; + if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, "index.html#!/dashboard.html"); + } + + if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, "index.html"); + } } - if (!string.IsNullOrWhiteSpace(GlobalResponse)) + if (!string.IsNullOrEmpty(GlobalResponse)) { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/html"; - Write(httpRes, GlobalResponse); - return; + // We don't want the address pings in ApplicationHost to fail + if (localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) + { + httpRes.StatusCode = 503; + httpRes.ContentType = "text/html"; + await Write(httpRes, GlobalResponse).ConfigureAwait(false); + return; + } } var handler = GetServiceHandler(httpReq); @@ -594,23 +661,34 @@ namespace Emby.Server.Implementations.HttpServer } else { - ErrorHandler(new FileNotFoundException(), httpReq, false); + await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false); } } catch (OperationCanceledException ex) { - ErrorHandler(ex, httpReq, false); + await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); + } + + catch (IOException ex) + { + await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); + } + + catch (SocketException ex) + { + await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); + } + + catch (SecurityException ex) + { + await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false); } catch (Exception ex) { var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); -#if DEBUG - logException = true; -#endif - - ErrorHandler(ex, httpReq, logException); + await ErrorHandler(ex, httpReq, logException, false).ConfigureAwait(false); } finally { @@ -655,13 +733,36 @@ namespace Emby.Server.Implementations.HttpServer return null; } - private void Write(IResponse response, string text) + private Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); response.SetContentLength(bOutput.Length); - var outputStream = response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); + return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); + } + + private void RedirectToSecureUrl(IHttpRequest httpReq, IResponse httpRes, string url) + { + int currentPort; + Uri uri; + if (Uri.TryCreate(url, UriKind.Absolute, out uri)) + { + currentPort = uri.Port; + var builder = new UriBuilder(uri); + builder.Port = _config.Configuration.PublicHttpsPort; + builder.Scheme = "https"; + url = builder.Uri.ToString(); + + RedirectToUrl(httpRes, url); + } + else + { + var httpsUrl = url + .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) + .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); + + RedirectToUrl(httpRes, url); + } } public static void RedirectToUrl(IResponse httpRes, string url) @@ -676,26 +777,18 @@ namespace Emby.Server.Implementations.HttpServer /// Adds the rest handlers. /// /// The services. - public void Init(IEnumerable services) + public void Init(IEnumerable services, IEnumerable listeners) { - _restServices.AddRange(services); + _webSocketListeners = listeners.ToArray(); ServiceController = new ServiceController(); _logger.Info("Calling ServiceStack AppHost.Init"); - var types = _restServices.Select(r => r.GetType()).ToArray(); + var types = services.Select(r => r.GetType()).ToArray(); ServiceController.Init(this, types); - var list = new List>(); - foreach (var filter in _appHost.GetExports()) - { - list.Add(filter.Filter); - } - - RequestFilters = list.ToArray(); - ResponseFilters = new Action[] { new ResponseFilter(_logger).FilterResponse @@ -750,12 +843,12 @@ namespace Emby.Server.Implementations.HttpServer _xmlSerializer.SerializeToStream(o, stream); } - public object DeserializeXml(Type type, Stream stream) + public Task DeserializeXml(Type type, Stream stream) { - return _xmlSerializer.DeserializeFromStream(type, stream); + return Task.FromResult(_xmlSerializer.DeserializeFromStream(type, stream)); } - public object DeserializeJson(Type type, Stream stream) + public Task DeserializeJson(Type type, Stream stream) { //using (var reader = new StreamReader(stream)) //{ @@ -763,7 +856,7 @@ namespace Emby.Server.Implementations.HttpServer // Logger.Info(json); // return _jsonSerializer.DeserializeFromString(json, type); //} - return _jsonSerializer.DeserializeFromStream(stream, type); + return _jsonSerializer.DeserializeFromStreamAsync(stream, type); } private string NormalizeEmbyRoutePath(string path) @@ -815,20 +908,46 @@ namespace Emby.Server.Implementations.HttpServer } } + /// + /// Processes the web socket message received. + /// + /// The result. + private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) + { + if (_disposed) + { + return Task.CompletedTask; + } + + //_logger.Debug("Websocket message received: {0}", result.MessageType); + + var tasks = _webSocketListeners.Select(i => Task.Run(async () => + { + try + { + await i.ProcessMessage(result).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType ?? string.Empty); + } + })); + + return Task.WhenAll(tasks); + } + public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } - public void StartServer(string[] urlPrefixes) + public void StartServer(string[] urlPrefixes, IHttpListener httpListener) { UrlPrefixes = urlPrefixes; - _listener = GetListener(); + _listener = httpListener; _listener.WebSocketConnected = OnWebSocketConnected; - _listener.WebSocketConnecting = OnWebSocketConnecting; _listener.ErrorHandler = ErrorHandler; _listener.RequestHandler = RequestHandler; diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 86deccee12..df493b4c3e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Net; using System.Runtime.Serialization; using System.Text; @@ -30,16 +31,17 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IMemoryStreamFactory _memoryStreamFactory; + + private IBrotliCompressor _brotliCompressor; /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamFactory) + public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _memoryStreamFactory = memoryStreamFactory; + _brotliCompressor = brotliCompressor; _logger = logManager.GetLogger("HttpResultFactory"); } @@ -50,9 +52,24 @@ namespace Emby.Server.Implementations.HttpServer /// Type of the content. /// The response headers. /// System.Object. - public object GetResult(object content, string contentType, IDictionary responseHeaders = null) + public object GetResult(IRequest requestContext, byte[] content, string contentType, IDictionary responseHeaders = null) { - return GetHttpResult(content, contentType, true, responseHeaders); + return GetHttpResult(requestContext, content, contentType, true, responseHeaders); + } + + public object GetResult(string content, string contentType, IDictionary responseHeaders = null) + { + return GetHttpResult(null, content, contentType, true, responseHeaders); + } + + public object GetResult(IRequest requestContext, Stream content, string contentType, IDictionary responseHeaders = null) + { + return GetHttpResult(requestContext, content, contentType, true, responseHeaders); + } + + public object GetResult(IRequest requestContext, string content, string contentType, IDictionary responseHeaders = null) + { + return GetHttpResult(requestContext, content, contentType, true, responseHeaders); } public object GetRedirectResult(string url) @@ -60,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer var responseHeaders = new Dictionary(); responseHeaders["Location"] = url; - var result = new HttpResult(new byte[] { }, "text/plain", HttpStatusCode.Redirect); + var result = new HttpResult(Array.Empty(), "text/plain", HttpStatusCode.Redirect); AddResponseHeaders(result, responseHeaders); @@ -70,39 +87,98 @@ namespace Emby.Server.Implementations.HttpServer /// /// Gets the HTTP result. /// - private IHasHeaders GetHttpResult(object content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null) + private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null) + { + var result = new StreamWriter(content, contentType, _logger); + + if (responseHeaders == null) + { + responseHeaders = new Dictionary(); + } + + string expires; + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out expires)) + { + responseHeaders["Expires"] = "-1"; + } + + AddResponseHeaders(result, responseHeaders); + + return result; + } + + /// + /// Gets the HTTP result. + /// + private IHasHeaders GetHttpResult(IRequest requestContext, byte[] content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null) { IHasHeaders result; - var stream = content as Stream; + var compressionType = requestContext == null ? null : GetCompressionType(requestContext, content, contentType); - if (stream != null) + var isHeadRequest = string.Equals(requestContext.Verb, "head", StringComparison.OrdinalIgnoreCase); + + if (string.IsNullOrEmpty(compressionType)) { - result = new StreamWriter(stream, contentType, _logger); - } + var contentLength = content.Length; + if (isHeadRequest) + { + content = Array.Empty(); + } + + result = new StreamWriter(content, contentType, contentLength, _logger); + } else { - var bytes = content as byte[]; - - if (bytes != null) - { - result = new StreamWriter(bytes, contentType, _logger); - } - else - { - var text = content as string; - - if (text != null) - { - result = new StreamWriter(Encoding.UTF8.GetBytes(text), contentType, _logger); - } - else - { - result = new HttpResult(content, contentType, HttpStatusCode.OK); - } - } + result = GetCompressedResult(content, compressionType, responseHeaders, isHeadRequest, contentType); } + + if (responseHeaders == null) + { + responseHeaders = new Dictionary(); + } + + string expires; + if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out expires)) + { + responseHeaders["Expires"] = "-1"; + } + + AddResponseHeaders(result, responseHeaders); + + return result; + } + + /// + /// Gets the HTTP result. + /// + private IHasHeaders GetHttpResult(IRequest requestContext, string content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null) + { + IHasHeaders result; + + var bytes = Encoding.UTF8.GetBytes(content); + + var compressionType = requestContext == null ? null : GetCompressionType(requestContext, bytes, contentType); + + var isHeadRequest = requestContext == null ? false : string.Equals(requestContext.Verb, "head", StringComparison.OrdinalIgnoreCase); + + if (string.IsNullOrEmpty(compressionType)) + { + var contentLength = bytes.Length; + + if (isHeadRequest) + { + bytes = Array.Empty(); + } + + result = new StreamWriter(bytes, contentType, contentLength, _logger); + } + else + { + result = GetCompressedResult(bytes, compressionType, responseHeaders, isHeadRequest, contentType); + } + if (responseHeaders == null) { responseHeaders = new Dictionary(); @@ -123,19 +199,8 @@ namespace Emby.Server.Implementations.HttpServer /// Gets the optimized result. /// /// - /// The request context. - /// The result. - /// The response headers. - /// System.Object. - /// result - public object GetOptimizedResult(IRequest requestContext, T result, IDictionary responseHeaders = null) + public object GetResult(IRequest requestContext, T result, IDictionary responseHeaders = null) where T : class - { - return GetOptimizedResultInternal(requestContext, result, true, responseHeaders); - } - - private object GetOptimizedResultInternal(IRequest requestContext, T result, bool addCachePrevention, IDictionary responseHeaders = null) - where T : class { if (result == null) { @@ -147,24 +212,49 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } - if (addCachePrevention) - { - responseHeaders["Expires"] = "-1"; - } + responseHeaders["Expires"] = "-1"; return ToOptimizedResultInternal(requestContext, result, responseHeaders); } - public static string GetCompressionType(IRequest request) + private string GetCompressionType(IRequest request, byte[] content, string responseContentType) + { + if (responseContentType == null) + { + return null; + } + + // Per apple docs, hls manifests must be compressed + if (!responseContentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) && + responseContentType.IndexOf("json", StringComparison.OrdinalIgnoreCase) == -1 && + responseContentType.IndexOf("javascript", StringComparison.OrdinalIgnoreCase) == -1 && + responseContentType.IndexOf("xml", StringComparison.OrdinalIgnoreCase) == -1 && + responseContentType.IndexOf("application/x-mpegURL", StringComparison.OrdinalIgnoreCase) == -1) + { + return null; + } + + if (content.Length < 1024) + { + return null; + } + + return GetCompressionType(request); + } + + private string GetCompressionType(IRequest request) { var acceptEncoding = request.Headers["Accept-Encoding"]; - if (!string.IsNullOrWhiteSpace(acceptEncoding)) + if (acceptEncoding != null) { - if (acceptEncoding.Contains("deflate")) + //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) + // return "br"; + + if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1) return "deflate"; - if (acceptEncoding.Contains("gzip")) + if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1) return "gzip"; } @@ -180,7 +270,7 @@ namespace Emby.Server.Implementations.HttpServer /// public object ToOptimizedResult(IRequest request, T dto) { - return ToOptimizedResultInternal(request, dto, null); + return ToOptimizedResultInternal(request, dto); } private object ToOptimizedResultInternal(IRequest request, T dto, IDictionary responseHeaders = null) @@ -192,28 +282,110 @@ namespace Emby.Server.Implementations.HttpServer case "application/xml": case "text/xml": case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml - return GetHttpResult(SerializeToXmlString(dto), contentType, false, responseHeaders); + return GetHttpResult(request, SerializeToXmlString(dto), contentType, false, responseHeaders); case "application/json": case "text/json": - return GetHttpResult(_jsonSerializer.SerializeToString(dto), contentType, false, responseHeaders); + return GetHttpResult(request, _jsonSerializer.SerializeToString(dto), contentType, false, responseHeaders); default: + break; + } + + var isHeadRequest = string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase); + + var ms = new MemoryStream(); + var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); + + writerFn(dto, ms); + + ms.Position = 0; + + if (isHeadRequest) + { + using (ms) { - var ms = new MemoryStream(); - var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); - - writerFn(dto, ms); - - ms.Position = 0; - - if (string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase)) - { - return GetHttpResult(new byte[] { }, contentType, true, responseHeaders); - } - - return GetHttpResult(ms, contentType, true, responseHeaders); + return GetHttpResult(request, Array.Empty(), contentType, true, responseHeaders); } } + + return GetHttpResult(request, ms, contentType, true, responseHeaders); + } + + private IHasHeaders GetCompressedResult(byte[] content, + string requestedCompressionType, + IDictionary responseHeaders, + bool isHeadRequest, + string contentType) + { + if (responseHeaders == null) + { + responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + content = Compress(content, requestedCompressionType); + responseHeaders["Content-Encoding"] = requestedCompressionType; + + responseHeaders["Vary"] = "Accept-Encoding"; + + var contentLength = content.Length; + + if (isHeadRequest) + { + var result = new StreamWriter(Array.Empty(), contentType, contentLength, _logger); + AddResponseHeaders(result, responseHeaders); + return result; + } + else + { + var result = new StreamWriter(content, contentType, contentLength, _logger); + AddResponseHeaders(result, responseHeaders); + return result; + } + } + + private byte[] Compress(byte[] bytes, string compressionType) + { + if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) + return CompressBrotli(bytes); + + if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) + return Deflate(bytes); + + if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase)) + return GZip(bytes); + + throw new NotSupportedException(compressionType); + } + + private byte[] CompressBrotli(byte[] bytes) + { + return _brotliCompressor.Compress(bytes); + } + + private byte[] Deflate(byte[] bytes) + { + // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream + // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream + using (var ms = new MemoryStream()) + using (var zipStream = new DeflateStream(ms, CompressionMode.Compress)) + { + zipStream.Write(bytes, 0, bytes.Length); + zipStream.Dispose(); + + return ms.ToArray(); + } + } + + private byte[] GZip(byte[] buffer) + { + using (var ms = new MemoryStream()) + using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) + { + zipStream.Write(buffer, 0, buffer.Length); + zipStream.Dispose(); + + return ms.ToArray(); + } } public static string GetRealContentType(string contentType) @@ -243,104 +415,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// Gets the optimized result using cache. - /// - /// - /// The request context. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// The response headers. - /// System.Object. - /// cacheKey - /// or - /// factoryFn - public object GetOptimizedResultUsingCache(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, IDictionary responseHeaders = null) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - if (responseHeaders == null) - { - responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, null); - - if (result != null) - { - return result; - } - - return GetOptimizedResultInternal(requestContext, factoryFn(), false, responseHeaders); - } - - /// - /// To the cached result. - /// - /// - /// The request context. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// Type of the content. - /// The response headers. - /// System.Object. - /// cacheKey - public object GetCachedResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType, IDictionary responseHeaders = null) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - if (responseHeaders == null) - { - responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType); - - if (result != null) - { - return result; - } - - result = factoryFn(); - - // Apply caching headers - var hasHeaders = result as IHasHeaders; - - if (hasHeaders != null) - { - AddResponseHeaders(hasHeaders, responseHeaders); - return hasHeaders; - } - - return GetHttpResult(result, contentType, false, responseHeaders); - } - /// /// Pres the process optimized result. /// @@ -357,7 +431,7 @@ namespace Emby.Server.Implementations.HttpServer AddAgeHeader(responseHeaders, lastDateModified); AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration); - var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified); + var result = new HttpResult(Array.Empty(), contentType ?? "text/html", HttpStatusCode.NotModified); AddResponseHeaders(result, responseHeaders); @@ -402,7 +476,7 @@ namespace Emby.Server.Implementations.HttpServer throw new ArgumentException("FileShare must be either Read or ReadWrite"); } - if (string.IsNullOrWhiteSpace(options.ContentType)) + if (string.IsNullOrEmpty(options.ContentType)) { options.ContentType = MimeTypes.GetMimeType(path); } @@ -460,19 +534,17 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(StringComparer.OrdinalIgnoreCase); var contentType = options.ContentType; - if (cacheKey == Guid.Empty) + if (!cacheKey.Equals(Guid.Empty)) { - throw new ArgumentNullException("cacheKey"); - } + var key = cacheKey.ToString("N"); - var key = cacheKey.ToString("N"); + // See if the result is already cached in the browser + var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType); - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType); - - if (result != null) - { - return result; + if (result != null) + { + return result; + } } // TODO: We don't really need the option value @@ -484,7 +556,7 @@ namespace Emby.Server.Implementations.HttpServer var rangeHeader = requestContext.Headers.Get("Range"); - if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) + if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) { @@ -497,11 +569,24 @@ namespace Emby.Server.Implementations.HttpServer return hasHeaders; } - if (!string.IsNullOrWhiteSpace(rangeHeader)) - { - var stream = await factoryFn().ConfigureAwait(false); + var stream = await factoryFn().ConfigureAwait(false); - var hasHeaders = new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) + var totalContentLength = options.ContentLength; + if (!totalContentLength.HasValue) + { + try + { + totalContentLength = stream.Length; + } + catch (NotSupportedException) + { + + } + } + + if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue) + { + var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger) { OnComplete = options.OnComplete }; @@ -511,15 +596,17 @@ namespace Emby.Server.Implementations.HttpServer } else { - var stream = await factoryFn().ConfigureAwait(false); - - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); + if (totalContentLength.HasValue) + { + responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); + } if (isHeadRequest) { - stream.Dispose(); - - return GetHttpResult(new byte[] { }, contentType, true, responseHeaders); + using (stream) + { + return GetHttpResult(requestContext, Array.Empty(), contentType, true, responseHeaders); + } } var hasHeaders = new StreamWriter(stream, contentType, _logger) @@ -603,7 +690,7 @@ namespace Emby.Server.Implementations.HttpServer /// The last date modified. /// Duration of the cache. /// true if [is not modified] [the specified cache key]; otherwise, false. - private bool IsNotModified(IRequest requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + private bool IsNotModified(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) { //var isNotModified = true; @@ -624,8 +711,10 @@ namespace Emby.Server.Implementations.HttpServer var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match"); + var hasCacheKey = !cacheKey.Equals(Guid.Empty); + // Validate If-None-Match - if ((cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader))) + if ((hasCacheKey || !string.IsNullOrEmpty(ifNoneMatchHeader))) { Guid ifNoneMatch; @@ -633,7 +722,7 @@ namespace Emby.Server.Implementations.HttpServer if (Guid.TryParse(ifNoneMatchHeader, out ifNoneMatch)) { - if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch) + if (hasCacheKey && cacheKey.Equals(ifNoneMatch)) { return true; } @@ -697,4 +786,9 @@ namespace Emby.Server.Implementations.HttpServer } } } + + public interface IBrotliCompressor + { + byte[] Compress(byte[] content); + } } \ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 9feb2311d7..e21607ebd6 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using Emby.Server.Implementations.Net; namespace Emby.Server.Implementations.HttpServer { @@ -13,7 +14,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the error handler. /// /// The error handler. - Action ErrorHandler { get; set; } + Func ErrorHandler { get; set; } /// /// Gets or sets the request handler. diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs index 46bb4c7f94..3aa48efd43 100644 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -2,24 +2,11 @@ using System; using System.Globalization; using MediaBrowser.Model.Services; -using SocketHttpListener.Net; namespace Emby.Server.Implementations.HttpServer { public static class LoggerUtils { - /// - /// Logs the request. - /// - /// The logger. - /// The request. - public static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var url = request.Url.ToString(); - - logger.Info("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); - } - public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers) { if (headers == null) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 7c967949b8..4177c7e786 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.HttpServer /// The source. /// Type of the content. /// if set to true [is head request]. - public RangeRequestWriter(string rangeHeader, Stream source, string contentType, bool isHeadRequest, ILogger logger) + public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger) { if (string.IsNullOrEmpty(contentType)) { @@ -76,17 +76,17 @@ namespace Emby.Server.Implementations.HttpServer StatusCode = HttpStatusCode.PartialContent; Cookies = new List(); - SetRangeValues(); + SetRangeValues(contentLength); } /// /// Sets the range values. /// - private void SetRangeValues() + private void SetRangeValues(long contentLength) { var requestedRange = RequestedRanges[0]; - TotalContentLength = SourceStream.Length; + TotalContentLength = contentLength; // If the requested range is "0-", we can optimize by just doing a stream copy if (!requestedRange.Value.HasValue) @@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.HttpServer Headers["Content-Length"] = RangeLength.ToString(UsCulture); Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); - if (RangeStart > 0) + if (RangeStart > 0 && SourceStream.CanSeek) { SourceStream.Position = RangeStart; } diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index ac36f8f516..385d19b6bc 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -2,7 +2,6 @@ using System; using System.Globalization; using System.Text; -using Emby.Server.Implementations.HttpServer.SocketSharp; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer @@ -47,7 +46,6 @@ namespace Emby.Server.Implementations.HttpServer } var hasHeaders = dto as IHasHeaders; - var sharpResponse = res as WebSocketSharpResponse; if (hasHeaders != null) { @@ -67,7 +65,7 @@ namespace Emby.Server.Implementations.HttpServer if (length > 0) { res.SetContentLength(length); - + //var listenerResponse = res.OriginalResponse as HttpListenerResponse; //if (listenerResponse != null) @@ -78,10 +76,7 @@ namespace Emby.Server.Implementations.HttpServer // return; //} - if (sharpResponse != null) - { - sharpResponse.SendChunked = false; - } + res.SendChunked = false; } } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index fadab44826..8f6d042dd8 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Session; using System; using System.Linq; using MediaBrowser.Model.Services; +using MediaBrowser.Common.Net; namespace Emby.Server.Implementations.HttpServer.Security { @@ -16,21 +17,21 @@ namespace Emby.Server.Implementations.HttpServer.Security { private readonly IServerConfigurationManager _config; - public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager, IDeviceManager deviceManager) + public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, IConnectManager connectManager, ISessionManager sessionManager, INetworkManager networkManager) { AuthorizationContext = authorizationContext; _config = config; - DeviceManager = deviceManager; SessionManager = sessionManager; ConnectManager = connectManager; UserManager = userManager; + NetworkManager = networkManager; } public IUserManager UserManager { get; private set; } public IAuthorizationContext AuthorizationContext { get; private set; } public IConnectManager ConnectManager { get; private set; } public ISessionManager SessionManager { get; private set; } - public IDeviceManager DeviceManager { get; private set; } + public INetworkManager NetworkManager { get; private set; } /// /// Redirect the client to a specific URL if authentication failed. @@ -38,14 +39,12 @@ namespace Emby.Server.Implementations.HttpServer.Security /// public string HtmlRedirect { get; set; } - public void Authenticate(IRequest request, - IAuthenticationAttributes authAttribtues) + public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) { ValidateUser(request, authAttribtues); } - private void ValidateUser(IRequest request, - IAuthenticationAttributes authAttribtues) + private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(request); @@ -60,11 +59,14 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - var user = string.IsNullOrWhiteSpace(auth.UserId) - ? null - : UserManager.GetUserById(auth.UserId); + if (authAttribtues.AllowLocalOnly && !request.IsLocal) + { + throw new SecurityException("Operation not found."); + } - if (user == null & !string.IsNullOrWhiteSpace(auth.UserId)) + var user = auth.User; + + if (user == null & !auth.UserId.Equals(Guid.Empty)) { throw new SecurityException("User with Id " + auth.UserId + " not found"); } @@ -83,9 +85,9 @@ namespace Emby.Server.Implementations.HttpServer.Security ValidateRoles(roles, user); } - if (!string.IsNullOrWhiteSpace(auth.DeviceId) && - !string.IsNullOrWhiteSpace(auth.Client) && - !string.IsNullOrWhiteSpace(auth.Device)) + if (!string.IsNullOrEmpty(auth.DeviceId) && + !string.IsNullOrEmpty(auth.Client) && + !string.IsNullOrEmpty(auth.Device)) { SessionManager.LogSessionActivity(auth.Client, auth.Version, @@ -108,6 +110,14 @@ namespace Emby.Server.Implementations.HttpServer.Security }; } + if (!user.Policy.EnableRemoteAccess && !NetworkManager.IsInLocalNetwork(request.RemoteIp)) + { + throw new SecurityException("User account has been disabled.") + { + SecurityExceptionType = SecurityExceptionType.Unauthenticated + }; + } + if (!user.Policy.IsAdministrator && !authAttribtues.EscapeParentalControl && !user.IsParentalScheduleAllowed()) @@ -119,17 +129,6 @@ namespace Emby.Server.Implementations.HttpServer.Security SecurityExceptionType = SecurityExceptionType.ParentalControl }; } - - if (!string.IsNullOrWhiteSpace(auth.DeviceId)) - { - if (!DeviceManager.CanAccessDevice(user.Id.ToString("N"), auth.DeviceId)) - { - throw new SecurityException("User is not allowed access from this device.") - { - SecurityExceptionType = SecurityExceptionType.ParentalControl - }; - } - } } private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request) @@ -143,6 +142,10 @@ namespace Emby.Server.Implementations.HttpServer.Security { return true; } + if (authAttribtues.AllowLocalOnly && request.IsLocal) + { + return true; + } return false; } @@ -159,12 +162,17 @@ namespace Emby.Server.Implementations.HttpServer.Security return true; } - if (string.IsNullOrWhiteSpace(auth.Token)) + if (authAttribtues.AllowLocalOnly && request.IsLocal) { return true; } - if (tokenInfo != null && string.IsNullOrWhiteSpace(tokenInfo.UserId)) + if (string.IsNullOrEmpty(auth.Token)) + { + return true; + } + + if (tokenInfo != null && tokenInfo.UserId.Equals(Guid.Empty)) { return true; } @@ -225,7 +233,7 @@ namespace Emby.Server.Implementations.HttpServer.Security private void ValidateSecurityToken(IRequest request, string token) { - if (string.IsNullOrWhiteSpace(token)) + if (string.IsNullOrEmpty(token)) { throw new SecurityException("Access token is required."); } @@ -237,12 +245,7 @@ namespace Emby.Server.Implementations.HttpServer.Security throw new SecurityException("Access token is invalid or expired."); } - if (!info.IsActive) - { - throw new SecurityException("Access token has expired."); - } - - //if (!string.IsNullOrWhiteSpace(info.UserId)) + //if (!string.IsNullOrEmpty(info.UserId)) //{ // var user = _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index a41c51d1a8..ee8f6c60ba 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using MediaBrowser.Model.Services; using System.Linq; using System.Threading; +using MediaBrowser.Controller.Library; namespace Emby.Server.Implementations.HttpServer.Security { @@ -13,11 +14,13 @@ namespace Emby.Server.Implementations.HttpServer.Security { private readonly IAuthenticationRepository _authRepo; private readonly IConnectManager _connectManager; + private readonly IUserManager _userManager; - public AuthorizationContext(IAuthenticationRepository authRepo, IConnectManager connectManager) + public AuthorizationContext(IAuthenticationRepository authRepo, IConnectManager connectManager, IUserManager userManager) { _authRepo = authRepo; _connectManager = connectManager; + _userManager = userManager; } public AuthorizationInfo GetAuthorizationInfo(object requestContext) @@ -60,16 +63,16 @@ namespace Emby.Server.Implementations.HttpServer.Security auth.TryGetValue("Token", out token); } - if (string.IsNullOrWhiteSpace(token)) + if (string.IsNullOrEmpty(token)) { token = httpReq.Headers["X-Emby-Token"]; } - if (string.IsNullOrWhiteSpace(token)) + if (string.IsNullOrEmpty(token)) { token = httpReq.Headers["X-MediaBrowser-Token"]; } - if (string.IsNullOrWhiteSpace(token)) + if (string.IsNullOrEmpty(token)) { token = httpReq.QueryString["api_key"]; } @@ -94,8 +97,6 @@ namespace Emby.Server.Implementations.HttpServer.Security if (tokenInfo != null) { - info.UserId = tokenInfo.UserId; - var updateToken = false; // TODO: Remove these checks for IsNullOrWhiteSpace @@ -109,15 +110,21 @@ namespace Emby.Server.Implementations.HttpServer.Security info.DeviceId = tokenInfo.DeviceId; } + // Temporary. TODO - allow clients to specify that the token has been shared with a casting device + var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; if (string.IsNullOrWhiteSpace(info.Device)) { info.Device = tokenInfo.DeviceName; } + else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) { - updateToken = true; - tokenInfo.DeviceName = info.Device; + if (allowTokenInfoUpdate) + { + updateToken = true; + tokenInfo.DeviceName = info.Device; + } } if (string.IsNullOrWhiteSpace(info.Version)) @@ -126,22 +133,38 @@ namespace Emby.Server.Implementations.HttpServer.Security } else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) { + if (allowTokenInfoUpdate) + { + updateToken = true; + tokenInfo.AppVersion = info.Version; + } + } + + if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3) + { + tokenInfo.DateLastActivity = DateTime.UtcNow; updateToken = true; - tokenInfo.AppVersion = info.Version; + } + + if (!tokenInfo.UserId.Equals(Guid.Empty)) + { + info.User = _userManager.GetUserById(tokenInfo.UserId); + + if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) + { + tokenInfo.UserName = info.User.Name; + updateToken = true; + } } if (updateToken) { - _authRepo.Update(tokenInfo, CancellationToken.None); + _authRepo.Update(tokenInfo); } } else { - var user = _connectManager.GetUserFromExchangeToken(token); - if (user != null) - { - info.UserId = user.Id.ToString("N"); - } + info.User = _connectManager.GetUserFromExchangeToken(token); } httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo; } @@ -160,7 +183,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { var auth = httpReq.Headers["X-Emby-Authorization"]; - if (string.IsNullOrWhiteSpace(auth)) + if (string.IsNullOrEmpty(auth)) { auth = httpReq.Headers["Authorization"]; } @@ -212,7 +235,7 @@ namespace Emby.Server.Implementations.HttpServer.Security private string NormalizeValue(string value) { - if (string.IsNullOrWhiteSpace(value)) + if (string.IsNullOrEmpty(value)) { return value; } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 9826a0d564..a919ce0083 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using System; namespace Emby.Server.Implementations.HttpServer.Security { @@ -21,11 +22,11 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public Task GetSession(IRequest requestContext) + public SessionInfo GetSession(IRequest requestContext) { var authorization = _authContext.GetAuthorizationInfo(requestContext); - var user = string.IsNullOrWhiteSpace(authorization.UserId) ? null : _userManager.GetUserById(authorization.UserId); + var user = authorization.User; return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user); } @@ -36,19 +37,19 @@ namespace Emby.Server.Implementations.HttpServer.Security return info as AuthenticationInfo; } - public Task GetSession(object requestContext) + public SessionInfo GetSession(object requestContext) { return GetSession((IRequest)requestContext); } - public async Task GetUser(IRequest requestContext) + public User GetUser(IRequest requestContext) { - var session = await GetSession(requestContext).ConfigureAwait(false); + var session = GetSession(requestContext); - return session == null || !session.UserId.HasValue ? null : _userManager.GetUserById(session.UserId.Value); + return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public Task GetUser(object requestContext) + public User GetUser(object requestContext) { return GetUser((IRequest)requestContext); } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs deleted file mode 100644 index 07a338f198..0000000000 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using SocketHttpListener.Net; - -namespace Emby.Server.Implementations.HttpServer.SocketSharp -{ - public static class Extensions - { - public static string GetOperationName(this HttpListenerRequest request) - { - return request.Url.Segments[request.Url.Segments.Length - 1]; - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs deleted file mode 100644 index 4e8dd73626..0000000000 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs +++ /dev/null @@ -1,923 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Extensions; - -namespace Emby.Server.Implementations.HttpServer.SocketSharp -{ - public static class MyHttpUtility - { - // Must be sorted - static readonly long[] entities = new long[] { - (long)'A' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'A' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'A' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'B' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'C' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'C' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'D' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'D' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'E' << 56 | (long)'T' << 48 | (long)'H' << 40, - (long)'E' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'E' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'E' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'E' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'G' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'I' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'I' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'I' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'K' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'L' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'M' << 56 | (long)'u' << 48, - (long)'N' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'N' << 56 | (long)'u' << 48, - (long)'O' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'O' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'O' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'O' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'O' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'O' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'P' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'P' << 56 | (long)'i' << 48, - (long)'P' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'P' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'R' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'S' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'S' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'T' << 56 | (long)'H' << 48 | (long)'O' << 40 | (long)'R' << 32 | (long)'N' << 24, - (long)'T' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'T' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'U' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'U' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'U' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'X' << 56 | (long)'i' << 48, - (long)'Y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'Y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'Z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'a' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'a' << 56 | (long)'c' << 48 | (long)'u' << 40 | (long)'t' << 32 | (long)'e' << 24, - (long)'a' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'l' << 48 | (long)'e' << 40 | (long)'f' << 32 | (long)'s' << 24 | (long)'y' << 16 | (long)'m' << 8, - (long)'a' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'a' << 56 | (long)'m' << 48 | (long)'p' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'d' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'g' << 40, - (long)'a' << 56 | (long)'p' << 48 | (long)'o' << 40 | (long)'s' << 32, - (long)'a' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'s' << 48 | (long)'y' << 40 | (long)'m' << 32 | (long)'p' << 24, - (long)'a' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'b' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'b' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'b' << 56 | (long)'r' << 48 | (long)'v' << 40 | (long)'b' << 32 | (long)'a' << 24 | (long)'r' << 16, - (long)'b' << 56 | (long)'u' << 48 | (long)'l' << 40 | (long)'l' << 32, - (long)'c' << 56 | (long)'a' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'c' << 56 | (long)'e' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'c' << 56 | (long)'e' << 48 | (long)'n' << 40 | (long)'t' << 32, - (long)'c' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'c' << 56 | (long)'i' << 48 | (long)'r' << 40 | (long)'c' << 32, - (long)'c' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'b' << 32 | (long)'s' << 24, - (long)'c' << 56 | (long)'o' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'c' << 56 | (long)'o' << 48 | (long)'p' << 40 | (long)'y' << 32, - (long)'c' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'r' << 24, - (long)'c' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'n' << 16, - (long)'d' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'d' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'d' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'a' << 40 | (long)'m' << 32 | (long)'s' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'v' << 40 | (long)'i' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'e' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'m' << 48 | (long)'p' << 40 | (long)'t' << 32 | (long)'y' << 24, - (long)'e' << 56 | (long)'m' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'n' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'e' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'i' << 32 | (long)'v' << 24, - (long)'e' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'e' << 56 | (long)'t' << 48 | (long)'h' << 40, - (long)'e' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'e' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'o' << 32, - (long)'e' << 56 | (long)'x' << 48 | (long)'i' << 40 | (long)'s' << 32 | (long)'t' << 24, - (long)'f' << 56 | (long)'n' << 48 | (long)'o' << 40 | (long)'f' << 32, - (long)'f' << 56 | (long)'o' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'l' << 24 | (long)'l' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'2' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'3' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'l' << 24, - (long)'g' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'g' << 56 | (long)'e' << 48, - (long)'g' << 56 | (long)'t' << 48, - (long)'h' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'t' << 24 | (long)'s' << 16, - (long)'h' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'l' << 32 | (long)'i' << 24 | (long)'p' << 16, - (long)'i' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'i' << 56 | (long)'e' << 48 | (long)'x' << 40 | (long)'c' << 32 | (long)'l' << 24, - (long)'i' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'m' << 48 | (long)'a' << 40 | (long)'g' << 32 | (long)'e' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'f' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'t' << 40, - (long)'i' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'i' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'e' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'i' << 56 | (long)'s' << 48 | (long)'i' << 40 | (long)'n' << 32, - (long)'i' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'k' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'l' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'l' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'l' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'e' << 48, - (long)'l' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'w' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'z' << 40, - (long)'l' << 56 | (long)'r' << 48 | (long)'m' << 40, - (long)'l' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'l' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'t' << 48, - (long)'m' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'r' << 32, - (long)'m' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'c' << 40 | (long)'r' << 32 | (long)'o' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'d' << 40 | (long)'d' << 32 | (long)'o' << 24 | (long)'t' << 16, - (long)'m' << 56 | (long)'i' << 48 | (long)'n' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'m' << 56 | (long)'u' << 48, - (long)'n' << 56 | (long)'a' << 48 | (long)'b' << 40 | (long)'l' << 32 | (long)'a' << 24, - (long)'n' << 56 | (long)'b' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'n' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'n' << 56 | (long)'e' << 48, - (long)'n' << 56 | (long)'i' << 48, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'n' << 56 | (long)'s' << 48 | (long)'u' << 40 | (long)'b' << 32, - (long)'n' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'n' << 56 | (long)'u' << 48, - (long)'o' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'o' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'o' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'l' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'e' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'o' << 56 | (long)'p' << 48 | (long)'l' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'o' << 56 | (long)'r' << 48, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'f' << 32, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'m' << 32, - (long)'o' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'o' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'a' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'t' << 32, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'m' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'p' << 56 | (long)'i' << 48, - (long)'p' << 56 | (long)'i' << 48 | (long)'v' << 40, - (long)'p' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'s' << 32 | (long)'m' << 24 | (long)'n' << 16, - (long)'p' << 56 | (long)'o' << 48 | (long)'u' << 40 | (long)'n' << 32 | (long)'d' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'d' << 32, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'q' << 56 | (long)'u' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'r' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'c' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'r' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'l' << 32, - (long)'r' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'r' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'r' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'r' << 56 | (long)'l' << 48 | (long)'m' << 40, - (long)'r' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'r' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'b' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'s' << 56 | (long)'d' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'e' << 48 | (long)'c' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'h' << 48 | (long)'y' << 40, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24 | (long)'f' << 16, - (long)'s' << 56 | (long)'i' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'p' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'1' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'2' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'3' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'z' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'t' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'4' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24 | (long)'s' << 16 | (long)'y' << 8 | (long)'m' << 0, - (long)'t' << 56 | (long)'h' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'s' << 24 | (long)'p' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'o' << 40 | (long)'r' << 32 | (long)'n' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'l' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'m' << 40 | (long)'e' << 32 | (long)'s' << 24, - (long)'t' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'u' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'u' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'m' << 48 | (long)'l' << 40, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'h' << 24, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'u' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'w' << 56 | (long)'e' << 48 | (long)'i' << 40 | (long)'e' << 32 | (long)'r' << 24 | (long)'p' << 16, - (long)'x' << 56 | (long)'i' << 48, - (long)'y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'y' << 56 | (long)'e' << 48 | (long)'n' << 40, - (long)'y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'z' << 56 | (long)'w' << 48 | (long)'j' << 40, - (long)'z' << 56 | (long)'w' << 48 | (long)'n' << 40 | (long)'j' << 32 - }; - - static readonly char[] entities_values = new char[] { - '\u00C6', - '\u00C1', - '\u00C2', - '\u00C0', - '\u0391', - '\u00C5', - '\u00C3', - '\u00C4', - '\u0392', - '\u00C7', - '\u03A7', - '\u2021', - '\u0394', - '\u00D0', - '\u00C9', - '\u00CA', - '\u00C8', - '\u0395', - '\u0397', - '\u00CB', - '\u0393', - '\u00CD', - '\u00CE', - '\u00CC', - '\u0399', - '\u00CF', - '\u039A', - '\u039B', - '\u039C', - '\u00D1', - '\u039D', - '\u0152', - '\u00D3', - '\u00D4', - '\u00D2', - '\u03A9', - '\u039F', - '\u00D8', - '\u00D5', - '\u00D6', - '\u03A6', - '\u03A0', - '\u2033', - '\u03A8', - '\u03A1', - '\u0160', - '\u03A3', - '\u00DE', - '\u03A4', - '\u0398', - '\u00DA', - '\u00DB', - '\u00D9', - '\u03A5', - '\u00DC', - '\u039E', - '\u00DD', - '\u0178', - '\u0396', - '\u00E1', - '\u00E2', - '\u00B4', - '\u00E6', - '\u00E0', - '\u2135', - '\u03B1', - '\u0026', - '\u2227', - '\u2220', - '\u0027', - '\u00E5', - '\u2248', - '\u00E3', - '\u00E4', - '\u201E', - '\u03B2', - '\u00A6', - '\u2022', - '\u2229', - '\u00E7', - '\u00B8', - '\u00A2', - '\u03C7', - '\u02C6', - '\u2663', - '\u2245', - '\u00A9', - '\u21B5', - '\u222A', - '\u00A4', - '\u21D3', - '\u2020', - '\u2193', - '\u00B0', - '\u03B4', - '\u2666', - '\u00F7', - '\u00E9', - '\u00EA', - '\u00E8', - '\u2205', - '\u2003', - '\u2002', - '\u03B5', - '\u2261', - '\u03B7', - '\u00F0', - '\u00EB', - '\u20AC', - '\u2203', - '\u0192', - '\u2200', - '\u00BD', - '\u00BC', - '\u00BE', - '\u2044', - '\u03B3', - '\u2265', - '\u003E', - '\u21D4', - '\u2194', - '\u2665', - '\u2026', - '\u00ED', - '\u00EE', - '\u00A1', - '\u00EC', - '\u2111', - '\u221E', - '\u222B', - '\u03B9', - '\u00BF', - '\u2208', - '\u00EF', - '\u03BA', - '\u21D0', - '\u03BB', - '\u2329', - '\u00AB', - '\u2190', - '\u2308', - '\u201C', - '\u2264', - '\u230A', - '\u2217', - '\u25CA', - '\u200E', - '\u2039', - '\u2018', - '\u003C', - '\u00AF', - '\u2014', - '\u00B5', - '\u00B7', - '\u2212', - '\u03BC', - '\u2207', - '\u00A0', - '\u2013', - '\u2260', - '\u220B', - '\u00AC', - '\u2209', - '\u2284', - '\u00F1', - '\u03BD', - '\u00F3', - '\u00F4', - '\u0153', - '\u00F2', - '\u203E', - '\u03C9', - '\u03BF', - '\u2295', - '\u2228', - '\u00AA', - '\u00BA', - '\u00F8', - '\u00F5', - '\u2297', - '\u00F6', - '\u00B6', - '\u2202', - '\u2030', - '\u22A5', - '\u03C6', - '\u03C0', - '\u03D6', - '\u00B1', - '\u00A3', - '\u2032', - '\u220F', - '\u221D', - '\u03C8', - '\u0022', - '\u21D2', - '\u221A', - '\u232A', - '\u00BB', - '\u2192', - '\u2309', - '\u201D', - '\u211C', - '\u00AE', - '\u230B', - '\u03C1', - '\u200F', - '\u203A', - '\u2019', - '\u201A', - '\u0161', - '\u22C5', - '\u00A7', - '\u00AD', - '\u03C3', - '\u03C2', - '\u223C', - '\u2660', - '\u2282', - '\u2286', - '\u2211', - '\u2283', - '\u00B9', - '\u00B2', - '\u00B3', - '\u2287', - '\u00DF', - '\u03C4', - '\u2234', - '\u03B8', - '\u03D1', - '\u2009', - '\u00FE', - '\u02DC', - '\u00D7', - '\u2122', - '\u21D1', - '\u00FA', - '\u2191', - '\u00FB', - '\u00F9', - '\u00A8', - '\u03D2', - '\u03C5', - '\u00FC', - '\u2118', - '\u03BE', - '\u00FD', - '\u00A5', - '\u00FF', - '\u03B6', - '\u200D', - '\u200C' - }; - - #region Methods - - static void WriteCharBytes(IList buf, char ch, Encoding e) - { - if (ch > 255) - { - foreach (byte b in e.GetBytes(new char[] { ch })) - buf.Add(b); - } - else - buf.Add((byte)ch); - } - - public static string UrlDecode(string s, Encoding e) - { - if (null == s) - return null; - - if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1) - return s; - - if (e == null) - e = Encoding.UTF8; - - long len = s.Length; - var bytes = new List(); - int xchar; - char ch; - - for (int i = 0; i < len; i++) - { - ch = s[i]; - if (ch == '%' && i + 2 < len && s[i + 1] != '%') - { - if (s[i + 1] == 'u' && i + 5 < len) - { - // unicode hex sequence - xchar = GetChar(s, i + 2, 4); - if (xchar != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 5; - } - else - WriteCharBytes(bytes, '%', e); - } - else if ((xchar = GetChar(s, i + 1, 2)) != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 2; - } - else - { - WriteCharBytes(bytes, '%', e); - } - continue; - } - - if (ch == '+') - WriteCharBytes(bytes, ' ', e); - else - WriteCharBytes(bytes, ch, e); - } - - byte[] buf = bytes.ToArray(bytes.Count); - bytes = null; - return e.GetString(buf, 0, buf.Length); - - } - - static int GetInt(byte b) - { - char c = (char)b; - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -1; - } - - static int GetChar(string str, int offset, int length) - { - int val = 0; - int end = length + offset; - for (int i = offset; i < end; i++) - { - char c = str[i]; - if (c > 127) - return -1; - - int current = GetInt((byte)c); - if (current == -1) - return -1; - val = (val << 4) + current; - } - - return val; - } - - static bool TryConvertKeyToEntity(string key, out char value) - { - var token = CalculateKeyValue(key); - if (token == 0) - { - value = '\0'; - return false; - } - - var idx = Array.BinarySearch(entities, token); - if (idx < 0) - { - value = '\0'; - return false; - } - - value = entities_values[idx]; - return true; - } - - static long CalculateKeyValue(string s) - { - if (s.Length > 8) - return 0; - - long key = 0; - for (int i = 0; i < s.Length; ++i) - { - long ch = s[i]; - if (ch > 'z' || ch < '0') - return 0; - - key |= ch << ((7 - i) * 8); - } - - return key; - } - - /// - /// Decodes an HTML-encoded string and returns the decoded string. - /// - /// The HTML string to decode. - /// The decoded text. - public static string HtmlDecode(string s) - { - if (s == null) - throw new ArgumentNullException("s"); - - if (s.IndexOf('&') == -1) - return s; - - StringBuilder entity = new StringBuilder(); - StringBuilder output = new StringBuilder(); - int len = s.Length; - // 0 -> nothing, - // 1 -> right after '&' - // 2 -> between '&' and ';' but no '#' - // 3 -> '#' found after '&' and getting numbers - int state = 0; - int number = 0; - int digit_start = 0; - bool hex_number = false; - - for (int i = 0; i < len; i++) - { - char c = s[i]; - if (state == 0) - { - if (c == '&') - { - entity.Append(c); - state = 1; - } - else - { - output.Append(c); - } - continue; - } - - if (c == '&') - { - state = 1; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - output.Append(entity.ToString()); - entity.Length = 0; - entity.Append('&'); - continue; - } - - switch (state) - { - case 1: - if (c == ';') - { - state = 0; - output.Append(entity.ToString()); - output.Append(c); - entity.Length = 0; - break; - } - - number = 0; - hex_number = false; - if (c != '#') - { - state = 2; - } - else - { - state = 3; - } - entity.Append(c); - - break; - case 2: - entity.Append(c); - if (c == ';') - { - string key = entity.ToString(); - state = 0; - entity.Length = 0; - - if (key.Length > 1) - { - var skey = key.Substring(1, key.Length - 2); - if (TryConvertKeyToEntity(skey, out c)) - { - output.Append(c); - break; - } - } - - output.Append(key); - } - - break; - case 3: - if (c == ';') - { - if (number < 0x10000) - { - output.Append((char)number); - } - else - { - output.Append((char)(0xd800 + ((number - 0x10000) >> 10))); - output.Append((char)(0xdc00 + ((number - 0x10000) & 0x3ff))); - } - state = 0; - entity.Length = 0; - digit_start = 0; - break; - } - - if (c == 'x' || c == 'X' && !hex_number) - { - digit_start = i; - hex_number = true; - break; - } - - if (Char.IsDigit(c)) - { - if (digit_start == 0) - digit_start = i; - - number = number * (hex_number ? 16 : 10) + ((int)c - '0'); - break; - } - - if (hex_number) - { - if (c >= 'a' && c <= 'f') - { - number = number * 16 + 10 + ((int)c - 'a'); - break; - } - if (c >= 'A' && c <= 'F') - { - number = number * 16 + 10 + ((int)c - 'A'); - break; - } - } - - state = 2; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - entity.Append(c); - break; - } - } - - if (entity.Length > 0) - { - output.Append(entity); - } - else if (digit_start > 0) - { - output.Append(s, digit_start, s.Length - digit_start); - } - return output.ToString(); - } - - public static QueryParamCollection ParseQueryString(string query) - { - return ParseQueryString(query, Encoding.UTF8); - } - - public static QueryParamCollection ParseQueryString(string query, Encoding encoding) - { - if (query == null) - throw new ArgumentNullException("query"); - if (encoding == null) - throw new ArgumentNullException("encoding"); - if (query.Length == 0 || (query.Length == 1 && query[0] == '?')) - return new QueryParamCollection(); - if (query[0] == '?') - query = query.Substring(1); - - QueryParamCollection result = new QueryParamCollection(); - ParseQueryString(query, encoding, result); - return result; - } - - internal static void ParseQueryString(string query, Encoding encoding, QueryParamCollection result) - { - if (query.Length == 0) - return; - - string decoded = HtmlDecode(query); - int decodedLength = decoded.Length; - int namePos = 0; - bool first = true; - while (namePos <= decodedLength) - { - int valuePos = -1, valueEnd = -1; - for (int q = namePos; q < decodedLength; q++) - { - if (valuePos == -1 && decoded[q] == '=') - { - valuePos = q + 1; - } - else if (decoded[q] == '&') - { - valueEnd = q; - break; - } - } - - if (first) - { - first = false; - if (decoded[namePos] == '?') - namePos++; - } - - string name, value; - if (valuePos == -1) - { - name = null; - valuePos = namePos; - } - else - { - name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding); - } - if (valueEnd < 0) - { - namePos = -1; - valueEnd = decoded.Length; - } - else - { - namePos = valueEnd + 1; - } - value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding); - - result.Add(name, value); - if (namePos == -1) - break; - } - } - #endregion // Methods - } -} diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 5d42f42fa0..3f394d3acd 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.HttpServer /// The source. /// Type of the content. /// The logger. - public StreamWriter(byte[] source, string contentType, ILogger logger) + public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger) { if (string.IsNullOrEmpty(contentType)) { @@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.HttpServer Headers["Content-Type"] = contentType; - Headers["Content-Length"] = source.Length.ToString(UsCulture); + Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) @@ -106,10 +106,8 @@ namespace Emby.Server.Implementations.HttpServer } } } - catch (Exception ex) + catch { - Logger.ErrorException("Error streaming data", ex); - if (OnError != null) { OnError(); diff --git a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs similarity index 85% rename from Emby.Server.Implementations/ServerManager/WebSocketConnection.cs rename to Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 076f50d937..d449e4424a 100644 --- a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -5,15 +5,14 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using System; -using System.Collections.Specialized; -using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using MediaBrowser.Model.Text; +using System.Net.WebSockets; +using Emby.Server.Implementations.Net; -namespace Emby.Server.Implementations.ServerManager +namespace Emby.Server.Implementations.HttpServer { /// /// Class WebSocketConnection @@ -32,11 +31,6 @@ namespace Emby.Server.Implementations.ServerManager /// public string RemoteEndPoint { get; private set; } - /// - /// The _cancellation token source - /// - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - /// /// The logger /// @@ -51,7 +45,7 @@ namespace Emby.Server.Implementations.ServerManager /// Gets or sets the receive action. /// /// The receive action. - public Action OnReceive { get; set; } + public Func OnReceive { get; set; } /// /// Gets the last activity date. @@ -75,7 +69,6 @@ namespace Emby.Server.Implementations.ServerManager /// /// The query string. public QueryParamCollection QueryString { get; set; } - private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly ITextEncoding _textEncoding; /// @@ -86,7 +79,7 @@ namespace Emby.Server.Implementations.ServerManager /// The json serializer. /// The logger. /// socket - public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding) + public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, ITextEncoding textEncoding) { if (socket == null) { @@ -109,10 +102,15 @@ namespace Emby.Server.Implementations.ServerManager _jsonSerializer = jsonSerializer; _socket = socket; _socket.OnReceiveBytes = OnReceiveInternal; - _socket.OnReceive = OnReceiveInternal; + + var memorySocket = socket as IMemoryWebSocket; + if (memorySocket != null) + { + memorySocket.OnReceiveMemoryBytes = OnReceiveInternal; + } + RemoteEndPoint = remoteEndPoint; _logger = logger; - _memoryStreamProvider = memoryStreamProvider; _textEncoding = textEncoding; socket.Closed += socket_Closed; @@ -148,6 +146,33 @@ namespace Emby.Server.Implementations.ServerManager } } + /// + /// Called when [receive]. + /// + /// The bytes. + private void OnReceiveInternal(Memory memory, int length) + { + LastActivityDate = DateTime.UtcNow; + + if (OnReceive == null) + { + return; + } + + var bytes = memory.Slice(0, length).ToArray(); + + var charset = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, null, false); + + if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) + { + OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length)); + } + else + { + OnReceiveInternal(_textEncoding.GetASCIIEncoding().GetString(bytes, 0, bytes.Length)); + } + } + private void OnReceiveInternal(string message) { LastActivityDate = DateTime.UtcNow; @@ -223,7 +248,7 @@ namespace Emby.Server.Implementations.ServerManager public Task SendAsync(string text, CancellationToken cancellationToken) { - if (string.IsNullOrWhiteSpace(text)) + if (string.IsNullOrEmpty(text)) { throw new ArgumentNullException("text"); } @@ -248,7 +273,6 @@ namespace Emby.Server.Implementations.ServerManager public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// @@ -259,7 +283,6 @@ namespace Emby.Server.Implementations.ServerManager { if (dispose) { - _cancellationTokenSource.Dispose(); _socket.Dispose(); } } diff --git a/Emby.Server.Implementations/HttpServerFactory.cs b/Emby.Server.Implementations/HttpServerFactory.cs deleted file mode 100644 index 717c50e7b5..0000000000 --- a/Emby.Server.Implementations/HttpServerFactory.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.IO; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.Net; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Text; -using ServiceStack.Text.Jsv; -using SocketHttpListener.Primitives; - -namespace Emby.Server.Implementations -{ - /// - /// Class ServerFactory - /// - public static class HttpServerFactory - { - /// - /// Creates the server. - /// - /// IHttpServer. - public static IHttpServer CreateServer(IServerApplicationHost applicationHost, - ILogManager logManager, - IServerConfigurationManager config, - INetworkManager networkmanager, - IMemoryStreamFactory streamProvider, - string serverName, - string defaultRedirectpath, - ITextEncoding textEncoding, - ISocketFactory socketFactory, - ICryptoProvider cryptoProvider, - IJsonSerializer json, - IXmlSerializer xml, - IEnvironmentInfo environment, - X509Certificate certificate, - IFileSystem fileSystem, - bool enableDualModeSockets) - { - var logger = logManager.GetLogger("HttpServer"); - - return new HttpListenerHost(applicationHost, - logger, - config, - serverName, - defaultRedirectpath, - networkmanager, - streamProvider, - textEncoding, - socketFactory, - cryptoProvider, - json, - xml, - environment, - certificate, - GetParseFn, - enableDualModeSockets, - fileSystem); - } - - private static Func GetParseFn(Type propertyType) - { - return s => JsvReader.GetParseFn(propertyType)(s); - } - } -} diff --git a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs new file mode 100644 index 0000000000..6b08c26c9b --- /dev/null +++ b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Emby.Server.Implementations.IO +{ + public class ExtendedFileSystemInfo + { + public bool IsHidden { get; set; } + public bool IsReadOnly { get; set; } + public bool Exists { get; set; } + } +} diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 85b8bddd28..4be30f8b74 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.IO private void AddAffectedPath(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } @@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.IO public void AddPath(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } @@ -113,7 +113,7 @@ namespace Emby.Server.Implementations.IO Path = path; AddAffectedPath(path); - if (!string.IsNullOrWhiteSpace(affectedFile)) + if (!string.IsNullOrEmpty(affectedFile)) { AddAffectedPath(affectedFile); } @@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.IO // If the item has been deleted find the first valid parent that still exists while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) { - item = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); + item = item.GetOwner() ?? item.GetParent(); if (item == null) { @@ -231,7 +231,6 @@ namespace Emby.Server.Implementations.IO { _disposed = true; DisposeTimer(); - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/IsoManager.cs b/Emby.Server.Implementations/IO/IsoManager.cs index dc0b9e122e..903d5f301f 100644 --- a/Emby.Server.Implementations/IO/IsoManager.cs +++ b/Emby.Server.Implementations/IO/IsoManager.cs @@ -70,7 +70,6 @@ namespace Emby.Server.Implementations.IO { mounter.Dispose(); } - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index a2abb2a5c9..00fe447f0f 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -266,7 +266,7 @@ namespace Emby.Server.Implementations.IO /// path private static bool ContainsParentFolder(IEnumerable lst, string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } @@ -304,6 +304,12 @@ namespace Emby.Server.Implementations.IO } } + if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Android) + { + // causing crashing + return; + } + // Already being watched if (_fileSystemWatchers.ContainsKey(path)) { @@ -320,11 +326,7 @@ namespace Emby.Server.Implementations.IO IncludeSubdirectories = true }; - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows || - _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) - { - newWatcher.InternalBufferSize = 32767; - } + newWatcher.InternalBufferSize = 65536; newWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | @@ -337,7 +339,6 @@ namespace Emby.Server.Implementations.IO newWatcher.Deleted += watcher_Changed; newWatcher.Renamed += watcher_Changed; newWatcher.Changed += watcher_Changed; - newWatcher.Error += watcher_Error; if (_fileSystemWatchers.TryAdd(path, newWatcher)) @@ -347,7 +348,7 @@ namespace Emby.Server.Implementations.IO } else { - newWatcher.Dispose(); + DisposeWatcher(newWatcher, false); } } @@ -368,15 +369,14 @@ namespace Emby.Server.Implementations.IO if (_fileSystemWatchers.TryGetValue(path, out watcher)) { - DisposeWatcher(watcher); + DisposeWatcher(watcher, true); } } /// /// Disposes the watcher. /// - /// The watcher. - private void DisposeWatcher(FileSystemWatcher watcher) + private void DisposeWatcher(FileSystemWatcher watcher, bool removeFromList) { try { @@ -384,16 +384,37 @@ namespace Emby.Server.Implementations.IO { Logger.Info("Stopping directory watching for path {0}", watcher.Path); - watcher.EnableRaisingEvents = false; + watcher.Created -= watcher_Changed; + watcher.Deleted -= watcher_Changed; + watcher.Renamed -= watcher_Changed; + watcher.Changed -= watcher_Changed; + watcher.Error -= watcher_Error; + + try + { + watcher.EnableRaisingEvents = false; + } + catch (InvalidOperationException) + { + // Seeing this under mono on linux sometimes + // Collection was modified; enumeration operation may not execute. + } } } + catch (NotImplementedException) + { + // the dispose method on FileSystemWatcher is sometimes throwing NotImplementedException on Xamarin Android + } catch { } finally { - RemoveWatcherFromList(watcher); + if (removeFromList) + { + RemoveWatcherFromList(watcher); + } } } @@ -420,7 +441,7 @@ namespace Emby.Server.Implementations.IO Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); - DisposeWatcher(dw); + DisposeWatcher(dw, true); } /// @@ -452,7 +473,7 @@ namespace Emby.Server.Implementations.IO } var filename = Path.GetFileName(path); - + var monitorPath = !string.IsNullOrEmpty(filename) && !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) && @@ -466,13 +487,13 @@ namespace Emby.Server.Implementations.IO { if (_fileSystem.AreEqual(i, path)) { - Logger.Debug("Ignoring change to {0}", path); + //Logger.Debug("Ignoring change to {0}", path); return true; } if (_fileSystem.ContainsSubPath(i, path)) { - Logger.Debug("Ignoring change to {0}", path); + //Logger.Debug("Ignoring change to {0}", path); return true; } @@ -482,7 +503,7 @@ namespace Emby.Server.Implementations.IO { if (_fileSystem.AreEqual(parent, path)) { - Logger.Debug("Ignoring change to {0}", path); + //Logger.Debug("Ignoring change to {0}", path); return true; } } @@ -561,22 +582,7 @@ namespace Emby.Server.Implementations.IO foreach (var watcher in _fileSystemWatchers.Values.ToList()) { - watcher.Created -= watcher_Changed; - watcher.Deleted -= watcher_Changed; - watcher.Renamed -= watcher_Changed; - watcher.Changed -= watcher_Changed; - - try - { - watcher.EnableRaisingEvents = false; - } - catch (InvalidOperationException) - { - // Seeing this under mono on linux sometimes - // Collection was modified; enumeration operation may not execute. - } - - watcher.Dispose(); + DisposeWatcher(watcher, false); } _fileSystemWatchers.Clear(); @@ -612,7 +618,6 @@ namespace Emby.Server.Implementations.IO { _disposed = true; Dispose(true); - GC.SuppressFinalize(this); } /// @@ -644,7 +649,6 @@ namespace Emby.Server.Implementations.IO public void Dispose() { - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index c8e4031a90..66d7802c6f 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -20,27 +20,57 @@ namespace Emby.Server.Implementations.IO private readonly bool _supportsAsyncFileStreams; private char[] _invalidFileNameChars; private readonly List _shortcutHandlers = new List(); - private bool EnableFileSystemRequestConcat; + private bool EnableSeparateFileAndDirectoryQueries; private string _tempPath; private SharpCifsFileSystem _sharpCifsFileSystem; private IEnvironmentInfo _environmentInfo; + private bool _isEnvironmentCaseInsensitive; - public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath) + private string _defaultDirectory; + + public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string defaultDirectory, string tempPath, bool enableSeparateFileAndDirectoryQueries) { Logger = logger; _supportsAsyncFileStreams = true; _tempPath = tempPath; _environmentInfo = environmentInfo; + _defaultDirectory = defaultDirectory; - // On Linux, this needs to be true or symbolic links are ignored - EnableFileSystemRequestConcat = environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows && - environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX; + // On Linux with mono, this needs to be true or symbolic links are ignored + EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries; SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); _sharpCifsFileSystem = new SharpCifsFileSystem(environmentInfo.OperatingSystem); + + _isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; + } + + public string DefaultDirectory + { + get + { + var value = _defaultDirectory; + + if (!string.IsNullOrEmpty(value)) + { + try + { + if (DirectoryExists(value)) + { + return value; + } + } + catch + { + + } + } + + return null; + } } public void AddShortcutHandler(IShortcutHandler handler) @@ -56,11 +86,9 @@ namespace Emby.Server.Implementations.IO } else { - // GetInvalidFileNameChars is less restrictive in Linux/Mac than Windows, this mimic Windows behavior for mono under Linux/Mac. - _invalidFileNameChars = new char[41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', - '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', - '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', - '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' }; + // Be consistent across platforms because the windows server will fail to query network shares that don't follow windows conventions + // https://referencesource.microsoft.com/#mscorlib/system/io/path.cs + _invalidFileNameChars = new char[] { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' }; } } @@ -118,6 +146,49 @@ namespace Emby.Server.Implementations.IO return null; } + public string MakeAbsolutePath(string folderPath, string filePath) + { + if (String.IsNullOrWhiteSpace(filePath)) return filePath; + + if (filePath.Contains(@"://")) return filePath; //stream + if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/') return filePath; //absolute local path + + // unc path + if (filePath.StartsWith("\\\\")) + { + return filePath; + } + + var firstChar = filePath[0]; + if (firstChar == '/') + { + // For this we don't really know. + return filePath; + } + if (firstChar == '\\') //relative path + { + filePath = filePath.Substring(1); + } + try + { + string path = System.IO.Path.Combine(folderPath, filePath); + path = System.IO.Path.GetFullPath(path); + return path; + } + catch (ArgumentException ex) + { + return filePath; + } + catch (PathTooLongException) + { + return filePath; + } + catch (NotSupportedException) + { + return filePath; + } + } + /// /// Creates the shortcut. /// @@ -162,11 +233,6 @@ namespace Emby.Server.Implementations.IO /// property will be set to true and all other properties will reflect the properties of the directory. public FileSystemMetadata GetFileSystemInfo(string path) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - if (_sharpCifsFileSystem.IsEnabledForPath(path)) { return _sharpCifsFileSystem.GetFileSystemInfo(path); @@ -207,11 +273,6 @@ namespace Emby.Server.Implementations.IO /// For automatic handling of files and directories, use . public FileSystemMetadata GetFileInfo(string path) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - if (_sharpCifsFileSystem.IsEnabledForPath(path)) { return _sharpCifsFileSystem.GetFileInfo(path); @@ -232,11 +293,6 @@ namespace Emby.Server.Implementations.IO /// For automatic handling of files and directories, use . public FileSystemMetadata GetDirectoryInfo(string path) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - if (_sharpCifsFileSystem.IsEnabledForPath(path)) { return _sharpCifsFileSystem.GetDirectoryInfo(path); @@ -258,10 +314,12 @@ namespace Emby.Server.Implementations.IO if (result.Exists) { - var attributes = info.Attributes; - result.IsDirectory = info is DirectoryInfo || (attributes & FileAttributes.Directory) == FileAttributes.Directory; - result.IsHidden = (attributes & FileAttributes.Hidden) == FileAttributes.Hidden; - result.IsReadOnly = (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; + result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory; + + //if (!result.IsDirectory) + //{ + // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; + //} var fileInfo = info as FileInfo; if (fileInfo != null) @@ -281,6 +339,25 @@ namespace Emby.Server.Implementations.IO return result; } + private ExtendedFileSystemInfo GetExtendedFileSystemInfo(string path) + { + var result = new ExtendedFileSystemInfo(); + + var info = new FileInfo(path); + + if (info.Exists) + { + result.Exists = true; + + var attributes = info.Attributes; + + result.IsHidden = (attributes & FileAttributes.Hidden) == FileAttributes.Hidden; + result.IsReadOnly = (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; + } + + return result; + } + /// /// The space char /// @@ -294,11 +371,6 @@ namespace Emby.Server.Implementations.IO /// filename public string GetValidFilename(string filename) { - if (string.IsNullOrEmpty(filename)) - { - throw new ArgumentNullException("filename"); - } - var builder = new StringBuilder(filename); foreach (var c in _invalidFileNameChars) @@ -484,7 +556,7 @@ namespace Emby.Server.Implementations.IO return; } - var info = GetFileInfo(path); + var info = GetExtendedFileSystemInfo(path); if (info.Exists && info.IsHidden != isHidden) { @@ -514,7 +586,7 @@ namespace Emby.Server.Implementations.IO return; } - var info = GetFileInfo(path); + var info = GetExtendedFileSystemInfo(path); if (info.Exists && info.IsReadOnly != isReadOnly) { @@ -544,7 +616,7 @@ namespace Emby.Server.Implementations.IO return; } - var info = GetFileInfo(path); + var info = GetExtendedFileSystemInfo(path); if (!info.Exists) { @@ -720,11 +792,6 @@ namespace Emby.Server.Implementations.IO public bool IsPathFile(string path) { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\ if (_sharpCifsFileSystem.IsEnabledForPath(path)) @@ -822,7 +889,7 @@ namespace Emby.Server.Implementations.IO // On linux and osx the search pattern is case sensitive // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method - if (enableCaseSensitiveExtensions && extensions != null && extensions.Length == 1) + if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1) { return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption)); } @@ -855,7 +922,7 @@ namespace Emby.Server.Implementations.IO var directoryInfo = new DirectoryInfo(path); var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - if (EnableFileSystemRequestConcat) + if (EnableSeparateFileAndDirectoryQueries) { return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption)) .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption))); @@ -897,9 +964,28 @@ namespace Emby.Server.Implementations.IO return File.OpenRead(path); } + private void CopyFileUsingStreams(string source, string target, bool overwrite) + { + using (var sourceStream = OpenRead(source)) + { + using (var targetStream = GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + { + sourceStream.CopyTo(targetStream); + } + } + } + public void CopyFile(string source, string target, bool overwrite) { - if (_sharpCifsFileSystem.IsEnabledForPath(source)) + var enableSharpCifsForSource = _sharpCifsFileSystem.IsEnabledForPath(source); + + if (enableSharpCifsForSource != _sharpCifsFileSystem.IsEnabledForPath(target)) + { + CopyFileUsingStreams(source, target, overwrite); + return; + } + + if (enableSharpCifsForSource) { _sharpCifsFileSystem.CopyFile(source, target, overwrite); return; @@ -1033,7 +1119,7 @@ namespace Emby.Server.Implementations.IO // On linux and osx the search pattern is case sensitive // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method - if (enableCaseSensitiveExtensions && extensions != null && extensions.Length == 1) + if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1) { return Directory.EnumerateFiles(path, "*" + extensions[0], searchOption); } diff --git a/Emby.Server.Implementations/IO/MemoryStreamProvider.cs b/Emby.Server.Implementations/IO/MemoryStreamProvider.cs deleted file mode 100644 index e9ecb7e44f..0000000000 --- a/Emby.Server.Implementations/IO/MemoryStreamProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.IO; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.IO -{ - public class MemoryStreamProvider : IMemoryStreamFactory - { - public MemoryStream CreateNew() - { - return new MemoryStream(); - } - - public MemoryStream CreateNew(int capacity) - { - return new MemoryStream(capacity); - } - - public MemoryStream CreateNew(byte[] buffer) - { - return new MemoryStream(buffer); - } - - public bool TryGetBuffer(MemoryStream stream, out byte[] buffer) - { - buffer = stream.GetBuffer(); - return true; - } - } -} diff --git a/Emby.Server.Implementations/IO/SharpCifsFileSystem.cs b/Emby.Server.Implementations/IO/SharpCifsFileSystem.cs index 0e1f6bb008..a48543bc70 100644 --- a/Emby.Server.Implementations/IO/SharpCifsFileSystem.cs +++ b/Emby.Server.Implementations/IO/SharpCifsFileSystem.cs @@ -136,9 +136,6 @@ namespace Emby.Server.Implementations.IO if (result.Exists) { result.IsDirectory = info.IsDirectory(); - result.IsHidden = info.IsHidden(); - - result.IsReadOnly = !info.CanWrite(); if (info.IsFile()) { diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs new file mode 100644 index 0000000000..48a5063e80 --- /dev/null +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -0,0 +1,190 @@ +using System.IO; +using System.Threading; +using System; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.IO +{ + public class StreamHelper : IStreamHelper + { + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) + { + byte[] buffer = new byte[bufferSize]; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + + if (onStarted != null) + { + onStarted(); + onStarted = null; + } + } + } + + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken) + { + byte[] buffer = new byte[bufferSize]; + + if (emptyReadLimit <= 0) + { + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + } + + return; + } + + var eofCount = 0; + + while (eofCount < emptyReadLimit) + { + cancellationToken.ThrowIfCancellationRequested(); + + var bytesRead = source.Read(buffer, 0, buffer.Length); + + if (bytesRead == 0) + { + eofCount++; + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + + await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + } + } + } + + const int StreamCopyToBufferSize = 81920; + public async Task CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken) + { + var array = new byte[StreamCopyToBufferSize]; + int bytesRead; + int totalBytesRead = 0; + + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + var bytesToWrite = bytesRead; + + if (bytesToWrite > 0) + { + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } + } + + return totalBytesRead; + } + + public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) + { + var array = new byte[StreamCopyToBufferSize]; + int bytesRead; + int totalBytesRead = 0; + + while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + { + var bytesToWrite = bytesRead; + + if (bytesToWrite > 0) + { + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } + } + + return totalBytesRead; + } + + public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) + { + var array = new byte[StreamCopyToBufferSize]; + int bytesRead; + + while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + { + var bytesToWrite = Math.Min(bytesRead, copyLength); + + if (bytesToWrite > 0) + { + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } + + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; + } + } + } + + public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) + { + var array = new byte[StreamCopyToBufferSize]; + int bytesRead; + + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + var bytesToWrite = Math.Min(bytesRead, copyLength); + + if (bytesToWrite > 0) + { + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } + + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; + } + } + } + + public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken) + { + byte[] buffer = new byte[bufferSize]; + + while (!cancellationToken.IsCancellationRequested) + { + var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false); + + //var position = fs.Position; + //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); + + if (bytesRead == 0) + { + await Task.Delay(100).ConfigureAwait(false); + } + } + } + + private static async Task CopyToAsyncInternal(Stream source, Stream destination, byte[] buffer, CancellationToken cancellationToken) + { + int bytesRead; + int totalBytesRead = 0; + + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } + } +} diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 5cd7e4262e..be17893d8f 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -22,7 +22,7 @@ using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.Images { public abstract class BaseDynamicImageProvider : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider, IHasOrder - where T : IHasMetadata + where T : BaseItem { protected IFileSystem FileSystem { get; private set; } protected IProviderManager ProviderManager { get; private set; } @@ -37,12 +37,12 @@ namespace Emby.Server.Implementations.Images ImageProcessor = imageProcessor; } - protected virtual bool Supports(IHasMetadata item) + protected virtual bool Supports(BaseItem item) { return true; } - public virtual ImageType[] GetSupportedImages(IHasMetadata item) + public virtual ImageType[] GetSupportedImages(BaseItem item) { return new ImageType[] { @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Images return updateType; } - protected async Task FetchAsync(IHasMetadata item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken) + protected Task FetchAsync(BaseItem item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken) { var image = item.GetImageInfo(imageType, 0); @@ -83,21 +83,21 @@ namespace Emby.Server.Implementations.Images { if (!image.IsLocalFile) { - return ItemUpdateType.None; + return Task.FromResult(ItemUpdateType.None); } if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) { - return ItemUpdateType.None; + return Task.FromResult(ItemUpdateType.None); } } var items = GetItemsWithImages(item); - return await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false); + return FetchToFileInternal(item, items, imageType, cancellationToken); } - protected async Task FetchToFileInternal(IHasMetadata item, + protected async Task FetchToFileInternal(BaseItem item, List itemsWithImages, ImageType imageType, CancellationToken cancellationToken) @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.Images FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPathWithoutExtension)); string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); - if (string.IsNullOrWhiteSpace(outputPath)) + if (string.IsNullOrEmpty(outputPath)) { return ItemUpdateType.None; } @@ -123,14 +123,14 @@ namespace Emby.Server.Implementations.Images return ItemUpdateType.ImageUpdate; } - protected abstract List GetItemsWithImages(IHasMetadata item); + protected abstract List GetItemsWithImages(BaseItem item); - protected string CreateThumbCollage(IHasMetadata primaryItem, List items, string outputPath) + protected string CreateThumbCollage(BaseItem primaryItem, List items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 640, 360); } - protected virtual IEnumerable GetStripCollageImagePaths(IHasMetadata primaryItem, IEnumerable items) + protected virtual IEnumerable GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable items) { return items .Select(i => @@ -149,25 +149,25 @@ namespace Emby.Server.Implementations.Images } return null; }) - .Where(i => !string.IsNullOrWhiteSpace(i)); + .Where(i => !string.IsNullOrEmpty(i)); } - protected string CreatePosterCollage(IHasMetadata primaryItem, List items, string outputPath) + protected string CreatePosterCollage(BaseItem primaryItem, List items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 400, 600); } - protected string CreateSquareCollage(IHasMetadata primaryItem, List items, string outputPath) + protected string CreateSquareCollage(BaseItem primaryItem, List items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 600, 600); } - protected string CreateThumbCollage(IHasMetadata primaryItem, List items, string outputPath, int width, int height) + protected string CreateThumbCollage(BaseItem primaryItem, List items, string outputPath, int width, int height) { return CreateCollage(primaryItem, items, outputPath, width, height); } - private string CreateCollage(IHasMetadata primaryItem, List items, string outputPath, int width, int height) + private string CreateCollage(BaseItem primaryItem, List items, string outputPath, int width, int height) { FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath)); @@ -198,7 +198,7 @@ namespace Emby.Server.Implementations.Images get { return "Dynamic Image Provider"; } } - protected virtual string CreateImage(IHasMetadata item, + protected virtual string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, @@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.Images get { return 7; } } - public bool HasChanged(IHasMetadata item, IDirectoryService directoryServicee) + public bool HasChanged(BaseItem item, IDirectoryService directoryServicee) { if (!Supports(item)) { @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images return false; } - protected bool HasChanged(IHasMetadata item, ImageType type) + protected bool HasChanged(BaseItem item, ImageType type) { var image = item.GetImageInfo(type, 0); @@ -283,7 +283,7 @@ namespace Emby.Server.Implementations.Images return true; } - protected virtual bool HasChangedByDate(IHasMetadata item, ItemImageInfo image) + protected virtual bool HasChangedByDate(BaseItem item, ItemImageInfo image) { var age = DateTime.UtcNow - image.DateModified; if (age.TotalDays <= MaxImageAgeDays) @@ -293,19 +293,6 @@ namespace Emby.Server.Implementations.Images return true; } - protected List GetFinalItems(IEnumerable items) - { - return GetFinalItems(items, 4); - } - - protected virtual List GetFinalItems(IEnumerable items, int limit) - { - return items - .OrderBy(i => Guid.NewGuid()) - .Take(limit) - .ToList(); - } - public int Order { get @@ -322,7 +309,7 @@ namespace Emby.Server.Implementations.Images .Select(i => i.GetImagePath(imageType)) .FirstOrDefault(); - if (string.IsNullOrWhiteSpace(image)) + if (string.IsNullOrEmpty(image)) { return null; } diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 5c3e1dab1a..59f0a9fc9d 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -22,10 +22,12 @@ namespace Emby.Server.Implementations.Library private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; + private bool _ignoreDotPrefix; + /// /// Any folder named in this list will be ignored - can be added to at runtime for extensibility /// - public static readonly List IgnoreFolders = new List + public static readonly Dictionary IgnoreFolders = new List { "metadata", "ps3_update", @@ -41,15 +43,24 @@ namespace Emby.Server.Implementations.Library "#recycle", // Qnap - "@Recycle" + "@Recycle", + ".@__thumb", + "$RECYCLE.BIN", + "System Volume Information", + ".grab", + + // macos + ".AppleDouble" + + }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - }; - public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager, ILogger logger) { _fileSystem = fileSystem; _libraryManager = libraryManager; _logger = logger; + + _ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT; } /// @@ -67,46 +78,48 @@ namespace Emby.Server.Implementations.Library } var filename = fileInfo.Name; - var isHidden = fileInfo.IsHidden; var path = fileInfo.FullName; // Handle mac .DS_Store // https://github.com/MediaBrowser/MediaBrowser/issues/427 - if (filename.IndexOf("._", StringComparison.OrdinalIgnoreCase) == 0) + if (_ignoreDotPrefix) { - return true; + if (filename.IndexOf('.') == 0) + { + return true; + } } // Ignore hidden files and folders - if (isHidden) - { - if (parent == null) - { - var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path)); + //if (fileInfo.IsHidden) + //{ + // if (parent == null) + // { + // var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path)); - if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } + // if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) + // { + // return false; + // } + // if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) + // { + // return false; + // } + // } - // Sometimes these are marked hidden - if (_fileSystem.IsRootPath(path)) - { - return false; - } + // // Sometimes these are marked hidden + // if (_fileSystem.IsRootPath(path)) + // { + // return false; + // } - return true; - } + // return true; + //} if (fileInfo.IsDirectory) { // Ignore any folders in our list - if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase)) + if (IgnoreFolders.ContainsKey(filename)) { return true; } @@ -141,6 +154,17 @@ namespace Emby.Server.Implementations.Library return true; } } + + // Ignore samples + var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase) + .Replace("-", " ", StringComparison.OrdinalIgnoreCase) + .Replace("_", " ", StringComparison.OrdinalIgnoreCase) + .Replace("!", " ", StringComparison.OrdinalIgnoreCase); + + if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1) + { + return true; + } } return false; diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs new file mode 100644 index 0000000000..7c79a7c69e --- /dev/null +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + public string Name => "Default"; + + public bool IsEnabled => true; + + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + public Task Authenticate(string username, string password, User resolvedUser) + { + if (resolvedUser == null) + { + throw new Exception("Invalid username or password"); + } + + var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + + if (!success) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + public Task HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + public Task ChangePassword(User user, string newPassword) + { + string newPasswordHash = null; + + if (newPassword != null) + { + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException("newPasswordHash"); + } + + user.Password = newPasswordHash; + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return string.IsNullOrEmpty(user.Password) + ? GetEmptyHashedString(user) + : user.Password; + } + + public string GetEmptyHashedString(User user) + { + return GetHashedString(user, string.Empty); + } + + /// + /// Gets the hashed string. + /// + public string GetHashedString(User user, string str) + { + var salt = user.Salt; + if (salt != null) + { + // return BCrypt.HashPassword(str, salt); + } + + // legacy + return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + } + } +} diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs new file mode 100644 index 0000000000..186ec63dac --- /dev/null +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Dto; +using MediaBrowser.Controller.Library; + +namespace Emby.Server.Implementations.Library +{ + public class ExclusiveLiveStream : ILiveStream + { + public int ConsumerCount { get; set; } + public string OriginalStreamId { get; set; } + + public string TunerHostId => null; + + public bool EnableStreamSharing { get; set; } + public MediaSourceInfo MediaSource { get; set; } + + public string UniqueId { get; private set; } + + private Func _closeFn; + + public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func closeFn) + { + MediaSource = mediaSource; + EnableStreamSharing = false; + _closeFn = closeFn; + ConsumerCount = 1; + UniqueId = Guid.NewGuid().ToString("N"); + } + + public Task Close() + { + return _closeFn(); + } + + public Task Open(CancellationToken openCancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 2934a51479..31af9370c7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -44,6 +44,9 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Tasks; +using Emby.Server.Implementations.Playlists; +using MediaBrowser.Providers.MediaInfo; +using MediaBrowser.Controller; namespace Emby.Server.Implementations.Library { @@ -70,12 +73,6 @@ namespace Emby.Server.Implementations.Library /// The entity resolution ignore rules. private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } - /// - /// Gets the list of BasePluginFolders added by plugins - /// - /// The plugin folders. - private IVirtualFolderCreator[] PluginFolderCreators { get; set; } - /// /// Gets the list of currently registered entity resolvers /// @@ -140,6 +137,7 @@ namespace Emby.Server.Implementations.Library private readonly Func _providerManagerFactory; private readonly Func _userviewManager; public bool IsScanRunning { get; private set; } + private IServerApplicationHost _appHost; /// /// The _library items cache @@ -167,7 +165,7 @@ namespace Emby.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func libraryMonitorFactory, IFileSystem fileSystem, Func providerManagerFactory, Func userviewManager) + public LibraryManager(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func libraryMonitorFactory, IFileSystem fileSystem, Func providerManagerFactory, Func userviewManager) { _logger = logger; _taskManager = taskManager; @@ -178,6 +176,7 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; + _appHost = appHost; _libraryItemsCache = new ConcurrentDictionary(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -195,14 +194,12 @@ namespace Emby.Server.Implementations.Library /// The item comparers. /// The postscan tasks. public void AddParts(IEnumerable rules, - IEnumerable pluginFolders, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, IEnumerable postscanTasks) { EntityResolutionIgnoreRules = rules.ToArray(); - PluginFolderCreators = pluginFolders.ToArray(); EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); MultiItemResolvers = EntityResolvers.OfType().ToArray(); IntroProviders = introProviders.ToArray(); @@ -302,7 +299,7 @@ namespace Emby.Server.Implementations.Library } else { - if (!(item is Video)) + if (!(item is Video) && !(item is LiveTvChannel)) { return; } @@ -311,13 +308,47 @@ namespace Emby.Server.Implementations.Library LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); } - public async Task DeleteItem(BaseItem item, DeleteOptions options) + public void DeleteItem(BaseItem item, DeleteOptions options) + { + DeleteItem(item, options, false); + } + + public void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem) { if (item == null) { throw new ArgumentNullException("item"); } + var parent = item.GetOwner() ?? item.GetParent(); + + DeleteItem(item, options, parent, notifyParentItem); + } + + public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + if (item.SourceType == SourceType.Channel) + { + if (options.DeleteFromExternalProvider) + { + try + { + var task = BaseItem.ChannelManager.DeleteItem(item); + Task.WaitAll(task); + } + catch (ArgumentException) + { + // channel no longer installed + } + } + options.DeleteFileLocation = false; + } + if (item is LiveTvProgram) { _logger.Debug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", @@ -335,10 +366,6 @@ namespace Emby.Server.Implementations.Library item.Id); } - var parent = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); - - var locationType = item.LocationType; - var children = item.IsFolder ? ((Folder)item).GetRecursiveChildren(false).ToList() : new List(); @@ -361,7 +388,7 @@ namespace Emby.Server.Implementations.Library } } - if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual) + if (options.DeleteFileLocation && item.IsFileProtocol) { // Assume only the first is required // Add this flag to GetDeletePaths if required in the future @@ -407,32 +434,9 @@ namespace Emby.Server.Implementations.Library isRequiredForDelete = false; } + } - if (parent != null) - { - var parentFolder = parent as Folder; - if (parentFolder != null) - { - await parentFolder.ValidateChildren(new SimpleProgress(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false); - } - else - { - await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); - } - } - } - else if (parent != null) - { - var parentFolder = parent as Folder; - if (parentFolder != null) - { - parentFolder.RemoveChild(item); - } - else - { - await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); - } - } + item.SetParent(null); ItemRepository.DeleteItem(item.Id, CancellationToken.None); foreach (var child in children) @@ -497,7 +501,7 @@ namespace Emby.Server.Implementations.Library private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive) { - if (string.IsNullOrWhiteSpace(key)) + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException("key"); } @@ -544,7 +548,7 @@ namespace Emby.Server.Implementations.Library var fullPath = fileInfo.FullName; - if (string.IsNullOrWhiteSpace(collectionType) && parent != null) + if (string.IsNullOrEmpty(collectionType) && parent != null) { collectionType = GetContentTypeOverride(fullPath, true); } @@ -572,7 +576,26 @@ namespace Emby.Server.Implementations.Library // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); + FileSystemMetadata[] files; + var isVf = args.IsVf; + + try + { + files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _appHost, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || isVf); + } + catch (Exception ex) + { + if (parent != null && parent.IsPhysicalRoot) + { + _logger.ErrorException("Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", ex, isPhysicalRoot, isVf); + + files = new FileSystemMetadata[] { }; + } + else + { + throw; + } + } // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action @@ -717,43 +740,44 @@ namespace Emby.Server.Implementations.Library } // Add in the plug-in folders - foreach (var child in PluginFolderCreators) + var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists"); + + _fileSystem.CreateDirectory(path); + + Folder folder = new PlaylistsFolder { - var folder = child.GetFolder(); + Path = path + }; - if (folder != null) + if (folder.Id.Equals(Guid.Empty)) + { + if (string.IsNullOrEmpty(folder.Path)) { - if (folder.Id == Guid.Empty) - { - if (string.IsNullOrWhiteSpace(folder.Path)) - { - folder.Id = GetNewItemId(folder.GetType().Name, folder.GetType()); - } - else - { - folder.Id = GetNewItemId(folder.Path, folder.GetType()); - } - } - - var dbItem = GetItemById(folder.Id) as BasePluginFolder; - - if (dbItem != null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) - { - folder = dbItem; - } - - if (folder.ParentId != rootFolder.Id) - { - folder.ParentId = rootFolder.Id; - folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - } - - rootFolder.AddVirtualChild(folder); - - RegisterItem(folder); + folder.Id = GetNewItemId(folder.GetType().Name, folder.GetType()); + } + else + { + folder.Id = GetNewItemId(folder.Path, folder.GetType()); } } + var dbItem = GetItemById(folder.Id) as BasePluginFolder; + + if (dbItem != null && string.Equals(dbItem.Path, folder.Path, StringComparison.OrdinalIgnoreCase)) + { + folder = dbItem; + } + + if (folder.ParentId != rootFolder.Id) + { + folder.ParentId = rootFolder.Id; + folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); + } + + rootFolder.AddVirtualChild(folder); + + RegisterItem(folder); + return rootFolder; } @@ -798,16 +822,18 @@ namespace Emby.Server.Implementations.Library // If this returns multiple items it could be tricky figuring out which one is correct. // In most cases, the newest one will be and the others obsolete but not yet cleaned up - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } + //_logger.Info("FindByPath {0}", path); + var query = new InternalItemsQuery { Path = path, IsFolder = isFolder, - OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new Tuple(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -957,7 +983,7 @@ namespace Emby.Server.Implementations.Library Path = path }; - CreateItem(item, CancellationToken.None); + CreateItem(item, null); } return item; @@ -997,7 +1023,7 @@ namespace Emby.Server.Implementations.Library // Just run the scheduled task so that the user can see it _taskManager.CancelIfRunningAndQueue(); - return Task.FromResult(true); + return Task.CompletedTask; } /// @@ -1031,48 +1057,45 @@ namespace Emby.Server.Implementations.Library } } - private async Task PerformLibraryValidation(IProgress progress, CancellationToken cancellationToken) + private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) { - _logger.Info("Validating media library"); - - // Ensure these objects are lazy loaded. - // Without this there is a deadlock that will need to be investigated var rootChildren = RootFolder.Children.ToList(); rootChildren = GetUserRootFolder().Children.ToList(); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); - progress.Report(.5); - // Start by just validating the children of the root, but go no further await RootFolder.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(_fileSystem), recursive: false); - progress.Report(1); - await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); await GetUserRootFolder().ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(_fileSystem), recursive: false).ConfigureAwait(false); - progress.Report(2); // Quickly scan CollectionFolders for changes foreach (var folder in GetUserRootFolder().Children.OfType().ToList()) { await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false); } - progress.Report(3); + } + + private async Task PerformLibraryValidation(IProgress progress, CancellationToken cancellationToken) + { + _logger.Info("Validating media library"); + + await ValidateTopLibraryFolders(cancellationToken).ConfigureAwait(false); var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => progress.Report(3 + pct * .72)); + innerProgress.RegisterAction(pct => progress.Report(pct * .96)); // Now validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(_fileSystem), recursive: true).ConfigureAwait(false); - progress.Report(75); + progress.Report(96); innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => progress.Report(75 + pct * .25)); + innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04))); // Run post-scan tasks await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false); @@ -1102,8 +1125,13 @@ namespace Emby.Server.Implementations.Library innerProgress.RegisterAction(pct => { - double innerPercent = currentNumComplete * 100 + pct; + double innerPercent = pct; + innerPercent /= 100; + innerPercent += currentNumComplete; + innerPercent /= numTasks; + innerPercent *= 100; + progress.Report(innerPercent); }); @@ -1163,7 +1191,19 @@ namespace Emby.Server.Implementations.Library Locations = _fileSystem.GetFilePaths(dir, false) .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) - .Select(_fileSystem.ResolveShortcut) + .Select(i => + { + try + { + return _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(i)); + } + catch (Exception ex) + { + _logger.ErrorException("Error resolving shortcut file {0}", ex, i); + return null; + } + }) + .Where(i => i != null) .OrderBy(i => i) .ToArray(), @@ -1197,7 +1237,7 @@ namespace Emby.Server.Implementations.Library { return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false) .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } /// @@ -1208,7 +1248,7 @@ namespace Emby.Server.Implementations.Library /// id public BaseItem GetItemById(Guid id) { - if (id == Guid.Empty) + if (id.Equals(Guid.Empty)) { throw new ArgumentNullException("id"); } @@ -1234,9 +1274,9 @@ namespace Emby.Server.Implementations.Library public List GetItemList(InternalItemsQuery query, bool allowExternalContent) { - if (query.Recursive && query.ParentId.HasValue) + if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) { - var parent = GetItemById(query.ParentId.Value); + var parent = GetItemById(query.ParentId); if (parent != null) { SetTopParentIdsOrAncestors(query, new List { parent }); @@ -1258,9 +1298,9 @@ namespace Emby.Server.Implementations.Library public int GetCount(InternalItemsQuery query) { - if (query.Recursive && query.ParentId.HasValue) + if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) { - var parent = GetItemById(query.ParentId.Value); + var parent = GetItemById(query.ParentId); if (parent != null) { SetTopParentIdsOrAncestors(query, new List { parent }); @@ -1391,7 +1431,7 @@ namespace Emby.Server.Implementations.Library return; } - var parents = query.AncestorIds.Select(i => GetItemById(new Guid(i))).ToList(); + var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); if (parents.All(i => { @@ -1406,13 +1446,13 @@ namespace Emby.Server.Implementations.Library })) { // Optimize by querying against top level views - query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).Select(i => i.ToString("N")).ToArray(); - query.AncestorIds = new string[] { }; + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); + query.AncestorIds = Array.Empty(); // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid().ToString("N") }; + query.TopParentIds = new[] { Guid.NewGuid() }; } } } @@ -1430,9 +1470,9 @@ namespace Emby.Server.Implementations.Library public QueryResult GetItemsResult(InternalItemsQuery query) { - if (query.Recursive && query.ParentId.HasValue) + if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) { - var parent = GetItemById(query.ParentId.Value); + var parent = GetItemById(query.ParentId); if (parent != null) { SetTopParentIdsOrAncestors(query, new List { parent }); @@ -1472,23 +1512,23 @@ namespace Emby.Server.Implementations.Library })) { // Optimize by querying against top level views - query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).Select(i => i.ToString("N")).ToArray(); + query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid().ToString("N") }; + query.TopParentIds = new[] { Guid.NewGuid() }; } } else { // We need to be able to query from any arbitrary ancestor up the tree - query.AncestorIds = parents.SelectMany(i => i.GetIdsForAncestorQuery()).Select(i => i.ToString("N")).ToArray(); + query.AncestorIds = parents.SelectMany(i => i.GetIdsForAncestorQuery()).ToArray(); // Prevent searching in all libraries due to empty filter if (query.AncestorIds.Length == 0) { - query.AncestorIds = new[] { Guid.NewGuid().ToString("N") }; + query.AncestorIds = new[] { Guid.NewGuid() }; } } @@ -1498,22 +1538,21 @@ namespace Emby.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) { if (query.AncestorIds.Length == 0 && - !query.ParentId.HasValue && + query.ParentId.Equals(Guid.Empty) && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && - string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) && - string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey) && + string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && + string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) && query.ItemIds.Length == 0) { var userViews = _userviewManager().GetUserViews(new UserViewQuery { - UserId = user.Id.ToString("N"), + UserId = user.Id, IncludeHidden = true, IncludeExternalContent = allowExternalContent + }); - }, CancellationToken.None).Result; - - query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).Select(i => i.ToString("N")).ToArray(); + query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).ToArray(); } } @@ -1527,48 +1566,38 @@ namespace Emby.Server.Implementations.Library { return new[] { view.Id }; } - if (string.Equals(view.ViewType, CollectionType.Channels)) - { - var channelResult = BaseItem.ChannelManager.GetChannelsInternal(new ChannelQuery - { - UserId = user.Id.ToString("N") - - }, CancellationToken.None).Result; - - return channelResult.Items.Select(i => i.Id); - } // Translate view into folders - if (view.DisplayParentId != Guid.Empty) + if (!view.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.DisplayParentId); if (displayParent != null) { return GetTopParentIdsForQuery(displayParent, user); } - return new Guid[] { }; + return Array.Empty(); } - if (view.ParentId != Guid.Empty) + if (!view.ParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.ParentId); if (displayParent != null) { return GetTopParentIdsForQuery(displayParent, user); } - return new Guid[] { }; + return Array.Empty(); } // Handle grouping - if (user != null && !string.IsNullOrWhiteSpace(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) + if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) { - return user.RootFolder + return GetUserRootFolder() .GetChildren(user, true) .OfType() - .Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase)) + .Where(i => string.IsNullOrEmpty(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase)) .Where(i => user.IsFolderGrouped(i.Id)) .SelectMany(i => GetTopParentIdsForQuery(i, user)); } - return new Guid[] { }; + return Array.Empty(); } var collectionFolder = item as CollectionFolder; @@ -1582,7 +1611,7 @@ namespace Emby.Server.Implementations.Library { return new[] { topParent.Id }; } - return new Guid[] { }; + return Array.Empty(); } /// @@ -1737,7 +1766,7 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } - public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderByList) + public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderByList) { var isFirst = true; @@ -1802,9 +1831,9 @@ namespace Emby.Server.Implementations.Library /// The item. /// The cancellation token. /// Task. - public void CreateItem(BaseItem item, CancellationToken cancellationToken) + public void CreateItem(BaseItem item, BaseItem parent) { - CreateItems(new[] { item }, item.GetParent(), cancellationToken); + CreateItems(new[] { item }, parent, CancellationToken.None); } /// @@ -1828,6 +1857,12 @@ namespace Emby.Server.Implementations.Library { foreach (var item in list) { + // With the live tv guide this just creates too much noise + if (item.SourceType != SourceType.Library) + { + continue; + } + try { ItemAdded(this, new ItemChangeEventArgs @@ -1851,6 +1886,55 @@ namespace Emby.Server.Implementations.Library RegisterItem(item); } + /// + /// Updates the item. + /// + public void UpdateItems(List items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + { + foreach (var item in items) + { + if (item.IsFileProtocol) + { + _providerManagerFactory().SaveMetadata(item, updateReason); + } + + item.DateLastSaved = DateTime.UtcNow; + + RegisterItem(item); + } + + //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; + //_logger.Debug("Saving {0} to database.", logName); + + ItemRepository.SaveItems(items, cancellationToken); + + if (ItemUpdated != null) + { + foreach (var item in items) + { + // With the live tv guide this just creates too much noise + if (item.SourceType != SourceType.Library) + { + continue; + } + + try + { + ItemUpdated(this, new ItemChangeEventArgs + { + Item = item, + Parent = parent, + UpdateReason = updateReason + }); + } + catch (Exception ex) + { + _logger.ErrorException("Error in ItemUpdated event handler", ex); + } + } + } + } + /// /// Updates the item. /// @@ -1858,39 +1942,9 @@ namespace Emby.Server.Implementations.Library /// The update reason. /// The cancellation token. /// Task. - public void UpdateItem(BaseItem item, ItemUpdateType updateReason, CancellationToken cancellationToken) + public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - var locationType = item.LocationType; - if (locationType != LocationType.Remote && locationType != LocationType.Virtual) - { - _providerManagerFactory().SaveMetadata(item, updateReason); - } - - item.DateLastSaved = DateTime.UtcNow; - - var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; - _logger.Debug("Saving {0} to database.", logName); - - ItemRepository.SaveItem(item, cancellationToken); - - RegisterItem(item); - - if (ItemUpdated != null) - { - try - { - ItemUpdated(this, new ItemChangeEventArgs - { - Item = item, - Parent = item.GetParent(), - UpdateReason = updateReason - }); - } - catch (Exception ex) - { - _logger.ErrorException("Error in ItemUpdated event handler", ex); - } - } + UpdateItems(new List { item }, parent, updateReason, cancellationToken); } /// @@ -1995,12 +2049,12 @@ namespace Emby.Server.Implementations.Library public string GetContentType(BaseItem item) { string configuredContentType = GetConfiguredContentType(item, false); - if (!string.IsNullOrWhiteSpace(configuredContentType)) + if (!string.IsNullOrEmpty(configuredContentType)) { return configuredContentType; } configuredContentType = GetConfiguredContentType(item, true); - if (!string.IsNullOrWhiteSpace(configuredContentType)) + if (!string.IsNullOrEmpty(configuredContentType)) { return configuredContentType; } @@ -2011,14 +2065,14 @@ namespace Emby.Server.Implementations.Library { var type = GetTopFolderContentType(item); - if (!string.IsNullOrWhiteSpace(type)) + if (!string.IsNullOrEmpty(type)) { return type; } return item.GetParents() .Select(GetConfiguredContentType) - .LastOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .LastOrDefault(i => !string.IsNullOrEmpty(i)); } public string GetConfiguredContentType(BaseItem item) @@ -2043,7 +2097,7 @@ namespace Emby.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); + var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); if (nameValuePair != null) { return nameValuePair.Value; @@ -2058,16 +2112,21 @@ namespace Emby.Server.Implementations.Library return null; } - while (!(item.GetParent() is AggregateFolder) && item.GetParent() != null) + while (!item.ParentId.Equals(Guid.Empty)) { - item = item.GetParent(); + var parent = item.GetParent(); + if (parent == null || parent is AggregateFolder) + { + break; + } + item = parent; } return GetUserRootFolder().Children .OfType() .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path)) .Select(i => i.CollectionType) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); @@ -2076,18 +2135,16 @@ namespace Emby.Server.Implementations.Library public UserView GetNamedView(User user, string name, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { - return GetNamedView(user, name, null, viewType, sortName, cancellationToken); + return GetNamedView(user, name, Guid.Empty, viewType, sortName); } public UserView GetNamedView(string name, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, "views"); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views"); path = Path.Combine(path, _fileSystem.GetValidFilename(viewType)); @@ -2111,32 +2168,15 @@ namespace Emby.Server.Implementations.Library ForcedSortName = sortName }; - CreateItem(item, cancellationToken); + CreateItem(item, null); refresh = true; } - if (!refresh) - { - refresh = DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - } - - if (!refresh && item.DisplayParentId != Guid.Empty) - { - var displayParent = GetItemById(item.DisplayParentId); - refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; - } - if (refresh) { item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem) - { - // Not sure why this is necessary but need to figure it out - // View images are not getting utilized without this - ForceSave = true - - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Normal); } return item; @@ -2144,12 +2184,12 @@ namespace Emby.Server.Implementations.Library public UserView GetNamedView(User user, string name, - string parentId, + Guid parentId, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { - var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty) + (viewType ?? string.Empty); + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); + var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); var id = GetNewItemId(idValues, typeof(UserView)); @@ -2174,19 +2214,16 @@ namespace Emby.Server.Implementations.Library UserId = user.Id }; - if (!string.IsNullOrWhiteSpace(parentId)) - { - item.DisplayParentId = new Guid(parentId); - } + item.DisplayParentId = parentId; - CreateItem(item, cancellationToken); + CreateItem(item, null); isNew = true; } var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && item.DisplayParentId != Guid.Empty) + if (!refresh && !item.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2207,8 +2244,7 @@ namespace Emby.Server.Implementations.Library public UserView GetShadowView(BaseItem parent, string viewType, - string sortName, - CancellationToken cancellationToken) + string sortName) { if (parent == null) { @@ -2244,14 +2280,14 @@ namespace Emby.Server.Implementations.Library item.DisplayParentId = parentId; - CreateItem(item, cancellationToken); + CreateItem(item, null); isNew = true; } var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && item.DisplayParentId != Guid.Empty) + if (!refresh && !item.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2271,19 +2307,19 @@ namespace Emby.Server.Implementations.Library } public UserView GetNamedView(string name, - string parentId, + Guid parentId, string viewType, string sortName, - string uniqueId, - CancellationToken cancellationToken) + string uniqueId) { - if (string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } - var idValues = "37_namedview_" + name + (parentId ?? string.Empty) + (viewType ?? string.Empty); - if (!string.IsNullOrWhiteSpace(uniqueId)) + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); + var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); + if (!string.IsNullOrEmpty(uniqueId)) { idValues += uniqueId; } @@ -2310,12 +2346,9 @@ namespace Emby.Server.Implementations.Library ForcedSortName = sortName }; - if (!string.IsNullOrWhiteSpace(parentId)) - { - item.DisplayParentId = new Guid(parentId); - } + item.DisplayParentId = parentId; - CreateItem(item, cancellationToken); + CreateItem(item, null); isNew = true; } @@ -2323,12 +2356,12 @@ namespace Emby.Server.Implementations.Library if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase)) { item.ViewType = viewType; - item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken); + item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; - if (!refresh && item.DisplayParentId != Guid.Empty) + if (!refresh && !item.DisplayParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(item.DisplayParentId); refresh = displayParent != null && displayParent.DateLastSaved > item.DateLastRefreshed; @@ -2346,6 +2379,13 @@ namespace Emby.Server.Implementations.Library return item; } + public void AddExternalSubtitleStreams(List streams, + string videoPath, + string[] files) + { + new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); + } + public bool IsVideoFile(string path, LibraryOptions libraryOptions) { var resolver = new VideoResolver(GetNamingOptions()); @@ -2370,19 +2410,25 @@ namespace Emby.Server.Implementations.Library public int? GetSeasonNumberFromPath(string path) { - return new SeasonPathParser(GetNamingOptions(), new RegexProvider()).Parse(path, true, true).SeasonNumber; + return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber; } - public bool FillMissingEpisodeNumbersFromPath(Episode episode) + public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { + var series = episode.Series; + bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); + if (!isAbsoluteNaming.Value) + { + // In other words, no filter applied + isAbsoluteNaming = null; + } + var resolver = new EpisodeResolver(GetNamingOptions()); var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; - var locationType = episode.LocationType; - - var episodeInfo = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? - resolver.Resolve(episode.Path, isFolder) : + var episodeInfo = episode.IsFileProtocol ? + resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) : new Emby.Naming.TV.EpisodeInfo(); if (episodeInfo == null) @@ -2428,62 +2474,49 @@ namespace Emby.Server.Implementations.Library changed = true; } } - - if (!episode.ParentIndexNumber.HasValue) - { - var season = episode.Season; - - if (season != null) - { - episode.ParentIndexNumber = season.IndexNumber; - } - - if (episode.ParentIndexNumber.HasValue) - { - changed = true; - } - } } else { - if (!episode.IndexNumber.HasValue) + if (!episode.IndexNumber.HasValue || forceRefresh) { + if (episode.IndexNumber != episodeInfo.EpisodeNumber) + { + changed = true; + } episode.IndexNumber = episodeInfo.EpisodeNumber; + } - if (episode.IndexNumber.HasValue) + if (!episode.IndexNumberEnd.HasValue || forceRefresh) + { + if (episode.IndexNumberEnd != episodeInfo.EndingEpsiodeNumber) { changed = true; } - } - - if (!episode.IndexNumberEnd.HasValue) - { episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; - - if (episode.IndexNumberEnd.HasValue) - { - changed = true; - } } - if (!episode.ParentIndexNumber.HasValue) + if (!episode.ParentIndexNumber.HasValue || forceRefresh) { - episode.ParentIndexNumber = episodeInfo.SeasonNumber; - - if (!episode.ParentIndexNumber.HasValue) - { - var season = episode.Season; - - if (season != null) - { - episode.ParentIndexNumber = season.IndexNumber; - } - } - - if (episode.ParentIndexNumber.HasValue) + if (episode.ParentIndexNumber != episodeInfo.SeasonNumber) { changed = true; } + episode.ParentIndexNumber = episodeInfo.SeasonNumber; + } + } + + if (!episode.ParentIndexNumber.HasValue) + { + var season = episode.Season; + + if (season != null) + { + episode.ParentIndexNumber = season.IndexNumber; + } + + if (episode.ParentIndexNumber.HasValue) + { + changed = true; } } @@ -2492,41 +2525,16 @@ namespace Emby.Server.Implementations.Library public NamingOptions GetNamingOptions() { - return GetNamingOptions(true); - } - - public NamingOptions GetNamingOptions(bool allowOptimisticEpisodeDetection) - { - if (!allowOptimisticEpisodeDetection) - { - if (_namingOptionsWithoutOptimisticEpisodeDetection == null) - { - var namingOptions = new ExtendedNamingOptions(); - - InitNamingOptions(namingOptions); - namingOptions.EpisodeExpressions = namingOptions.EpisodeExpressions - .Where(i => i.IsNamed && !i.IsOptimistic) - .ToList(); - - _namingOptionsWithoutOptimisticEpisodeDetection = namingOptions; - } - - return _namingOptionsWithoutOptimisticEpisodeDetection; - } - return GetNamingOptionsInternal(); } - private NamingOptions _namingOptionsWithoutOptimisticEpisodeDetection; private NamingOptions _namingOptions; private string[] _videoFileExtensions; private NamingOptions GetNamingOptionsInternal() { if (_namingOptions == null) { - var options = new ExtendedNamingOptions(); - - InitNamingOptions(options); + var options = new NamingOptions(); _namingOptions = options; _videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray(); @@ -2535,27 +2543,6 @@ namespace Emby.Server.Implementations.Library return _namingOptions; } - private void InitNamingOptions(NamingOptions options) - { - // These cause apps to have problems - options.AudioFileExtensions.Remove(".m3u"); - options.AudioFileExtensions.Remove(".wpl"); - - //if (!libraryOptions.EnableArchiveMediaFiles) - { - options.AudioFileExtensions.Remove(".rar"); - options.AudioFileExtensions.Remove(".zip"); - } - - //if (!libraryOptions.EnableArchiveMediaFiles) - { - options.VideoFileExtensions.Remove(".rar"); - options.VideoFileExtensions.Remove(".zip"); - } - - options.VideoFileExtensions.Add(".tp"); - } - public ItemLookupInfo ParseName(string name) { var resolver = new VideoResolver(GetNamingOptions()); @@ -2606,12 +2593,11 @@ namespace Emby.Server.Implementations.Library { video = dbItem; } - else - { - // item is new - video.ExtraType = ExtraType.Trailer; - } - video.TrailerTypes = new List { TrailerType.LocalTrailer }; + + video.ParentId = Guid.Empty; + video.OwnerId = owner.Id; + video.ExtraType = ExtraType.Trailer; + video.TrailerTypes = new [] { TrailerType.LocalTrailer }; return video; @@ -2625,7 +2611,7 @@ namespace Emby.Server.Implementations.Library { var namingOptions = GetNamingOptions(); - var files = fileSystemChildren.Where(i => i.IsDirectory) + var files = owner.IsInMixedFolder ? new List() : fileSystemChildren.Where(i => i.IsDirectory) .Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); @@ -2653,6 +2639,9 @@ namespace Emby.Server.Implementations.Library video = dbItem; } + video.ParentId = Guid.Empty; + video.OwnerId = owner.Id; + SetExtraTypeFromFilename(video); return video; @@ -2756,7 +2745,7 @@ namespace Emby.Server.Implementations.Library private void SetExtraTypeFromFilename(Video item) { - var resolver = new ExtraResolver(GetNamingOptions(), new RegexProvider()); + var resolver = new ExtraResolver(GetNamingOptions()); var result = resolver.GetExtraInfo(item.Path); @@ -2841,7 +2830,7 @@ namespace Emby.Server.Implementations.Library ItemRepository.UpdatePeople(item.Id, people); } - public async Task ConvertImageToLocal(IHasMetadata item, ItemImageInfo image, int imageIndex) + public async Task ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) { foreach (var url in image.Path.Split('|')) { @@ -2872,7 +2861,7 @@ namespace Emby.Server.Implementations.Library throw new InvalidOperationException(); } - public void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary) + public async Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) { @@ -2910,7 +2899,7 @@ namespace Emby.Server.Implementations.Library { var path = Path.Combine(virtualFolderPath, collectionType + ".collection"); - _fileSystem.WriteAllBytes(path, new byte[] { }); + _fileSystem.WriteAllBytes(path, Array.Empty()); } CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); @@ -2925,26 +2914,30 @@ namespace Emby.Server.Implementations.Library } finally { - Task.Run(() => + if (refreshLibrary) { - // No need to start if scanning the library because it will handle it - if (refreshLibrary) - { - ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); + await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); - _libraryMonitorFactory().Start(); - } - }); + StartScanInBackground(); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitorFactory().Start(); + } } } + private void StartScanInBackground() + { + Task.Run(() => + { + // No need to start if scanning the library because it will handle it + ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); + }); + } + private bool ValidateNetworkPath(string path) { //if (Environment.OSVersion.Platform == PlatformID.Win32NT) @@ -3003,7 +2996,7 @@ namespace Emby.Server.Implementations.Library lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); } - _fileSystem.CreateShortcut(lnk, path); + _fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path)); RemoveContentTypeOverrides(path); @@ -3079,7 +3072,7 @@ namespace Emby.Server.Implementations.Library } } - public void RemoveVirtualFolder(string name, bool refreshLibrary) + public async Task RemoveVirtualFolder(string name, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) { @@ -3103,23 +3096,20 @@ namespace Emby.Server.Implementations.Library } finally { - Task.Run(() => - { - // No need to start if scanning the library because it will handle it - if (refreshLibrary) - { - ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); + CollectionFolder.OnCollectionFolderChange(); - _libraryMonitorFactory().Start(); - } - }); + if (refreshLibrary) + { + await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); + + StartScanInBackground(); + } + else + { + // Need to add a delay here or directory watchers may still pick up the changes + await Task.Delay(1000).ConfigureAwait(false); + _libraryMonitorFactory().Start(); + } } } @@ -3157,7 +3147,7 @@ namespace Emby.Server.Implementations.Library public void RemoveMediaPath(string virtualFolderName, string mediaPath) { - if (string.IsNullOrWhiteSpace(mediaPath)) + if (string.IsNullOrEmpty(mediaPath)) { throw new ArgumentNullException("mediaPath"); } @@ -3172,7 +3162,7 @@ namespace Emby.Server.Implementations.Library var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true) .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase)) - .FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(f => _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(f)).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(shortcut)) { diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs new file mode 100644 index 0000000000..e027e133fb --- /dev/null +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -0,0 +1,181 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using System.Collections.Generic; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Common.Configuration; +using System.IO; +using MediaBrowser.Common.Extensions; + +namespace Emby.Server.Implementations.Library +{ + public class LiveStreamHelper + { + private readonly IMediaEncoder _mediaEncoder; + private readonly ILogger _logger; + + private IJsonSerializer _json; + private IApplicationPaths _appPaths; + + public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths) + { + _mediaEncoder = mediaEncoder; + _logger = logger; + _json = json; + _appPaths = appPaths; + } + + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, CancellationToken cancellationToken) + { + var originalRuntime = mediaSource.RunTimeTicks; + + var now = DateTime.UtcNow; + + MediaInfo mediaInfo = null; + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + + if (!string.IsNullOrEmpty(cacheKey)) + { + try + { + mediaInfo = _json.DeserializeFromFile(cacheFilePath); + + //_logger.Debug("Found cached media info"); + } + catch + { + } + } + + if (mediaInfo == null) + { + if (addProbeDelay) + { + var delayMs = mediaSource.AnalyzeDurationMs ?? 0; + delayMs = Math.Max(3000, delayMs); + if (delayMs > 0) + { + _logger.Info("Waiting {0}ms before probing the live stream", delayMs); + await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); + } + } + + mediaSource.AnalyzeDurationMs = 3000; + + mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + + }, cancellationToken).ConfigureAwait(false); + + if (cacheFilePath != null) + { + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); + _json.SerializeToFile(mediaInfo, cacheFilePath); + + //_logger.Debug("Saved media info to {0}", cacheFilePath); + } + } + + var mediaStreams = mediaInfo.MediaStreams; + + if (!string.IsNullOrEmpty(cacheKey)) + { + var newList = new List(); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1)); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1)); + + foreach (var stream in newList) + { + stream.Index = -1; + stream.Language = null; + } + + mediaStreams = newList; + } + + _logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + mediaSource.Bitrate = mediaInfo.Bitrate; + mediaSource.Container = mediaInfo.Container; + mediaSource.Formats = mediaInfo.Formats; + mediaSource.MediaStreams = mediaStreams; + mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks; + mediaSource.Size = mediaInfo.Size; + mediaSource.Timestamp = mediaInfo.Timestamp; + mediaSource.Video3DFormat = mediaInfo.Video3DFormat; + mediaSource.VideoType = mediaInfo.VideoType; + + mediaSource.DefaultSubtitleStreamIndex = null; + + // Null this out so that it will be treated like a live stream + if (!originalRuntime.HasValue) + { + mediaSource.RunTimeTicks = null; + } + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + + if (audioStream == null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + if (videoStream != null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + + // This is coming up false and preventing stream copy + videoStream.IsAVC = null; + } + + mediaSource.AnalyzeDurationMs = 3000; + + // Try to estimate this + mediaSource.InferTotalBitrate(true); + } + + public Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, bool addProbeDelay, CancellationToken cancellationToken) + { + return AddMediaInfoWithProbe(mediaSource, isAudio, null, addProbeDelay, cancellationToken); + } + } +} diff --git a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs deleted file mode 100644 index 4830da8fcf..0000000000 --- a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs +++ /dev/null @@ -1,105 +0,0 @@ -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; - -namespace Emby.Server.Implementations.Library -{ - public class LocalTrailerPostScanTask : ILibraryPostScanTask - { - private readonly ILibraryManager _libraryManager; - private readonly IChannelManager _channelManager; - - public LocalTrailerPostScanTask(ILibraryManager libraryManager, IChannelManager channelManager) - { - _libraryManager = libraryManager; - _channelManager = channelManager; - } - - public async Task Run(IProgress progress, CancellationToken cancellationToken) - { - var items = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(BoxSet).Name, typeof(Game).Name, typeof(Movie).Name, typeof(Series).Name }, - Recursive = true, - DtoOptions = new DtoOptions(true) - - }).OfType().ToList(); - - var trailerTypes = Enum.GetNames(typeof(TrailerType)) - .Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true)) - .Except(new[] { TrailerType.LocalTrailer }) - .ToArray(); - - var trailers = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Trailer).Name }, - TrailerTypes = trailerTypes, - Recursive = true, - DtoOptions = new DtoOptions(false) - - }); - - var numComplete = 0; - - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); - - AssignTrailers(item, trailers); - - numComplete++; - double percent = numComplete; - percent /= items.Count; - progress.Report(percent * 100); - } - - progress.Report(100); - } - - private void AssignTrailers(IHasTrailers item, IEnumerable channelTrailers) - { - if (item is Game) - { - return; - } - - var imdbId = item.GetProviderId(MetadataProviders.Imdb); - var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); - - var trailers = channelTrailers.Where(i => - { - if (!string.IsNullOrWhiteSpace(imdbId) && - string.Equals(imdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (!string.IsNullOrWhiteSpace(tmdbId) && - string.Equals(tmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - return false; - }); - - var trailerIds = trailers.Select(i => i.Id) - .ToArray(); - - if (!trailerIds.SequenceEqual(item.RemoteTrailerIds)) - { - item.RemoteTrailerIds = trailerIds; - - var baseItem = (BaseItem)item; - baseItem.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - } - } - } -} diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 688da57649..0dc436800f 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; @@ -16,6 +17,11 @@ using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using System.IO; +using System.Globalization; +using MediaBrowser.Common.Configuration; namespace Emby.Server.Implementations.Library { @@ -31,8 +37,11 @@ namespace Emby.Server.Implementations.Library private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly ITimerFactory _timerFactory; + private readonly Func _mediaEncoder; + private ILocalizationManager _localizationManager; + private IApplicationPaths _appPaths; - public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager, ITimerFactory timerFactory) + public MediaSourceManager(IItemRepository itemRepo, IApplicationPaths applicationPaths, ILocalizationManager localizationManager, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager, ITimerFactory timerFactory, Func mediaEncoder) { _itemRepo = itemRepo; _userManager = userManager; @@ -42,6 +51,9 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _userDataManager = userDataManager; _timerFactory = timerFactory; + _mediaEncoder = mediaEncoder; + _localizationManager = localizationManager; + _appPaths = applicationPaths; } public void AddParts(IEnumerable providers) @@ -109,20 +121,23 @@ namespace Emby.Server.Implementations.Library return streams; } - public async Task> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, string[] supportedLiveMediaTypes, CancellationToken cancellationToken) + public async Task> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { - var item = _libraryManager.GetItemById(id); + var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); - var hasMediaSources = (IHasMediaSources)item; - User user = null; - - if (!string.IsNullOrWhiteSpace(userId)) + if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video)) { - user = _userManager.GetUserById(userId); + await item.RefreshMetadata(new MediaBrowser.Controller.Providers.MetadataRefreshOptions(_fileSystem) + { + EnableRemoteContentProbe = true, + MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh + + }, cancellationToken).ConfigureAwait(false); + + mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); } - var mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); - var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false); + var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false); var list = new List(); @@ -132,24 +147,13 @@ namespace Emby.Server.Implementations.Library { if (user != null) { - SetUserProperties(hasMediaSources, source, user); + SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); } - if (source.Protocol == MediaProtocol.File) + + // Validate that this is actually possible + if (source.SupportsDirectStream) { - // TODO: Path substitution - if (!_fileSystem.FileExists(source.Path)) - { - source.SupportsDirectStream = false; - } - } - else if (source.Protocol == MediaProtocol.Http) - { - // TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash - source.SupportsDirectStream = false; - } - else - { - source.SupportsDirectStream = false; + source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol); } list.Add(source); @@ -169,10 +173,63 @@ namespace Emby.Server.Implementations.Library } } - return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder); + return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList(); } - private async Task> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken) + public MediaProtocol GetPathProtocol(string path) + { + if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtsp; + } + if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtmp; + } + if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Http; + } + if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtp; + } + if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Ftp; + } + if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Udp; + } + + return _fileSystem.IsPathFile(path) ? MediaProtocol.File : MediaProtocol.Http; + } + + public bool SupportsDirectStream(string path, MediaProtocol protocol) + { + if (protocol == MediaProtocol.File) + { + return true; + } + + if (protocol == MediaProtocol.Http) + { + if (path != null) + { + if (path.IndexOf(".m3u", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + + return true; + } + } + + return false; + } + + private async Task> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken) { var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -180,7 +237,7 @@ namespace Emby.Server.Implementations.Library return results.SelectMany(i => i.ToList()); } - private async Task> GetDynamicMediaSources(IHasMediaSources item, IMediaSourceProvider provider, CancellationToken cancellationToken) + private async Task> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken) { try { @@ -207,78 +264,65 @@ namespace Emby.Server.Implementations.Library { var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter; - if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { mediaSource.OpenToken = prefix + mediaSource.OpenToken; } - if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId; } } - public async Task GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) + public async Task GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) { - if (!string.IsNullOrWhiteSpace(liveStreamId)) + if (!string.IsNullOrEmpty(liveStreamId)) { return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false); } - //await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - //try - //{ - // var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - - // if (stream != null) - // { - // return stream.MediaSource; - // } - //} - //finally - //{ - // _liveStreamSemaphore.Release(); - //} - - var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video }, - CancellationToken.None).ConfigureAwait(false); + var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false); return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - public List GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null) + public List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null) { if (item == null) { throw new ArgumentNullException("item"); } - if (!(item is Video)) - { - return item.GetMediaSources(enablePathSubstitution); - } + var hasMediaSources = (IHasMediaSources)item; - var sources = item.GetMediaSources(enablePathSubstitution); + var sources = hasMediaSources.GetMediaSources(enablePathSubstitution); if (user != null) { foreach (var source in sources) { - SetUserProperties(item, source, user); + SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); } } return sources; } - private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user) + private string[] NormalizeLanguage(string language) { - var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + if (language != null) + { + var culture = _localizationManager.FindLanguageInfo(language); + if (culture != null) + { + return culture.ThreeLetterISOLanguageNames; + } - var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + return new string[] { language }; + } - SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); - SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); + return Array.Empty(); } private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) @@ -293,9 +337,9 @@ namespace Emby.Server.Implementations.Library return; } } - + var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? new List() : new List { user.Configuration.SubtitleLanguagePreference }; + ? Array.Empty() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null @@ -325,12 +369,37 @@ namespace Emby.Server.Implementations.Library } var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) - ? new string[] { } - : new[] { user.Configuration.AudioLanguagePreference }; + ? Array.Empty() + : NormalizeLanguage(user.Configuration.AudioLanguagePreference); source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); } + public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) + { + // Item would only be null if the app didn't supply ItemId as part of the live stream open request + var mediaType = item == null ? MediaType.Video : item.MediaType; + + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + + var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + + SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); + SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); + } + else if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audio != null) + { + source.DefaultAudioStreamIndex = audio.Index; + } + } + } + private IEnumerable SortMediaSources(IEnumerable sources) { return sources.OrderBy(i => @@ -352,55 +421,157 @@ namespace Emby.Server.Implementations.Library .ToList(); } - private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) + public async Task> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + MediaSourceInfo mediaSource; + ILiveStream liveStream; + try { var tuple = GetProvider(request.OpenToken); var provider = tuple.Item1; - var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, request.EnableMediaProbe, cancellationToken).ConfigureAwait(false); + var currentLiveStreams = _openStreams.Values.ToList(); - var mediaSource = mediaSourceTuple.Item1; + liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) + mediaSource = liveStream.MediaSource; + + // Validate that this is actually possible + if (mediaSource.SupportsDirectStream) { - throw new InvalidOperationException(string.Format("{0} returned null LiveStreamId", provider.GetType().Name)); + mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol); } SetKeyProperties(provider, mediaSource); - var info = new LiveStreamInfo - { - Id = mediaSource.LiveStreamId, - MediaSource = mediaSource, - DirectStreamProvider = mediaSourceTuple.Item2 - }; + _openStreams[mediaSource.LiveStreamId] = liveStream; + } + finally + { + _liveStreamSemaphore.Release(); + } - _openStreams[mediaSource.LiveStreamId] = info; + // TODO: Don't hardcode this + var isAudio = false; - var json = _jsonSerializer.SerializeToString(mediaSource); - _logger.Debug("Live stream opened: " + json); - var clone = _jsonSerializer.DeserializeFromString(json); - - if (!string.IsNullOrWhiteSpace(request.UserId)) + try + { + if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing) { - var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrWhiteSpace(request.ItemId) - ? null - : _libraryManager.GetItemById(request.ItemId); - SetUserProperties(item, clone, user); + AddMediaInfo(mediaSource, isAudio); } - - return new LiveStreamResponse + else { - MediaSource = clone - }; + // hack - these two values were taken from LiveTVMediaSourceProvider + var cacheKey = request.OpenToken; + + await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error probing live tv stream", ex); + AddMediaInfo(mediaSource, isAudio); + } + + var json = _jsonSerializer.SerializeToString(mediaSource); + _logger.Info("Live stream opened: " + json); + var clone = _jsonSerializer.DeserializeFromString(json); + + if (!request.UserId.Equals(Guid.Empty)) + { + var user = _userManager.GetUserById(request.UserId); + var item = request.ItemId.Equals(Guid.Empty) + ? null + : _libraryManager.GetItemById(request.ItemId); + SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); + } + + return new Tuple(new LiveStreamResponse + { + MediaSource = clone + + }, liveStream as IDirectStreamProvider); + } + + private void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio) + { + mediaSource.DefaultSubtitleStreamIndex = null; + + // Null this out so that it will be treated like a live stream + if (mediaSource.IsInfiniteStream) + { + mediaSource.RunTimeTicks = null; + } + + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + + if (audioStream == null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + if (videoStream != null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + } + + // Try to estimate this + mediaSource.InferTotalBitrate(); + } + + public async Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var info = _openStreams.Values.FirstOrDefault(i => + { + var liveStream = i as ILiveStream; + if (liveStream != null) + { + return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); + } + + return false; + }); + + return info as IDirectStreamProvider; } finally { @@ -408,23 +579,207 @@ namespace Emby.Server.Implementations.Library } } + public async Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) + { + var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false); + return result.Item1; + } + + public async Task GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken) + { + var liveStreamInfo = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false); + + var mediaSource = liveStreamInfo.MediaSource; + + if (liveStreamInfo is IDirectStreamProvider) + { + var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest + { + MediaSource = mediaSource, + ExtractChapters = false, + MediaType = DlnaProfileType.Video + + }, cancellationToken).ConfigureAwait(false); + + mediaSource.MediaStreams = info.MediaStreams; + mediaSource.Container = info.Container; + mediaSource.Bitrate = info.Bitrate; + } + + return mediaSource; + } + + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken) + { + var originalRuntime = mediaSource.RunTimeTicks; + + var now = DateTime.UtcNow; + + MediaInfo mediaInfo = null; + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + + if (!string.IsNullOrEmpty(cacheKey)) + { + try + { + mediaInfo = _jsonSerializer.DeserializeFromFile(cacheFilePath); + + //_logger.Debug("Found cached media info"); + } + catch (Exception ex) + { + } + } + + if (mediaInfo == null) + { + if (addProbeDelay) + { + var delayMs = mediaSource.AnalyzeDurationMs ?? 0; + delayMs = Math.Max(3000, delayMs); + await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); + } + + if (isLiveStream) + { + mediaSource.AnalyzeDurationMs = 3000; + } + + mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + + }, cancellationToken).ConfigureAwait(false); + + if (cacheFilePath != null) + { + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); + _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath); + + //_logger.Debug("Saved media info to {0}", cacheFilePath); + } + } + + var mediaStreams = mediaInfo.MediaStreams; + + if (isLiveStream && !string.IsNullOrEmpty(cacheKey)) + { + var newList = new List(); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1)); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1)); + + foreach (var stream in newList) + { + stream.Index = -1; + stream.Language = null; + } + + mediaStreams = newList; + } + + _logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + mediaSource.Bitrate = mediaInfo.Bitrate; + mediaSource.Container = mediaInfo.Container; + mediaSource.Formats = mediaInfo.Formats; + mediaSource.MediaStreams = mediaStreams; + mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks; + mediaSource.Size = mediaInfo.Size; + mediaSource.Timestamp = mediaInfo.Timestamp; + mediaSource.Video3DFormat = mediaInfo.Video3DFormat; + mediaSource.VideoType = mediaInfo.VideoType; + + mediaSource.DefaultSubtitleStreamIndex = null; + + if (isLiveStream) + { + // Null this out so that it will be treated like a live stream + if (!originalRuntime.HasValue) + { + mediaSource.RunTimeTicks = null; + } + } + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audioStream == null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + if (videoStream != null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + + // This is coming up false and preventing stream copy + videoStream.IsAVC = null; + } + + if (isLiveStream) + { + mediaSource.AnalyzeDurationMs = 3000; + } + + // Try to estimate this + mediaSource.InferTotalBitrate(true); + } + public async Task> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) { - if (string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - _logger.Debug("Getting already opened live stream {0}", id); + var info = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false); + return new Tuple(info.MediaSource, info as IDirectStreamProvider); + } + + private async Task GetLiveStreamInfo(string id, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - LiveStreamInfo info; + ILiveStream info; if (_openStreams.TryGetValue(id, out info)) { - return new Tuple(info.MediaSource, info.DirectStreamProvider); + return info; } else { @@ -443,26 +798,9 @@ namespace Emby.Server.Implementations.Library return result.Item1; } - private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId) - { - _logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name); - - try - { - await provider.CloseMediaSource(streamId).ConfigureAwait(false); - } - catch (NotImplementedException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error closing live stream {0}", ex, streamId); - } - } - public async Task CloseLiveStream(string id) { - if (string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } @@ -471,18 +809,22 @@ namespace Emby.Server.Implementations.Library try { - LiveStreamInfo current; + ILiveStream liveStream; - if (_openStreams.TryGetValue(id, out current)) + if (_openStreams.TryGetValue(id, out liveStream)) { - _openStreams.Remove(id); - current.Closed = true; + liveStream.ConsumerCount--; - if (current.MediaSource.RequiresClosing) + _logger.Info("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liveStream.ConsumerCount); + + if (liveStream.ConsumerCount <= 0) { - var tuple = GetProvider(id); + _openStreams.Remove(id); - await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false); + _logger.Info("Closing live stream {0}", id); + + await liveStream.Close().ConfigureAwait(false); + _logger.Info("Live stream {0} closed successfully", id); } } } @@ -497,7 +839,7 @@ namespace Emby.Server.Implementations.Library private Tuple GetProvider(string key) { - if (string.IsNullOrWhiteSpace(key)) + if (string.IsNullOrEmpty(key)) { throw new ArgumentException("key"); } @@ -518,7 +860,6 @@ namespace Emby.Server.Implementations.Library public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } private readonly object _disposeLock = new object(); @@ -541,13 +882,5 @@ namespace Emby.Server.Implementations.Library } } } - - private class LiveStreamInfo - { - public string Id; - public bool Closed; - public MediaSourceInfo MediaSource; - public IDirectStreamProvider DirectStreamProvider; - } } } \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs similarity index 81% rename from MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs rename to Emby.Server.Implementations/Library/MediaStreamSelector.cs index 8169cc7d9f..5d4c5a452a 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -4,13 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MediaBrowser.Controller.MediaEncoding +namespace Emby.Server.Implementations.Library { public static class MediaStreamSelector { - public static int? GetDefaultAudioStreamIndex(List streams, IEnumerable preferredLanguages, bool preferDefaultTrack) + public static int? GetDefaultAudioStreamIndex(List streams, string[] preferredLanguages, bool preferDefaultTrack) { - streams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages.ToList()) + streams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages) .ToList(); if (preferDefaultTrack) @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.MediaEncoding } public static int? GetDefaultSubtitleStreamIndex(List streams, - List preferredLanguages, + string[] preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { @@ -57,9 +57,9 @@ namespace MediaBrowser.Controller.MediaEncoding streams.FirstOrDefault(s => s.IsDefault); // if the audio language is not understood by the user, load their preferred subs, if there are any - if (stream == null && !ContainsOrdinal(preferredLanguages, audioTrackLanguage)) + if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)); + stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Smart) @@ -67,10 +67,10 @@ namespace MediaBrowser.Controller.MediaEncoding // Prefer smart logic over embedded metadata // if the audio language is not understood by the user, load their preferred subs, if there are any - if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage)) + if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)) ?? - streams.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)); + stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ?? + streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)); } } else if (mode == SubtitlePlaybackMode.Always) @@ -96,18 +96,13 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - private static bool ContainsOrdinal(IEnumerable list, string item) - { - return list.Any(i => string.Equals(i, item, StringComparison.OrdinalIgnoreCase)); - } - - private static IEnumerable GetSortedStreams(IEnumerable streams, MediaStreamType type, List languagePreferences) + private static IEnumerable GetSortedStreams(IEnumerable streams, MediaStreamType type, string[] languagePreferences) { // Give some preferance to external text subs for better performance return streams.Where(i => i.Type == type) .OrderBy(i => { - var index = languagePreferences.FindIndex(l => string.Equals(i.Language, l, StringComparison.OrdinalIgnoreCase)); + var index = FindIndex(languagePreferences, i.Language); return index == -1 ? 100 : index; }) @@ -119,7 +114,7 @@ namespace MediaBrowser.Controller.MediaEncoding } public static void SetSubtitleStreamScores(List streams, - List preferredLanguages, + string[] preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { @@ -136,18 +131,15 @@ namespace MediaBrowser.Controller.MediaEncoding if (mode == SubtitlePlaybackMode.Default) { // Prefer embedded metadata over smart logic - filteredStreams = streams.Where(s => s.IsForced || s.IsDefault) .ToList(); } else if (mode == SubtitlePlaybackMode.Smart) { // Prefer smart logic over embedded metadata - - // if the audio language is not understood by the user, load their preferred subs, if there are any - if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage)) + if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase)) { - filteredStreams = streams.Where(s => !s.IsForced && ContainsOrdinal(preferredLanguages, s.Language)) + filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) .ToList(); } } @@ -177,11 +169,24 @@ namespace MediaBrowser.Controller.MediaEncoding } } - private static int GetSubtitleScore(MediaStream stream, List languagePreferences) + private static int FindIndex(string[] list, string value) + { + for (var i=0; i< list.Length; i++) + { + if (string.Equals(list[i], value, StringComparison.OrdinalIgnoreCase)) + { + return i; + } + } + + return -1; + } + + private static int GetSubtitleScore(MediaStream stream, string[] languagePreferences) { var values = new List(); - var index = languagePreferences.FindIndex(l => string.Equals(stream.Language, l, StringComparison.OrdinalIgnoreCase)); + var index = FindIndex(languagePreferences, stream.Language); values.Add(index == -1 ? 0 : 100 - index); diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 1cbf4235aa..1319ee6f4b 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -67,19 +67,19 @@ namespace Emby.Server.Implementations.Library { try { - return _libraryManager.GetMusicGenre(i).Id.ToString("N"); + return _libraryManager.GetMusicGenre(i).Id; } catch { - return null; + return Guid.Empty; } - }).Where(i => i != null); + }).Where(i => !i.Equals(Guid.Empty)).ToArray(); return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); } - public List GetInstantMixFromGenreIds(IEnumerable genreIds, User user, DtoOptions dtoOptions) + public List GetInstantMixFromGenreIds(Guid[] genreIds, User user, DtoOptions dtoOptions) { return _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library Limit = 200, - OrderBy = new [] { new Tuple(ItemSortBy.Random, SortOrder.Ascending) }, + OrderBy = new [] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, DtoOptions = dtoOptions @@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.Library var genre = item as MusicGenre; if (genre != null) { - return GetInstantMixFromGenreIds(new[] { item.Id.ToString("N") }, user, dtoOptions); + return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); } var playlist = item as Playlist; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index d0096de0c5..14b28966aa 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Library public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) { // This version of the below method has no ItemResolveArgs, so we have to require the path already being set - if (string.IsNullOrWhiteSpace(item.Path)) + if (string.IsNullOrEmpty(item.Path)) { throw new ArgumentException("Item must have a Path"); } @@ -107,17 +107,6 @@ namespace Emby.Server.Implementations.Library return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); } - /// - /// The MB name regex - /// - private static readonly Regex MbNameRegex = new Regex(@"(\[.*?\])"); - - internal static string StripBrackets(string inputString) - { - var output = MbNameRegex.Replace(inputString, string.Empty).Trim(); - return Regex.Replace(output, @"\s+", " "); - } - /// /// Ensures DateCreated and DateModified have values /// @@ -140,7 +129,7 @@ namespace Emby.Server.Implementations.Library } // See if a different path came out of the resolver than what went in - if (!string.Equals(args.Path, item.Path, StringComparison.OrdinalIgnoreCase)) + if (!fileSystem.AreEqual(args.Path, item.Path)) { var childData = args.IsDirectory ? args.GetFileSystemEntryByPath(item.Path) : null; @@ -173,7 +162,14 @@ namespace Emby.Server.Implementations.Library // directoryService.getFile may return null if (info != null) { - item.DateCreated = fileSystem.GetCreationTimeUtc(info); + var dateCreated = fileSystem.GetCreationTimeUtc(info); + + if (dateCreated.Equals(DateTime.MinValue)) + { + dateCreated = DateTime.UtcNow; + } + + item.DateCreated = dateCreated; } } else diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index d30aaa133b..8872bd641e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -101,13 +101,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (LibraryManager.IsAudioFile(args.Path, libraryOptions)) { - if (string.Equals(Path.GetExtension(args.Path), ".cue", StringComparison.OrdinalIgnoreCase)) + var extension = Path.GetExtension(args.Path); + + if (string.Equals(extension, ".cue", StringComparison.OrdinalIgnoreCase)) { // if audio file exists of same name, return null return null; } - var isMixedCollectionType = string.IsNullOrWhiteSpace(collectionType); + var isMixedCollectionType = string.IsNullOrEmpty(collectionType); // For conflicting extensions, give priority to videos if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions)) @@ -134,6 +136,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (item != null) { + item.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); + item.IsInMixedFolder = true; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index b8ec418053..a33f101aeb 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -52,14 +52,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// MusicAlbum. protected override MusicAlbum Resolve(ItemResolveArgs args) { - if (!args.IsDirectory) return null; - - // Avoid mis-identifying top folders - if (args.HasParent()) return null; - if (args.Parent.IsRoot) return null; - var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase); // If there's a collection type and it's not music, don't allow it. @@ -68,6 +61,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio return null; } + if (!args.IsDirectory) return null; + + // Avoid mis-identifying top folders + if (args.HasParent()) return null; + if (args.Parent.IsRoot) return null; + return IsMusicAlbum(args) ? new MusicAlbum() : null; } @@ -117,24 +116,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { if (allowSubfolders) { - var path = fileSystemInfo.FullName; - var isMultiDisc = IsMultiDiscFolder(path, libraryOptions); - - if (isMultiDisc) + if (notMultiDisc) { - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager); + continue; + } - if (hasMusic) + var path = fileSystemInfo.FullName; + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager); + + if (hasMusic) + { + if (IsMultiDiscFolder(path, libraryOptions)) { logger.Debug("Found multi-disc folder: " + path); discSubfolderCount++; } - } - else - { - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager); - - if (hasMusic) + else { // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album notMultiDisc = true; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 7e960f85e6..556748183f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -184,11 +184,6 @@ namespace Emby.Server.Implementations.Library.Resolvers else if (string.Equals(videoInfo.StubType, "bluray", StringComparison.OrdinalIgnoreCase)) { video.VideoType = VideoType.BluRay; - video.IsHD = true; - } - else if (string.Equals(videoInfo.StubType, "hdtv", StringComparison.OrdinalIgnoreCase)) - { - video.IsHD = true; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index df441c5eda..b9aca14173 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using System; using System.IO; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Library.Resolvers.Movies { @@ -30,14 +31,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { return null; } - - if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || - args.ContainsFileSystemEntryByName("collection.xml")) + + if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || args.ContainsFileSystemEntryByName("collection.xml")) { return new BoxSet { Path = args.Path, - Name = ResolverHelper.StripBrackets(Path.GetFileName(args.Path)) + Name = Path.GetFileName(args.Path).Replace("[boxset]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim() }; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index d74235ec7b..1394e3858e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return ResolveVideos private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private readonly ConcurrentDictionary> _allParentalRatings = - new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> _allParentalRatings = + new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; @@ -96,6 +96,61 @@ namespace Emby.Server.Implementations.Localization { LoadRatings(file); } + + LoadAdditionalRatings(); + } + + private void LoadAdditionalRatings() + { + LoadRatings("au", new[] { + + new ParentalRating("AU-G", 1), + new ParentalRating("AU-PG", 5), + new ParentalRating("AU-M", 6), + new ParentalRating("AU-MA15+", 7), + new ParentalRating("AU-M15+", 8), + new ParentalRating("AU-R18+", 9), + new ParentalRating("AU-X18+", 10), + new ParentalRating("AU-RC", 11) + }); + + LoadRatings("be", new[] { + + new ParentalRating("BE-AL", 1), + new ParentalRating("BE-MG6", 2), + new ParentalRating("BE-6", 3), + new ParentalRating("BE-9", 5), + new ParentalRating("BE-12", 6), + new ParentalRating("BE-16", 8) + }); + + LoadRatings("de", new[] { + + new ParentalRating("DE-0", 1), + new ParentalRating("FSK-0", 1), + new ParentalRating("DE-6", 5), + new ParentalRating("FSK-6", 5), + new ParentalRating("DE-12", 7), + new ParentalRating("FSK-12", 7), + new ParentalRating("DE-16", 8), + new ParentalRating("FSK-16", 8), + new ParentalRating("DE-18", 9), + new ParentalRating("FSK-18", 9) + }); + + LoadRatings("ru", new [] { + + new ParentalRating("RU-0+", 1), + new ParentalRating("RU-6+", 3), + new ParentalRating("RU-12+", 7), + new ParentalRating("RU-16+", 9), + new ParentalRating("RU-18+", 10) + }); + } + + private void LoadRatings(string country, ParentalRating[] ratings) + { + _allParentalRatings[country] = ratings.ToDictionary(i => i.Name); } private List GetRatingsFiles(string directory) @@ -161,11 +216,17 @@ namespace Emby.Server.Implementations.Localization if (parts.Length == 5) { + var threeletterNames = new List { parts[0] }; + if (!string.IsNullOrWhiteSpace(parts[1])) + { + threeletterNames.Add(parts[1]); + } + list.Add(new CultureDto { DisplayName = parts[3], Name = parts[3], - ThreeLetterISOLanguageName = parts[0], + ThreeLetterISOLanguageNames = threeletterNames.ToArray(), TwoLetterISOLanguageName = parts[2] }); } @@ -176,7 +237,7 @@ namespace Emby.Server.Implementations.Localization result = list.Where(i => !string.IsNullOrWhiteSpace(i.Name) && !string.IsNullOrWhiteSpace(i.DisplayName) && - !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) && + i.ThreeLetterISOLanguageNames.Length > 0 && !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray(); _cultures = result; @@ -184,19 +245,25 @@ namespace Emby.Server.Implementations.Localization return result; } + public CultureDto FindLanguageInfo(string language) + { + return GetCultures() + .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || + string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || + i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase) || + string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); + } + /// /// Gets the countries. /// /// IEnumerable{CountryInfo}. public CountryInfo[] GetCountries() { - var type = GetType(); - var path = type.Namespace + ".countries.json"; + // ToDo: DeserializeFromStream seems broken in this case + string jsonCountries = "[{\"Name\":\"AF\",\"DisplayName\":\"Afghanistan\",\"TwoLetterISORegionName\":\"AF\",\"ThreeLetterISORegionName\":\"AFG\"},{\"Name\":\"AL\",\"DisplayName\":\"Albania\",\"TwoLetterISORegionName\":\"AL\",\"ThreeLetterISORegionName\":\"ALB\"},{\"Name\":\"DZ\",\"DisplayName\":\"Algeria\",\"TwoLetterISORegionName\":\"DZ\",\"ThreeLetterISORegionName\":\"DZA\"},{\"Name\":\"AR\",\"DisplayName\":\"Argentina\",\"TwoLetterISORegionName\":\"AR\",\"ThreeLetterISORegionName\":\"ARG\"},{\"Name\":\"AM\",\"DisplayName\":\"Armenia\",\"TwoLetterISORegionName\":\"AM\",\"ThreeLetterISORegionName\":\"ARM\"},{\"Name\":\"AU\",\"DisplayName\":\"Australia\",\"TwoLetterISORegionName\":\"AU\",\"ThreeLetterISORegionName\":\"AUS\"},{\"Name\":\"AT\",\"DisplayName\":\"Austria\",\"TwoLetterISORegionName\":\"AT\",\"ThreeLetterISORegionName\":\"AUT\"},{\"Name\":\"AZ\",\"DisplayName\":\"Azerbaijan\",\"TwoLetterISORegionName\":\"AZ\",\"ThreeLetterISORegionName\":\"AZE\"},{\"Name\":\"BH\",\"DisplayName\":\"Bahrain\",\"TwoLetterISORegionName\":\"BH\",\"ThreeLetterISORegionName\":\"BHR\"},{\"Name\":\"BD\",\"DisplayName\":\"Bangladesh\",\"TwoLetterISORegionName\":\"BD\",\"ThreeLetterISORegionName\":\"BGD\"},{\"Name\":\"BY\",\"DisplayName\":\"Belarus\",\"TwoLetterISORegionName\":\"BY\",\"ThreeLetterISORegionName\":\"BLR\"},{\"Name\":\"BE\",\"DisplayName\":\"Belgium\",\"TwoLetterISORegionName\":\"BE\",\"ThreeLetterISORegionName\":\"BEL\"},{\"Name\":\"BZ\",\"DisplayName\":\"Belize\",\"TwoLetterISORegionName\":\"BZ\",\"ThreeLetterISORegionName\":\"BLZ\"},{\"Name\":\"VE\",\"DisplayName\":\"Bolivarian Republic of Venezuela\",\"TwoLetterISORegionName\":\"VE\",\"ThreeLetterISORegionName\":\"VEN\"},{\"Name\":\"BO\",\"DisplayName\":\"Bolivia\",\"TwoLetterISORegionName\":\"BO\",\"ThreeLetterISORegionName\":\"BOL\"},{\"Name\":\"BA\",\"DisplayName\":\"Bosnia and Herzegovina\",\"TwoLetterISORegionName\":\"BA\",\"ThreeLetterISORegionName\":\"BIH\"},{\"Name\":\"BW\",\"DisplayName\":\"Botswana\",\"TwoLetterISORegionName\":\"BW\",\"ThreeLetterISORegionName\":\"BWA\"},{\"Name\":\"BR\",\"DisplayName\":\"Brazil\",\"TwoLetterISORegionName\":\"BR\",\"ThreeLetterISORegionName\":\"BRA\"},{\"Name\":\"BN\",\"DisplayName\":\"Brunei Darussalam\",\"TwoLetterISORegionName\":\"BN\",\"ThreeLetterISORegionName\":\"BRN\"},{\"Name\":\"BG\",\"DisplayName\":\"Bulgaria\",\"TwoLetterISORegionName\":\"BG\",\"ThreeLetterISORegionName\":\"BGR\"},{\"Name\":\"KH\",\"DisplayName\":\"Cambodia\",\"TwoLetterISORegionName\":\"KH\",\"ThreeLetterISORegionName\":\"KHM\"},{\"Name\":\"CM\",\"DisplayName\":\"Cameroon\",\"TwoLetterISORegionName\":\"CM\",\"ThreeLetterISORegionName\":\"CMR\"},{\"Name\":\"CA\",\"DisplayName\":\"Canada\",\"TwoLetterISORegionName\":\"CA\",\"ThreeLetterISORegionName\":\"CAN\"},{\"Name\":\"029\",\"DisplayName\":\"Caribbean\",\"TwoLetterISORegionName\":\"029\",\"ThreeLetterISORegionName\":\"029\"},{\"Name\":\"CL\",\"DisplayName\":\"Chile\",\"TwoLetterISORegionName\":\"CL\",\"ThreeLetterISORegionName\":\"CHL\"},{\"Name\":\"CO\",\"DisplayName\":\"Colombia\",\"TwoLetterISORegionName\":\"CO\",\"ThreeLetterISORegionName\":\"COL\"},{\"Name\":\"CD\",\"DisplayName\":\"Congo [DRC]\",\"TwoLetterISORegionName\":\"CD\",\"ThreeLetterISORegionName\":\"COD\"},{\"Name\":\"CR\",\"DisplayName\":\"Costa Rica\",\"TwoLetterISORegionName\":\"CR\",\"ThreeLetterISORegionName\":\"CRI\"},{\"Name\":\"HR\",\"DisplayName\":\"Croatia\",\"TwoLetterISORegionName\":\"HR\",\"ThreeLetterISORegionName\":\"HRV\"},{\"Name\":\"CZ\",\"DisplayName\":\"Czech Republic\",\"TwoLetterISORegionName\":\"CZ\",\"ThreeLetterISORegionName\":\"CZE\"},{\"Name\":\"DK\",\"DisplayName\":\"Denmark\",\"TwoLetterISORegionName\":\"DK\",\"ThreeLetterISORegionName\":\"DNK\"},{\"Name\":\"DO\",\"DisplayName\":\"Dominican Republic\",\"TwoLetterISORegionName\":\"DO\",\"ThreeLetterISORegionName\":\"DOM\"},{\"Name\":\"EC\",\"DisplayName\":\"Ecuador\",\"TwoLetterISORegionName\":\"EC\",\"ThreeLetterISORegionName\":\"ECU\"},{\"Name\":\"EG\",\"DisplayName\":\"Egypt\",\"TwoLetterISORegionName\":\"EG\",\"ThreeLetterISORegionName\":\"EGY\"},{\"Name\":\"SV\",\"DisplayName\":\"El Salvador\",\"TwoLetterISORegionName\":\"SV\",\"ThreeLetterISORegionName\":\"SLV\"},{\"Name\":\"ER\",\"DisplayName\":\"Eritrea\",\"TwoLetterISORegionName\":\"ER\",\"ThreeLetterISORegionName\":\"ERI\"},{\"Name\":\"EE\",\"DisplayName\":\"Estonia\",\"TwoLetterISORegionName\":\"EE\",\"ThreeLetterISORegionName\":\"EST\"},{\"Name\":\"ET\",\"DisplayName\":\"Ethiopia\",\"TwoLetterISORegionName\":\"ET\",\"ThreeLetterISORegionName\":\"ETH\"},{\"Name\":\"FO\",\"DisplayName\":\"Faroe Islands\",\"TwoLetterISORegionName\":\"FO\",\"ThreeLetterISORegionName\":\"FRO\"},{\"Name\":\"FI\",\"DisplayName\":\"Finland\",\"TwoLetterISORegionName\":\"FI\",\"ThreeLetterISORegionName\":\"FIN\"},{\"Name\":\"FR\",\"DisplayName\":\"France\",\"TwoLetterISORegionName\":\"FR\",\"ThreeLetterISORegionName\":\"FRA\"},{\"Name\":\"GE\",\"DisplayName\":\"Georgia\",\"TwoLetterISORegionName\":\"GE\",\"ThreeLetterISORegionName\":\"GEO\"},{\"Name\":\"DE\",\"DisplayName\":\"Germany\",\"TwoLetterISORegionName\":\"DE\",\"ThreeLetterISORegionName\":\"DEU\"},{\"Name\":\"GR\",\"DisplayName\":\"Greece\",\"TwoLetterISORegionName\":\"GR\",\"ThreeLetterISORegionName\":\"GRC\"},{\"Name\":\"GL\",\"DisplayName\":\"Greenland\",\"TwoLetterISORegionName\":\"GL\",\"ThreeLetterISORegionName\":\"GRL\"},{\"Name\":\"GT\",\"DisplayName\":\"Guatemala\",\"TwoLetterISORegionName\":\"GT\",\"ThreeLetterISORegionName\":\"GTM\"},{\"Name\":\"HT\",\"DisplayName\":\"Haiti\",\"TwoLetterISORegionName\":\"HT\",\"ThreeLetterISORegionName\":\"HTI\"},{\"Name\":\"HN\",\"DisplayName\":\"Honduras\",\"TwoLetterISORegionName\":\"HN\",\"ThreeLetterISORegionName\":\"HND\"},{\"Name\":\"HK\",\"DisplayName\":\"Hong Kong S.A.R.\",\"TwoLetterISORegionName\":\"HK\",\"ThreeLetterISORegionName\":\"HKG\"},{\"Name\":\"HU\",\"DisplayName\":\"Hungary\",\"TwoLetterISORegionName\":\"HU\",\"ThreeLetterISORegionName\":\"HUN\"},{\"Name\":\"IS\",\"DisplayName\":\"Iceland\",\"TwoLetterISORegionName\":\"IS\",\"ThreeLetterISORegionName\":\"ISL\"},{\"Name\":\"IN\",\"DisplayName\":\"India\",\"TwoLetterISORegionName\":\"IN\",\"ThreeLetterISORegionName\":\"IND\"},{\"Name\":\"ID\",\"DisplayName\":\"Indonesia\",\"TwoLetterISORegionName\":\"ID\",\"ThreeLetterISORegionName\":\"IDN\"},{\"Name\":\"IR\",\"DisplayName\":\"Iran\",\"TwoLetterISORegionName\":\"IR\",\"ThreeLetterISORegionName\":\"IRN\"},{\"Name\":\"IQ\",\"DisplayName\":\"Iraq\",\"TwoLetterISORegionName\":\"IQ\",\"ThreeLetterISORegionName\":\"IRQ\"},{\"Name\":\"IE\",\"DisplayName\":\"Ireland\",\"TwoLetterISORegionName\":\"IE\",\"ThreeLetterISORegionName\":\"IRL\"},{\"Name\":\"PK\",\"DisplayName\":\"Islamic Republic of Pakistan\",\"TwoLetterISORegionName\":\"PK\",\"ThreeLetterISORegionName\":\"PAK\"},{\"Name\":\"IL\",\"DisplayName\":\"Israel\",\"TwoLetterISORegionName\":\"IL\",\"ThreeLetterISORegionName\":\"ISR\"},{\"Name\":\"IT\",\"DisplayName\":\"Italy\",\"TwoLetterISORegionName\":\"IT\",\"ThreeLetterISORegionName\":\"ITA\"},{\"Name\":\"CI\",\"DisplayName\":\"Ivory Coast\",\"TwoLetterISORegionName\":\"CI\",\"ThreeLetterISORegionName\":\"CIV\"},{\"Name\":\"JM\",\"DisplayName\":\"Jamaica\",\"TwoLetterISORegionName\":\"JM\",\"ThreeLetterISORegionName\":\"JAM\"},{\"Name\":\"JP\",\"DisplayName\":\"Japan\",\"TwoLetterISORegionName\":\"JP\",\"ThreeLetterISORegionName\":\"JPN\"},{\"Name\":\"JO\",\"DisplayName\":\"Jordan\",\"TwoLetterISORegionName\":\"JO\",\"ThreeLetterISORegionName\":\"JOR\"},{\"Name\":\"KZ\",\"DisplayName\":\"Kazakhstan\",\"TwoLetterISORegionName\":\"KZ\",\"ThreeLetterISORegionName\":\"KAZ\"},{\"Name\":\"KE\",\"DisplayName\":\"Kenya\",\"TwoLetterISORegionName\":\"KE\",\"ThreeLetterISORegionName\":\"KEN\"},{\"Name\":\"KR\",\"DisplayName\":\"Korea\",\"TwoLetterISORegionName\":\"KR\",\"ThreeLetterISORegionName\":\"KOR\"},{\"Name\":\"KW\",\"DisplayName\":\"Kuwait\",\"TwoLetterISORegionName\":\"KW\",\"ThreeLetterISORegionName\":\"KWT\"},{\"Name\":\"KG\",\"DisplayName\":\"Kyrgyzstan\",\"TwoLetterISORegionName\":\"KG\",\"ThreeLetterISORegionName\":\"KGZ\"},{\"Name\":\"LA\",\"DisplayName\":\"Lao P.D.R.\",\"TwoLetterISORegionName\":\"LA\",\"ThreeLetterISORegionName\":\"LAO\"},{\"Name\":\"419\",\"DisplayName\":\"Latin America\",\"TwoLetterISORegionName\":\"419\",\"ThreeLetterISORegionName\":\"419\"},{\"Name\":\"LV\",\"DisplayName\":\"Latvia\",\"TwoLetterISORegionName\":\"LV\",\"ThreeLetterISORegionName\":\"LVA\"},{\"Name\":\"LB\",\"DisplayName\":\"Lebanon\",\"TwoLetterISORegionName\":\"LB\",\"ThreeLetterISORegionName\":\"LBN\"},{\"Name\":\"LY\",\"DisplayName\":\"Libya\",\"TwoLetterISORegionName\":\"LY\",\"ThreeLetterISORegionName\":\"LBY\"},{\"Name\":\"LI\",\"DisplayName\":\"Liechtenstein\",\"TwoLetterISORegionName\":\"LI\",\"ThreeLetterISORegionName\":\"LIE\"},{\"Name\":\"LT\",\"DisplayName\":\"Lithuania\",\"TwoLetterISORegionName\":\"LT\",\"ThreeLetterISORegionName\":\"LTU\"},{\"Name\":\"LU\",\"DisplayName\":\"Luxembourg\",\"TwoLetterISORegionName\":\"LU\",\"ThreeLetterISORegionName\":\"LUX\"},{\"Name\":\"MO\",\"DisplayName\":\"Macao S.A.R.\",\"TwoLetterISORegionName\":\"MO\",\"ThreeLetterISORegionName\":\"MAC\"},{\"Name\":\"MK\",\"DisplayName\":\"Macedonia (FYROM)\",\"TwoLetterISORegionName\":\"MK\",\"ThreeLetterISORegionName\":\"MKD\"},{\"Name\":\"MY\",\"DisplayName\":\"Malaysia\",\"TwoLetterISORegionName\":\"MY\",\"ThreeLetterISORegionName\":\"MYS\"},{\"Name\":\"MV\",\"DisplayName\":\"Maldives\",\"TwoLetterISORegionName\":\"MV\",\"ThreeLetterISORegionName\":\"MDV\"},{\"Name\":\"ML\",\"DisplayName\":\"Mali\",\"TwoLetterISORegionName\":\"ML\",\"ThreeLetterISORegionName\":\"MLI\"},{\"Name\":\"MT\",\"DisplayName\":\"Malta\",\"TwoLetterISORegionName\":\"MT\",\"ThreeLetterISORegionName\":\"MLT\"},{\"Name\":\"MX\",\"DisplayName\":\"Mexico\",\"TwoLetterISORegionName\":\"MX\",\"ThreeLetterISORegionName\":\"MEX\"},{\"Name\":\"MN\",\"DisplayName\":\"Mongolia\",\"TwoLetterISORegionName\":\"MN\",\"ThreeLetterISORegionName\":\"MNG\"},{\"Name\":\"ME\",\"DisplayName\":\"Montenegro\",\"TwoLetterISORegionName\":\"ME\",\"ThreeLetterISORegionName\":\"MNE\"},{\"Name\":\"MA\",\"DisplayName\":\"Morocco\",\"TwoLetterISORegionName\":\"MA\",\"ThreeLetterISORegionName\":\"MAR\"},{\"Name\":\"NP\",\"DisplayName\":\"Nepal\",\"TwoLetterISORegionName\":\"NP\",\"ThreeLetterISORegionName\":\"NPL\"},{\"Name\":\"NL\",\"DisplayName\":\"Netherlands\",\"TwoLetterISORegionName\":\"NL\",\"ThreeLetterISORegionName\":\"NLD\"},{\"Name\":\"NZ\",\"DisplayName\":\"New Zealand\",\"TwoLetterISORegionName\":\"NZ\",\"ThreeLetterISORegionName\":\"NZL\"},{\"Name\":\"NI\",\"DisplayName\":\"Nicaragua\",\"TwoLetterISORegionName\":\"NI\",\"ThreeLetterISORegionName\":\"NIC\"},{\"Name\":\"NG\",\"DisplayName\":\"Nigeria\",\"TwoLetterISORegionName\":\"NG\",\"ThreeLetterISORegionName\":\"NGA\"},{\"Name\":\"NO\",\"DisplayName\":\"Norway\",\"TwoLetterISORegionName\":\"NO\",\"ThreeLetterISORegionName\":\"NOR\"},{\"Name\":\"OM\",\"DisplayName\":\"Oman\",\"TwoLetterISORegionName\":\"OM\",\"ThreeLetterISORegionName\":\"OMN\"},{\"Name\":\"PA\",\"DisplayName\":\"Panama\",\"TwoLetterISORegionName\":\"PA\",\"ThreeLetterISORegionName\":\"PAN\"},{\"Name\":\"PY\",\"DisplayName\":\"Paraguay\",\"TwoLetterISORegionName\":\"PY\",\"ThreeLetterISORegionName\":\"PRY\"},{\"Name\":\"CN\",\"DisplayName\":\"People's Republic of China\",\"TwoLetterISORegionName\":\"CN\",\"ThreeLetterISORegionName\":\"CHN\"},{\"Name\":\"PE\",\"DisplayName\":\"Peru\",\"TwoLetterISORegionName\":\"PE\",\"ThreeLetterISORegionName\":\"PER\"},{\"Name\":\"PH\",\"DisplayName\":\"Philippines\",\"TwoLetterISORegionName\":\"PH\",\"ThreeLetterISORegionName\":\"PHL\"},{\"Name\":\"PL\",\"DisplayName\":\"Poland\",\"TwoLetterISORegionName\":\"PL\",\"ThreeLetterISORegionName\":\"POL\"},{\"Name\":\"PT\",\"DisplayName\":\"Portugal\",\"TwoLetterISORegionName\":\"PT\",\"ThreeLetterISORegionName\":\"PRT\"},{\"Name\":\"MC\",\"DisplayName\":\"Principality of Monaco\",\"TwoLetterISORegionName\":\"MC\",\"ThreeLetterISORegionName\":\"MCO\"},{\"Name\":\"PR\",\"DisplayName\":\"Puerto Rico\",\"TwoLetterISORegionName\":\"PR\",\"ThreeLetterISORegionName\":\"PRI\"},{\"Name\":\"QA\",\"DisplayName\":\"Qatar\",\"TwoLetterISORegionName\":\"QA\",\"ThreeLetterISORegionName\":\"QAT\"},{\"Name\":\"MD\",\"DisplayName\":\"Republica Moldova\",\"TwoLetterISORegionName\":\"MD\",\"ThreeLetterISORegionName\":\"MDA\"},{\"Name\":\"RE\",\"DisplayName\":\"Réunion\",\"TwoLetterISORegionName\":\"RE\",\"ThreeLetterISORegionName\":\"REU\"},{\"Name\":\"RO\",\"DisplayName\":\"Romania\",\"TwoLetterISORegionName\":\"RO\",\"ThreeLetterISORegionName\":\"ROU\"},{\"Name\":\"RU\",\"DisplayName\":\"Russia\",\"TwoLetterISORegionName\":\"RU\",\"ThreeLetterISORegionName\":\"RUS\"},{\"Name\":\"RW\",\"DisplayName\":\"Rwanda\",\"TwoLetterISORegionName\":\"RW\",\"ThreeLetterISORegionName\":\"RWA\"},{\"Name\":\"SA\",\"DisplayName\":\"Saudi Arabia\",\"TwoLetterISORegionName\":\"SA\",\"ThreeLetterISORegionName\":\"SAU\"},{\"Name\":\"SN\",\"DisplayName\":\"Senegal\",\"TwoLetterISORegionName\":\"SN\",\"ThreeLetterISORegionName\":\"SEN\"},{\"Name\":\"RS\",\"DisplayName\":\"Serbia\",\"TwoLetterISORegionName\":\"RS\",\"ThreeLetterISORegionName\":\"SRB\"},{\"Name\":\"CS\",\"DisplayName\":\"Serbia and Montenegro (Former)\",\"TwoLetterISORegionName\":\"CS\",\"ThreeLetterISORegionName\":\"SCG\"},{\"Name\":\"SG\",\"DisplayName\":\"Singapore\",\"TwoLetterISORegionName\":\"SG\",\"ThreeLetterISORegionName\":\"SGP\"},{\"Name\":\"SK\",\"DisplayName\":\"Slovakia\",\"TwoLetterISORegionName\":\"SK\",\"ThreeLetterISORegionName\":\"SVK\"},{\"Name\":\"SI\",\"DisplayName\":\"Slovenia\",\"TwoLetterISORegionName\":\"SI\",\"ThreeLetterISORegionName\":\"SVN\"},{\"Name\":\"SO\",\"DisplayName\":\"Soomaaliya\",\"TwoLetterISORegionName\":\"SO\",\"ThreeLetterISORegionName\":\"SOM\"},{\"Name\":\"ZA\",\"DisplayName\":\"South Africa\",\"TwoLetterISORegionName\":\"ZA\",\"ThreeLetterISORegionName\":\"ZAF\"},{\"Name\":\"ES\",\"DisplayName\":\"Spain\",\"TwoLetterISORegionName\":\"ES\",\"ThreeLetterISORegionName\":\"ESP\"},{\"Name\":\"LK\",\"DisplayName\":\"Sri Lanka\",\"TwoLetterISORegionName\":\"LK\",\"ThreeLetterISORegionName\":\"LKA\"},{\"Name\":\"SE\",\"DisplayName\":\"Sweden\",\"TwoLetterISORegionName\":\"SE\",\"ThreeLetterISORegionName\":\"SWE\"},{\"Name\":\"CH\",\"DisplayName\":\"Switzerland\",\"TwoLetterISORegionName\":\"CH\",\"ThreeLetterISORegionName\":\"CHE\"},{\"Name\":\"SY\",\"DisplayName\":\"Syria\",\"TwoLetterISORegionName\":\"SY\",\"ThreeLetterISORegionName\":\"SYR\"},{\"Name\":\"TW\",\"DisplayName\":\"Taiwan\",\"TwoLetterISORegionName\":\"TW\",\"ThreeLetterISORegionName\":\"TWN\"},{\"Name\":\"TJ\",\"DisplayName\":\"Tajikistan\",\"TwoLetterISORegionName\":\"TJ\",\"ThreeLetterISORegionName\":\"TAJ\"},{\"Name\":\"TH\",\"DisplayName\":\"Thailand\",\"TwoLetterISORegionName\":\"TH\",\"ThreeLetterISORegionName\":\"THA\"},{\"Name\":\"TT\",\"DisplayName\":\"Trinidad and Tobago\",\"TwoLetterISORegionName\":\"TT\",\"ThreeLetterISORegionName\":\"TTO\"},{\"Name\":\"TN\",\"DisplayName\":\"Tunisia\",\"TwoLetterISORegionName\":\"TN\",\"ThreeLetterISORegionName\":\"TUN\"},{\"Name\":\"TR\",\"DisplayName\":\"Turkey\",\"TwoLetterISORegionName\":\"TR\",\"ThreeLetterISORegionName\":\"TUR\"},{\"Name\":\"TM\",\"DisplayName\":\"Turkmenistan\",\"TwoLetterISORegionName\":\"TM\",\"ThreeLetterISORegionName\":\"TKM\"},{\"Name\":\"AE\",\"DisplayName\":\"U.A.E.\",\"TwoLetterISORegionName\":\"AE\",\"ThreeLetterISORegionName\":\"ARE\"},{\"Name\":\"UA\",\"DisplayName\":\"Ukraine\",\"TwoLetterISORegionName\":\"UA\",\"ThreeLetterISORegionName\":\"UKR\"},{\"Name\":\"GB\",\"DisplayName\":\"United Kingdom\",\"TwoLetterISORegionName\":\"GB\",\"ThreeLetterISORegionName\":\"GBR\"},{\"Name\":\"US\",\"DisplayName\":\"United States\",\"TwoLetterISORegionName\":\"US\",\"ThreeLetterISORegionName\":\"USA\"},{\"Name\":\"UY\",\"DisplayName\":\"Uruguay\",\"TwoLetterISORegionName\":\"UY\",\"ThreeLetterISORegionName\":\"URY\"},{\"Name\":\"UZ\",\"DisplayName\":\"Uzbekistan\",\"TwoLetterISORegionName\":\"UZ\",\"ThreeLetterISORegionName\":\"UZB\"},{\"Name\":\"VN\",\"DisplayName\":\"Vietnam\",\"TwoLetterISORegionName\":\"VN\",\"ThreeLetterISORegionName\":\"VNM\"},{\"Name\":\"YE\",\"DisplayName\":\"Yemen\",\"TwoLetterISORegionName\":\"YE\",\"ThreeLetterISORegionName\":\"YEM\"},{\"Name\":\"ZW\",\"DisplayName\":\"Zimbabwe\",\"TwoLetterISORegionName\":\"ZW\",\"ThreeLetterISORegionName\":\"ZWE\"}]"; - using (var stream = _assemblyInfo.GetManifestResourceStream(type, path)) - { - return _jsonSerializer.DeserializeFromStream(stream); - } + return _jsonSerializer.DeserializeFromString(jsonCountries); } /// @@ -278,7 +345,7 @@ namespace Emby.Server.Implementations.Localization .Split('-') .Last(); - _allParentalRatings.TryAdd(countryCode, dict); + _allParentalRatings[countryCode] = dict; } private readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; @@ -305,19 +372,34 @@ namespace Emby.Server.Implementations.Localization ParentalRating value; - if (!ratingsDictionary.TryGetValue(rating, out value)) + if (ratingsDictionary.TryGetValue(rating, out value)) { - // If we don't find anything check all ratings systems - foreach (var dictionary in _allParentalRatings.Values) + return value.Value; + } + + // If we don't find anything check all ratings systems + foreach (var dictionary in _allParentalRatings.Values) + { + if (dictionary.TryGetValue(rating, out value)) { - if (dictionary.TryGetValue(rating, out value)) - { - return value.Value; - } + return value.Value; } } - return value == null ? (int?)null : value.Value; + // Try splitting by : to handle "Germany: FSK 18" + var index = rating.IndexOf(':'); + if (index != -1) + { + rating = rating.Substring(index).TrimStart(':').Trim(); + + if (!string.IsNullOrWhiteSpace(rating)) + { + return GetRatingLevel(rating); + } + } + + // TODO: Further improve by normalizing out all spaces and dashes + return null; } public bool HasUnicodeCategory(string value, UnicodeCategory category) @@ -340,11 +422,11 @@ namespace Emby.Server.Implementations.Localization public string GetLocalizedString(string phrase, string culture) { - if (string.IsNullOrWhiteSpace(culture)) + if (string.IsNullOrEmpty(culture)) { culture = _configurationManager.Configuration.UICulture; } - if (string.IsNullOrWhiteSpace(culture)) + if (string.IsNullOrEmpty(culture)) { culture = DefaultCulture; } @@ -368,7 +450,7 @@ namespace Emby.Server.Implementations.Localization public Dictionary GetLocalizationDictionary(string culture) { - if (string.IsNullOrWhiteSpace(culture)) + if (string.IsNullOrEmpty(culture)) { throw new ArgumentNullException("culture"); } @@ -381,7 +463,7 @@ namespace Emby.Server.Implementations.Localization private Dictionary GetDictionary(string prefix, string culture, string baseFilename) { - if (string.IsNullOrWhiteSpace(culture)) + if (string.IsNullOrEmpty(culture)) { throw new ArgumentNullException("culture"); } diff --git a/Emby.Server.Implementations/Localization/Ratings/au.txt b/Emby.Server.Implementations/Localization/Ratings/au.txt deleted file mode 100644 index fa60f53055..0000000000 --- a/Emby.Server.Implementations/Localization/Ratings/au.txt +++ /dev/null @@ -1,8 +0,0 @@ -AU-G,1 -AU-PG,5 -AU-M,6 -AU-MA15+,7 -AU-M15+,8 -AU-R18+,9 -AU-X18+,10 -AU-RC,11 diff --git a/Emby.Server.Implementations/Localization/Ratings/be.txt b/Emby.Server.Implementations/Localization/Ratings/be.txt deleted file mode 100644 index 99a53f664a..0000000000 --- a/Emby.Server.Implementations/Localization/Ratings/be.txt +++ /dev/null @@ -1,6 +0,0 @@ -BE-AL,1 -BE-MG6,2 -BE-6,3 -BE-9,5 -BE-12,6 -BE-16,8 \ No newline at end of file diff --git a/Emby.Server.Implementations/Localization/Ratings/de.txt b/Emby.Server.Implementations/Localization/Ratings/de.txt deleted file mode 100644 index ad1f186197..0000000000 --- a/Emby.Server.Implementations/Localization/Ratings/de.txt +++ /dev/null @@ -1,10 +0,0 @@ -DE-0,1 -FSK-0,1 -DE-6,5 -FSK-6,5 -DE-12,7 -FSK-12,7 -DE-16,8 -FSK-16,8 -DE-18,9 -FSK-18,9 \ No newline at end of file diff --git a/Emby.Server.Implementations/Localization/Ratings/ru.txt b/Emby.Server.Implementations/Localization/Ratings/ru.txt deleted file mode 100644 index 1bc94affd6..0000000000 --- a/Emby.Server.Implementations/Localization/Ratings/ru.txt +++ /dev/null @@ -1,5 +0,0 @@ -RU-0+,1 -RU-6+,3 -RU-12+,7 -RU-16+,9 -RU-18+,10 diff --git a/Emby.Server.Implementations/Localization/Ratings/us.txt b/Emby.Server.Implementations/Localization/Ratings/us.txt index 9bd78c72b7..eebd828c71 100644 --- a/Emby.Server.Implementations/Localization/Ratings/us.txt +++ b/Emby.Server.Implementations/Localization/Ratings/us.txt @@ -1,9 +1,9 @@ +TV-Y,1 APPROVED,1 G,1 E,1 EC,1 TV-G,1 -TV-Y,2 TV-Y7,3 TV-Y7-FV,4 PG,5 diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt index 5616d41bc3..a7e7cfc208 100644 --- a/Emby.Server.Implementations/Localization/iso6392.txt +++ b/Emby.Server.Implementations/Localization/iso6392.txt @@ -403,7 +403,7 @@ sot||st|Sotho, Southern|sotho du Sud spa||es|Spanish; Castilian|espagnol; castillan srd||sc|Sardinian|sarde srn|||Sranan Tongo|sranan tongo -srp||sr|Serbian|serbe +srp|scc|sr|Serbian|serbe srr|||Serer|sérère ssa|||Nilo-Saharan languages|nilo-sahariennes, langues ssw||ss|Swati|swati diff --git a/Emby.Server.Implementations/Logging/SimpleLogManager.cs b/Emby.Server.Implementations/Logging/SimpleLogManager.cs index 6129f38c4d..390814c34e 100644 --- a/Emby.Server.Implementations/Logging/SimpleLogManager.cs +++ b/Emby.Server.Implementations/Logging/SimpleLogManager.cs @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.Logging return new NamedLogger(name, this); } - public void ReloadLogger(LogSeverity severity) + public async Task ReloadLogger(LogSeverity severity, CancellationToken cancellationToken) { LogSeverity = severity; @@ -39,19 +39,23 @@ namespace Emby.Server.Implementations.Logging if (logger != null) { logger.Dispose(); + await TryMoveToArchive(logger.Path, cancellationToken).ConfigureAwait(false); } - var path = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Floor(DateTime.Now.Ticks / 10000000) + ".txt"); + var newPath = Path.Combine(LogDirectory, LogFilePrefix + ".txt"); - _fileLogger = new FileLogger(path); + if (File.Exists(newPath)) + { + newPath = await TryMoveToArchive(newPath, cancellationToken).ConfigureAwait(false); + } + + _fileLogger = new FileLogger(newPath); if (LoggerLoaded != null) { try { - LoggerLoaded(this, EventArgs.Empty); - } catch (Exception ex) { @@ -60,6 +64,42 @@ namespace Emby.Server.Implementations.Logging } } + private async Task TryMoveToArchive(string file, CancellationToken cancellationToken, int retryCount = 0) + { + var archivePath = GetArchiveFilePath(); + + try + { + File.Move(file, archivePath); + + return file; + } + catch (FileNotFoundException) + { + return file; + } + catch (DirectoryNotFoundException) + { + return file; + } + catch + { + if (retryCount >= 50) + { + return GetArchiveFilePath(); + } + + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + + return await TryMoveToArchive(file, cancellationToken, retryCount + 1).ConfigureAwait(false); + } + } + + private string GetArchiveFilePath() + { + return Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Floor(DateTime.Now.Ticks / 10000000) + ".txt"); + } + public event EventHandler LoggerLoaded; public void Flush() @@ -104,10 +144,12 @@ namespace Emby.Server.Implementations.Logging if (logger != null) { logger.Dispose(); + + var task = TryMoveToArchive(logger.Path, CancellationToken.None); + Task.WaitAll(task); } _fileLogger = null; - GC.SuppressFinalize(this); } } @@ -119,9 +161,13 @@ namespace Emby.Server.Implementations.Logging private readonly CancellationTokenSource _cancellationTokenSource; private readonly BlockingCollection _queue = new BlockingCollection(); + public string Path { get; set; } + public FileLogger(string path) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + Path = path; + + Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); _fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 32768); _cancellationTokenSource = new CancellationTokenSource(); @@ -144,6 +190,10 @@ namespace Emby.Server.Implementations.Logging } _fileStream.Write(bytes, 0, bytes.Length); + if (_disposed) + { + return; + } _fileStream.Flush(true); } @@ -177,13 +227,22 @@ namespace Emby.Server.Implementations.Logging public void Dispose() { - _cancellationTokenSource.Cancel(); - - Flush(); + if (_disposed) + { + return; + } _disposed = true; - _fileStream.Dispose(); - GC.SuppressFinalize(this); + _cancellationTokenSource.Cancel(); + + var stream = _fileStream; + if (stream != null) + { + using (stream) + { + stream.Flush(true); + } + } } } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index d790f4ab8b..c6033b4f48 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.MediaEncoder /// Gets the chapter images data path. /// /// The chapter images data path. - private string GetChapterImagesPath(IHasMetadata item) + private string GetChapterImagesPath(BaseItem item) { return Path.Combine(item.GetInternalMetadataPath(), "chapters"); } @@ -133,6 +133,8 @@ namespace Emby.Server.Implementations.MediaEncoder { if (extractImages) { + cancellationToken.ThrowIfCancellationRequested(); + try { // Add some time for the first chapter to make sure we don't end up with a black image @@ -140,7 +142,7 @@ namespace Emby.Server.Implementations.MediaEncoder var protocol = MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, new string[] { }); + var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, Array.Empty()); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); diff --git a/Emby.Server.Implementations/Migrations/IVersionMigration.cs b/Emby.Server.Implementations/Migrations/IVersionMigration.cs deleted file mode 100644 index 7804912e3b..0000000000 --- a/Emby.Server.Implementations/Migrations/IVersionMigration.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Migrations -{ - public interface IVersionMigration - { - Task Run(); - } -} diff --git a/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs b/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs index b18335da77..b721e8a262 100644 --- a/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs +++ b/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs @@ -54,16 +54,9 @@ namespace Emby.Server.Implementations.Net /// public void Dispose() { - try - { - IsDisposed = true; + IsDisposed = true; - Dispose(true); - } - finally - { - GC.SuppressFinalize(this); - } + Dispose(true); } #endregion diff --git a/MediaBrowser.Controller/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs similarity index 85% rename from MediaBrowser.Controller/Net/IWebSocket.cs rename to Emby.Server.Implementations/Net/IWebSocket.cs index b88f2c3896..f79199a071 100644 --- a/MediaBrowser.Controller/Net/IWebSocket.cs +++ b/Emby.Server.Implementations/Net/IWebSocket.cs @@ -1,9 +1,9 @@ -using MediaBrowser.Model.Net; -using System; +using System; using System.Threading; using System.Threading.Tasks; +using System.Net.WebSockets; -namespace MediaBrowser.Controller.Net +namespace Emby.Server.Implementations.Net { /// /// Interface IWebSocket @@ -27,12 +27,6 @@ namespace MediaBrowser.Controller.Net /// The receive action. Action OnReceiveBytes { get; set; } - /// - /// Gets or sets the on receive. - /// - /// The on receive. - Action OnReceive { get; set; } - /// /// Sends the async. /// @@ -51,4 +45,9 @@ namespace MediaBrowser.Controller.Net /// Task. Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken); } + + public interface IMemoryWebSocket + { + Action, int> OnReceiveMemoryBytes { get; set; } + } } diff --git a/Emby.Server.Implementations/Net/NetAcceptSocket.cs b/Emby.Server.Implementations/Net/NetAcceptSocket.cs deleted file mode 100644 index d80341a077..0000000000 --- a/Emby.Server.Implementations/Net/NetAcceptSocket.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Networking; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; - -namespace Emby.Server.Implementations.Net -{ - public class NetAcceptSocket : IAcceptSocket - { - public Socket Socket { get; private set; } - private readonly ILogger _logger; - - public bool DualMode { get; private set; } - - public NetAcceptSocket(Socket socket, ILogger logger, bool isDualMode) - { - if (socket == null) - { - throw new ArgumentNullException("socket"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - Socket = socket; - _logger = logger; - DualMode = isDualMode; - } - - public IpEndPointInfo LocalEndPoint - { - get - { - return NetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.LocalEndPoint); - } - } - - public IpEndPointInfo RemoteEndPoint - { - get - { - return NetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.RemoteEndPoint); - } - } - - public void Connect(IpEndPointInfo endPoint) - { - var nativeEndpoint = NetworkManager.ToIPEndPoint(endPoint); - - Socket.Connect(nativeEndpoint); - } - - public void Close() - { -#if NET46 - Socket.Close(); -#else - Socket.Dispose(); -#endif - } - - public void Shutdown(bool both) - { - if (both) - { - Socket.Shutdown(SocketShutdown.Both); - } - else - { - // Change interface if ever needed - throw new NotImplementedException(); - } - } - - public void Listen(int backlog) - { - Socket.Listen(backlog); - } - - public void Bind(IpEndPointInfo endpoint) - { - var nativeEndpoint = NetworkManager.ToIPEndPoint(endpoint); - - Socket.Bind(nativeEndpoint); - } - - public void Dispose() - { - Socket.Dispose(); - GC.SuppressFinalize(this); - } - } -} diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index bdae1728f9..9726ef0974 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -29,41 +29,6 @@ namespace Emby.Server.Implementations.Net _logger = logger; } - public IAcceptSocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) - { - try - { - var addressFamily = family == IpAddressFamily.InterNetwork - ? AddressFamily.InterNetwork - : AddressFamily.InterNetworkV6; - - var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - - if (dualMode) - { - socket.DualMode = true; - } - - return new NetAcceptSocket(socket, _logger, dualMode); - } - catch (SocketException ex) - { - throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); - } - catch (ArgumentException ex) - { - if (dualMode) - { - // Mono for BSD incorrectly throws ArgumentException instead of SocketException - throw new SocketCreateException("AddressFamilyNotSupported", ex); - } - else - { - throw; - } - } - } - public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort) { if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort"); diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 58e4d6f89b..523ca3752a 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -118,6 +118,8 @@ namespace Emby.Server.Implementations.Net public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback) { + ThrowIfDisposed(); + EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); return _Socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer); @@ -125,17 +127,21 @@ namespace Emby.Server.Implementations.Net public int Receive(byte[] buffer, int offset, int count) { + ThrowIfDisposed(); + return _Socket.Receive(buffer, 0, buffer.Length, SocketFlags.None); } public SocketReceiveResult EndReceive(IAsyncResult result) { + ThrowIfDisposed(); + IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); EndPoint remoteEndPoint = (EndPoint)sender; var receivedBytes = _Socket.EndReceiveFrom(result, ref remoteEndPoint); - var buffer = (byte[]) result.AsyncState; + var buffer = (byte[])result.AsyncState; return new SocketReceiveResult { @@ -148,13 +154,20 @@ namespace Emby.Server.Implementations.Net public Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + ThrowIfDisposed(); + var taskCompletion = new TaskCompletionSource(); + bool isResultSet = false; Action callback = callbackResult => { try { - taskCompletion.TrySetResult(EndReceive(callbackResult)); + if (!isResultSet) + { + isResultSet = true; + taskCompletion.TrySetResult(EndReceive(callbackResult)); + } } catch (Exception ex) { @@ -167,6 +180,7 @@ namespace Emby.Server.Implementations.Net if (result.CompletedSynchronously) { callback(result); + return taskCompletion.Task; } cancellationToken.Register(() => taskCompletion.TrySetCanceled()); @@ -176,6 +190,8 @@ namespace Emby.Server.Implementations.Net public Task ReceiveAsync(CancellationToken cancellationToken) { + ThrowIfDisposed(); + var buffer = new byte[8192]; return ReceiveAsync(buffer, 0, buffer.Length, cancellationToken); @@ -183,13 +199,20 @@ namespace Emby.Server.Implementations.Net public Task SendToAsync(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken) { + ThrowIfDisposed(); + var taskCompletion = new TaskCompletionSource(); + bool isResultSet = false; Action callback = callbackResult => { try { - taskCompletion.TrySetResult(EndSendTo(callbackResult)); + if (!isResultSet) + { + isResultSet = true; + taskCompletion.TrySetResult(EndSendTo(callbackResult)); + } } catch (Exception ex) { @@ -202,6 +225,7 @@ namespace Emby.Server.Implementations.Net if (result.CompletedSynchronously) { callback(result); + return taskCompletion.Task; } cancellationToken.Register(() => taskCompletion.TrySetCanceled()); @@ -211,6 +235,8 @@ namespace Emby.Server.Implementations.Net public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, AsyncCallback callback, object state) { + ThrowIfDisposed(); + var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); return _Socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state); @@ -218,6 +244,8 @@ namespace Emby.Server.Implementations.Net public int EndSendTo(IAsyncResult result) { + ThrowIfDisposed(); + return _Socket.EndSendTo(result); } diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs new file mode 100644 index 0000000000..7b7f12d505 --- /dev/null +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.Net +{ + public class WebSocketConnectEventArgs : EventArgs + { + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + /// + /// Gets or sets the query string. + /// + /// The query string. + public QueryParamCollection QueryString { get; set; } + /// + /// Gets or sets the web socket. + /// + /// The web socket. + public IWebSocket WebSocket { get; set; } + /// + /// Gets or sets the endpoint. + /// + /// The endpoint. + public string Endpoint { get; set; } + } +} diff --git a/Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs b/Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs new file mode 100644 index 0000000000..afb202fa39 --- /dev/null +++ b/Emby.Server.Implementations/Networking/IPNetwork/BigIntegerExt.cs @@ -0,0 +1,168 @@ +using System.Collections.Generic; + +namespace System.Net +{ + using System; + using System.Numerics; + using System.Text; + + /// + /// Extension methods to convert + /// instances to hexadecimal, octal, and binary strings. + /// + public static class BigIntegerExtensions + { + /// + /// Converts a to a binary string. + /// + /// A . + /// + /// A containing a binary + /// representation of the supplied . + /// + public static string ToBinaryString(this BigInteger bigint) + { + var bytes = bigint.ToByteArray(); + var idx = bytes.Length - 1; + + // Create a StringBuilder having appropriate capacity. + var base2 = new StringBuilder(bytes.Length * 8); + + // Convert first byte to binary. + var binary = Convert.ToString(bytes[idx], 2); + + // Ensure leading zero exists if value is positive. + if (binary[0] != '0' && bigint.Sign == 1) + { + base2.Append('0'); + } + + // Append binary string to StringBuilder. + base2.Append(binary); + + // Convert remaining bytes adding leading zeros. + for (idx--; idx >= 0; idx--) + { + base2.Append(Convert.ToString(bytes[idx], 2).PadLeft(8, '0')); + } + + return base2.ToString(); + } + + /// + /// Converts a to a hexadecimal string. + /// + /// A . + /// + /// A containing a hexadecimal + /// representation of the supplied . + /// + public static string ToHexadecimalString(this BigInteger bigint) + { + return bigint.ToString("X"); + } + + /// + /// Converts a to a octal string. + /// + /// A . + /// + /// A containing an octal + /// representation of the supplied . + /// + public static string ToOctalString(this BigInteger bigint) + { + var bytes = bigint.ToByteArray(); + var idx = bytes.Length - 1; + + // Create a StringBuilder having appropriate capacity. + var base8 = new StringBuilder(((bytes.Length / 3) + 1) * 8); + + // Calculate how many bytes are extra when byte array is split + // into three-byte (24-bit) chunks. + var extra = bytes.Length % 3; + + // If no bytes are extra, use three bytes for first chunk. + if (extra == 0) + { + extra = 3; + } + + // Convert first chunk (24-bits) to integer value. + int int24 = 0; + for (; extra != 0; extra--) + { + int24 <<= 8; + int24 += bytes[idx--]; + } + + // Convert 24-bit integer to octal without adding leading zeros. + var octal = Convert.ToString(int24, 8); + + // Ensure leading zero exists if value is positive. + if (octal[0] != '0') + { + if (bigint.Sign == 1) + { + base8.Append('0'); + } + } + + // Append first converted chunk to StringBuilder. + base8.Append(octal); + + // Convert remaining 24-bit chunks, adding leading zeros. + for (; idx >= 0; idx -= 3) + { + int24 = (bytes[idx] << 16) + (bytes[idx - 1] << 8) + bytes[idx - 2]; + base8.Append(Convert.ToString(int24, 8).PadLeft(8, '0')); + } + + return base8.ToString(); + } + + /// + /// + /// Reverse a Positive BigInteger ONLY + /// Bitwise ~ operator + /// + /// Input : FF FF FF FF + /// Width : 4 + /// Result : 00 00 00 00 + /// + /// + /// Input : 00 00 00 00 + /// Width : 4 + /// Result : FF FF FF FF + /// + /// Input : FF FF FF FF + /// Width : 8 + /// Result : FF FF FF FF 00 00 00 00 + /// + /// + /// Input : 00 00 00 00 + /// Width : 8 + /// Result : FF FF FF FF FF FF FF FF + /// + /// + /// + /// + /// + public static BigInteger PositiveReverse(this BigInteger input, int width) + { + + var result = new List(); + var bytes = input.ToByteArray(); + var work = new byte[width]; + Array.Copy(bytes, 0, work, 0, bytes.Length - 1); // Length -1 : positive BigInteger + + for (int i = 0; i < work.Length; i++) + { + result.Add((byte)(~work[i])); + } + result.Add(0); // positive BigInteger + return new BigInteger(result.ToArray()); + + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs new file mode 100644 index 0000000000..2b31a0a32a --- /dev/null +++ b/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs @@ -0,0 +1,104 @@ +using System.Collections; +using System.Collections.Generic; +using System.Numerics; + +namespace System.Net +{ + public class IPAddressCollection : IEnumerable, IEnumerator + { + + private IPNetwork _ipnetwork; + private BigInteger _enumerator; + + internal IPAddressCollection(IPNetwork ipnetwork) + { + this._ipnetwork = ipnetwork; + this._enumerator = -1; + } + + + #region Count, Array, Enumerator + + public BigInteger Count + { + get + { + return this._ipnetwork.Total; + } + } + + public IPAddress this[BigInteger i] + { + get + { + if (i >= this.Count) + { + throw new ArgumentOutOfRangeException("i"); + } + byte width = this._ipnetwork.AddressFamily == Sockets.AddressFamily.InterNetwork ? (byte)32 : (byte)128; + IPNetworkCollection ipn = this._ipnetwork.Subnet(width); + return ipn[i].Network; + } + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + + #region IEnumerator Members + + public IPAddress Current + { + get { return this[this._enumerator]; } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + // nothing to dispose + return; + } + + #endregion + + #region IEnumerator Members + + object IEnumerator.Current + { + get { return this.Current; } + } + + public bool MoveNext() + { + this._enumerator++; + if (this._enumerator >= this.Count) + { + return false; + } + return true; + + } + + public void Reset() + { + this._enumerator = -1; + } + + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs new file mode 100644 index 0000000000..6d7785b906 --- /dev/null +++ b/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs @@ -0,0 +1,2170 @@ +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Numerics; +using System.Text.RegularExpressions; + +namespace System.Net +{ + /// + /// IP Network utility class. + /// Use IPNetwork.Parse to create instances. + /// + public class IPNetwork : IComparable + { + + #region properties + + //private uint _network; + private BigInteger _ipaddress; + private AddressFamily _family; + //private uint _netmask; + //private uint _broadcast; + //private uint _firstUsable; + //private uint _lastUsable; + //private uint _usable; + private byte _cidr; + + #endregion + + #region accessors + + private BigInteger _network + { + get + { + BigInteger uintNetwork = this._ipaddress & this._netmask; + return uintNetwork; + } + } + + /// + /// Network address + /// + public IPAddress Network + { + get + { + + return IPNetwork.ToIPAddress(this._network, this._family); + } + } + + /// + /// Address Family + /// + public AddressFamily AddressFamily + { + get + { + return this._family; + } + } + + private BigInteger _netmask + { + get + { + return IPNetwork.ToUint(this._cidr, this._family); + } + } + + /// + /// Netmask + /// + public IPAddress Netmask + { + get + { + return IPNetwork.ToIPAddress(this._netmask, this._family); + } + } + + private BigInteger _broadcast + { + get + { + + int width = this._family == Sockets.AddressFamily.InterNetwork ? 4 : 16; + BigInteger uintBroadcast = this._network + this._netmask.PositiveReverse(width); + return uintBroadcast; + } + } + + /// + /// Broadcast address + /// + public IPAddress Broadcast + { + get + { + if (this._family == Sockets.AddressFamily.InterNetworkV6) + { + return null; + } + return IPNetwork.ToIPAddress(this._broadcast, this._family); + } + } + + /// + /// First usable IP adress in Network + /// + public IPAddress FirstUsable + { + get + { + BigInteger fisrt = this._family == Sockets.AddressFamily.InterNetworkV6 + ? this._network + : (this.Usable <= 0) ? this._network : this._network + 1; + return IPNetwork.ToIPAddress(fisrt, this._family); + } + } + + /// + /// Last usable IP adress in Network + /// + public IPAddress LastUsable + { + get + { + BigInteger last = this._family == Sockets.AddressFamily.InterNetworkV6 + ? this._broadcast + : (this.Usable <= 0) ? this._network : this._broadcast - 1; + return IPNetwork.ToIPAddress(last, this._family); + } + } + + /// + /// Number of usable IP adress in Network + /// + public BigInteger Usable + { + get + { + + if (this._family == Sockets.AddressFamily.InterNetworkV6) + { + return this.Total; + } + byte[] mask = new byte[] { 0xff, 0xff, 0xff, 0xff, 0x00 }; + BigInteger bmask = new BigInteger(mask); + BigInteger usableIps = (_cidr > 30) ? 0 : ((bmask >> _cidr) - 1); + return usableIps; + } + } + + /// + /// Number of IP adress in Network + /// + public BigInteger Total + { + get + { + + int max = this._family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + BigInteger count = BigInteger.Pow(2, (max - _cidr)); + return count; + } + } + + + /// + /// The CIDR netmask notation + /// + public byte Cidr + { + get + { + return this._cidr; + } + } + + #endregion + + #region constructor + +#if TRAVISCI + public +#else + internal +#endif + + IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr) + { + + int maxCidr = family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + if (cidr > maxCidr) + { + throw new ArgumentOutOfRangeException("cidr"); + } + + this._ipaddress = ipaddress; + this._family = family; + this._cidr = cidr; + + } + + #endregion + + #region parsers + + /// + /// 192.168.168.100 - 255.255.255.0 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + public static IPNetwork Parse(string ipaddress, string netmask) + { + + IPNetwork ipnetwork = null; + IPNetwork.InternalParse(false, ipaddress, netmask, out ipnetwork); + return ipnetwork; + } + + /// + /// 192.168.168.100/24 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + public static IPNetwork Parse(string ipaddress, byte cidr) + { + + IPNetwork ipnetwork = null; + IPNetwork.InternalParse(false, ipaddress, cidr, out ipnetwork); + return ipnetwork; + + } + + /// + /// 192.168.168.100 255.255.255.0 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + public static IPNetwork Parse(IPAddress ipaddress, IPAddress netmask) + { + + IPNetwork ipnetwork = null; + IPNetwork.InternalParse(false, ipaddress, netmask, out ipnetwork); + return ipnetwork; + + } + + /// + /// 192.168.0.1/24 + /// 192.168.0.1 255.255.255.0 + /// + /// Network : 192.168.0.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.0.1 + /// End : 192.168.0.254 + /// Broadcast : 192.168.0.255 + /// + /// + /// + public static IPNetwork Parse(string network) + { + + IPNetwork ipnetwork = null; + IPNetwork.InternalParse(false, network, out ipnetwork); + return ipnetwork; + + } + + #endregion + + #region TryParse + + + + /// + /// 192.168.168.100 - 255.255.255.0 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + public static bool TryParse(string ipaddress, string netmask, out IPNetwork ipnetwork) + { + + IPNetwork ipnetwork2 = null; + IPNetwork.InternalParse(true, ipaddress, netmask, out ipnetwork2); + bool parsed = (ipnetwork2 != null); + ipnetwork = ipnetwork2; + return parsed; + + } + + + + /// + /// 192.168.168.100/24 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + public static bool TryParse(string ipaddress, byte cidr, out IPNetwork ipnetwork) + { + + IPNetwork ipnetwork2 = null; + IPNetwork.InternalParse(true, ipaddress, cidr, out ipnetwork2); + bool parsed = (ipnetwork2 != null); + ipnetwork = ipnetwork2; + return parsed; + + } + + /// + /// 192.168.0.1/24 + /// 192.168.0.1 255.255.255.0 + /// + /// Network : 192.168.0.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.0.1 + /// End : 192.168.0.254 + /// Broadcast : 192.168.0.255 + /// + /// + /// + /// + public static bool TryParse(string network, out IPNetwork ipnetwork) + { + + IPNetwork ipnetwork2 = null; + IPNetwork.InternalParse(true, network, out ipnetwork2); + bool parsed = (ipnetwork2 != null); + ipnetwork = ipnetwork2; + return parsed; + + } + + /// + /// 192.168.0.1/24 + /// 192.168.0.1 255.255.255.0 + /// + /// Network : 192.168.0.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.0.1 + /// End : 192.168.0.254 + /// Broadcast : 192.168.0.255 + /// + /// + /// + /// + /// + public static bool TryParse(IPAddress ipaddress, IPAddress netmask, out IPNetwork ipnetwork) + { + + IPNetwork ipnetwork2 = null; + IPNetwork.InternalParse(true, ipaddress, netmask, out ipnetwork2); + bool parsed = (ipnetwork2 != null); + ipnetwork = ipnetwork2; + return parsed; + + } + + + #endregion + + #region InternalParse + + /// + /// 192.168.168.100 - 255.255.255.0 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + private static void InternalParse(bool tryParse, string ipaddress, string netmask, out IPNetwork ipnetwork) + { + + if (string.IsNullOrEmpty(ipaddress)) + { + if (tryParse == false) + { + throw new ArgumentNullException("ipaddress"); + } + ipnetwork = null; + return; + } + + if (string.IsNullOrEmpty(netmask)) + { + if (tryParse == false) + { + throw new ArgumentNullException("netmask"); + } + ipnetwork = null; + return; + } + + IPAddress ip = null; + bool ipaddressParsed = IPAddress.TryParse(ipaddress, out ip); + if (ipaddressParsed == false) + { + if (tryParse == false) + { + throw new ArgumentException("ipaddress"); + } + ipnetwork = null; + return; + } + + IPAddress mask = null; + bool netmaskParsed = IPAddress.TryParse(netmask, out mask); + if (netmaskParsed == false) + { + if (tryParse == false) + { + throw new ArgumentException("netmask"); + } + ipnetwork = null; + return; + } + + IPNetwork.InternalParse(tryParse, ip, mask, out ipnetwork); + } + + private static void InternalParse(bool tryParse, string network, out IPNetwork ipnetwork) + { + + if (string.IsNullOrEmpty(network)) + { + if (tryParse == false) + { + throw new ArgumentNullException("network"); + } + ipnetwork = null; + return; + } + + network = Regex.Replace(network, @"[^0-9a-fA-F\.\/\s\:]+", ""); + network = Regex.Replace(network, @"\s{2,}", " "); + network = network.Trim(); + string[] args = network.Split(new char[] { ' ', '/' }); + byte cidr = 0; + if (args.Length == 1) + { + + if (IPNetwork.TryGuessCidr(args[0], out cidr)) + { + IPNetwork.InternalParse(tryParse, args[0], cidr, out ipnetwork); + return; + } + + if (tryParse == false) + { + throw new ArgumentException("network"); + } + ipnetwork = null; + return; + } + + if (byte.TryParse(args[1], out cidr)) + { + IPNetwork.InternalParse(tryParse, args[0], cidr, out ipnetwork); + return; + } + + IPNetwork.InternalParse(tryParse, args[0], args[1], out ipnetwork); + return; + + } + + + + /// + /// 192.168.168.100 255.255.255.0 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + private static void InternalParse(bool tryParse, IPAddress ipaddress, IPAddress netmask, out IPNetwork ipnetwork) + { + + if (ipaddress == null) + { + if (tryParse == false) + { + throw new ArgumentNullException("ipaddress"); + } + ipnetwork = null; + return; + } + + if (netmask == null) + { + if (tryParse == false) + { + throw new ArgumentNullException("netmask"); + } + ipnetwork = null; + return; + } + + BigInteger uintIpAddress = IPNetwork.ToBigInteger(ipaddress); + byte? cidr2 = null; + bool parsed = IPNetwork.TryToCidr(netmask, out cidr2); + if (parsed == false) + { + if (tryParse == false) + { + throw new ArgumentException("netmask"); + } + ipnetwork = null; + return; + } + byte cidr = (byte)cidr2; + + IPNetwork ipnet = new IPNetwork(uintIpAddress, ipaddress.AddressFamily, cidr); + ipnetwork = ipnet; + + return; + } + + + + /// + /// 192.168.168.100/24 + /// + /// Network : 192.168.168.0 + /// Netmask : 255.255.255.0 + /// Cidr : 24 + /// Start : 192.168.168.1 + /// End : 192.168.168.254 + /// Broadcast : 192.168.168.255 + /// + /// + /// + /// + private static void InternalParse(bool tryParse, string ipaddress, byte cidr, out IPNetwork ipnetwork) + { + + if (string.IsNullOrEmpty(ipaddress)) + { + if (tryParse == false) + { + throw new ArgumentNullException("ipaddress"); + } + ipnetwork = null; + return; + } + + + IPAddress ip = null; + bool ipaddressParsed = IPAddress.TryParse(ipaddress, out ip); + if (ipaddressParsed == false) + { + if (tryParse == false) + { + throw new ArgumentException("ipaddress"); + } + ipnetwork = null; + return; + } + + IPAddress mask = null; + bool parsedNetmask = IPNetwork.TryToNetmask(cidr, ip.AddressFamily, out mask); + if (parsedNetmask == false) + { + if (tryParse == false) + { + throw new ArgumentException("cidr"); + } + ipnetwork = null; + return; + } + + + IPNetwork.InternalParse(tryParse, ip, mask, out ipnetwork); + } + + #endregion + + #region converters + + #region ToUint + + /// + /// Convert an ipadress to decimal + /// 0.0.0.0 -> 0 + /// 0.0.1.0 -> 256 + /// + /// + /// + public static BigInteger ToBigInteger(IPAddress ipaddress) + { + BigInteger? uintIpAddress = null; + IPNetwork.InternalToBigInteger(false, ipaddress, out uintIpAddress); + return (BigInteger)uintIpAddress; + + } + + /// + /// Convert an ipadress to decimal + /// 0.0.0.0 -> 0 + /// 0.0.1.0 -> 256 + /// + /// + /// + public static bool TryToBigInteger(IPAddress ipaddress, out BigInteger? uintIpAddress) + { + BigInteger? uintIpAddress2 = null; + IPNetwork.InternalToBigInteger(true, ipaddress, out uintIpAddress2); + bool parsed = (uintIpAddress2 != null); + uintIpAddress = uintIpAddress2; + return parsed; + } + +#if TRAVISCI + public +#else + internal +#endif + static void InternalToBigInteger(bool tryParse, IPAddress ipaddress, out BigInteger? uintIpAddress) + { + + if (ipaddress == null) + { + if (tryParse == false) + { + throw new ArgumentNullException("ipaddress"); + } + uintIpAddress = null; + return; + } + + byte[] bytes = ipaddress.GetAddressBytes(); + /// 20180217 lduchosal + /// code impossible to reach, GetAddressBytes returns either 4 or 16 bytes length addresses + /// if (bytes.Length != 4 && bytes.Length != 16) { + /// if (tryParse == false) { + /// throw new ArgumentException("bytes"); + /// } + /// uintIpAddress = null; + /// return; + /// } + + Array.Reverse(bytes); + var unsigned = new List(bytes); + unsigned.Add(0); + uintIpAddress = new BigInteger(unsigned.ToArray()); + return; + } + + + /// + /// Convert a cidr to BigInteger netmask + /// + /// + /// + public static BigInteger ToUint(byte cidr, AddressFamily family) + { + + BigInteger? uintNetmask = null; + IPNetwork.InternalToBigInteger(false, cidr, family, out uintNetmask); + return (BigInteger)uintNetmask; + } + + + /// + /// Convert a cidr to uint netmask + /// + /// + /// + public static bool TryToUint(byte cidr, AddressFamily family, out BigInteger? uintNetmask) + { + + BigInteger? uintNetmask2 = null; + IPNetwork.InternalToBigInteger(true, cidr, family, out uintNetmask2); + bool parsed = (uintNetmask2 != null); + uintNetmask = uintNetmask2; + return parsed; + } + + /// + /// Convert a cidr to uint netmask + /// + /// + /// +#if TRAVISCI + public +#else + internal +#endif + static void InternalToBigInteger(bool tryParse, byte cidr, AddressFamily family, out BigInteger? uintNetmask) + { + + if (family == AddressFamily.InterNetwork && cidr > 32) + { + if (tryParse == false) + { + throw new ArgumentOutOfRangeException("cidr"); + } + uintNetmask = null; + return; + } + + if (family == AddressFamily.InterNetworkV6 && cidr > 128) + { + if (tryParse == false) + { + throw new ArgumentOutOfRangeException("cidr"); + } + uintNetmask = null; + return; + } + + if (family != AddressFamily.InterNetwork + && family != AddressFamily.InterNetworkV6) + { + if (tryParse == false) + { + throw new NotSupportedException(family.ToString()); + } + uintNetmask = null; + return; + } + + if (family == AddressFamily.InterNetwork) + { + + uintNetmask = cidr == 0 ? 0 : 0xffffffff << (32 - cidr); + return; + } + + BigInteger mask = new BigInteger(new byte[] { + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0x00 + }); + + BigInteger masked = cidr == 0 ? 0 : mask << (128 - cidr); + byte[] m = masked.ToByteArray(); + byte[] bmask = new byte[17]; + int copy = m.Length > 16 ? 16 : m.Length; + Array.Copy(m, 0, bmask, 0, copy); + uintNetmask = new BigInteger(bmask); + + + } + + #endregion + + #region ToCidr + + /// + /// Convert netmask to CIDR + /// 255.255.255.0 -> 24 + /// 255.255.0.0 -> 16 + /// 255.0.0.0 -> 8 + /// + /// + /// + private static void InternalToCidr(bool tryParse, BigInteger netmask, AddressFamily family, out byte? cidr) + { + + if (!IPNetwork.InternalValidNetmask(netmask, family)) + { + if (tryParse == false) + { + throw new ArgumentException("netmask"); + } + cidr = null; + return; + } + + byte cidr2 = IPNetwork.BitsSet(netmask, family); + cidr = cidr2; + return; + + } + /// + /// Convert netmask to CIDR + /// 255.255.255.0 -> 24 + /// 255.255.0.0 -> 16 + /// 255.0.0.0 -> 8 + /// + /// + /// + public static byte ToCidr(IPAddress netmask) + { + byte? cidr = null; + IPNetwork.InternalToCidr(false, netmask, out cidr); + return (byte)cidr; + } + + /// + /// Convert netmask to CIDR + /// 255.255.255.0 -> 24 + /// 255.255.0.0 -> 16 + /// 255.0.0.0 -> 8 + /// + /// + /// + public static bool TryToCidr(IPAddress netmask, out byte? cidr) + { + byte? cidr2 = null; + IPNetwork.InternalToCidr(true, netmask, out cidr2); + bool parsed = (cidr2 != null); + cidr = cidr2; + return parsed; + } + + private static void InternalToCidr(bool tryParse, IPAddress netmask, out byte? cidr) + { + + if (netmask == null) + { + if (tryParse == false) + { + throw new ArgumentNullException("netmask"); + } + cidr = null; + return; + } + BigInteger? uintNetmask2 = null; + bool parsed = IPNetwork.TryToBigInteger(netmask, out uintNetmask2); + + /// 20180217 lduchosal + /// impossible to reach code. + /// if (parsed == false) { + /// if (tryParse == false) { + /// throw new ArgumentException("netmask"); + /// } + /// cidr = null; + /// return; + /// } + BigInteger uintNetmask = (BigInteger)uintNetmask2; + + byte? cidr2 = null; + IPNetwork.InternalToCidr(tryParse, uintNetmask, netmask.AddressFamily, out cidr2); + cidr = cidr2; + + return; + + } + + + #endregion + + #region ToNetmask + + /// + /// Convert CIDR to netmask + /// 24 -> 255.255.255.0 + /// 16 -> 255.255.0.0 + /// 8 -> 255.0.0.0 + /// + /// + /// + /// + public static IPAddress ToNetmask(byte cidr, AddressFamily family) + { + + IPAddress netmask = null; + IPNetwork.InternalToNetmask(false, cidr, family, out netmask); + return netmask; + } + + /// + /// Convert CIDR to netmask + /// 24 -> 255.255.255.0 + /// 16 -> 255.255.0.0 + /// 8 -> 255.0.0.0 + /// + /// + /// + /// + public static bool TryToNetmask(byte cidr, AddressFamily family, out IPAddress netmask) + { + + IPAddress netmask2 = null; + IPNetwork.InternalToNetmask(true, cidr, family, out netmask2); + bool parsed = (netmask2 != null); + netmask = netmask2; + return parsed; + } + + +#if TRAVISCI + public +#else + internal +#endif + static void InternalToNetmask(bool tryParse, byte cidr, AddressFamily family, out IPAddress netmask) + { + + if (family != AddressFamily.InterNetwork + && family != AddressFamily.InterNetworkV6) + { + if (tryParse == false) + { + throw new ArgumentException("family"); + } + netmask = null; + return; + } + + /// 20180217 lduchosal + /// impossible to reach code, byte cannot be negative : + /// + /// if (cidr < 0) { + /// if (tryParse == false) { + /// throw new ArgumentOutOfRangeException("cidr"); + /// } + /// netmask = null; + /// return; + /// } + + int maxCidr = family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + if (cidr > maxCidr) + { + if (tryParse == false) + { + throw new ArgumentOutOfRangeException("cidr"); + } + netmask = null; + return; + } + + BigInteger mask = IPNetwork.ToUint(cidr, family); + IPAddress netmask2 = IPNetwork.ToIPAddress(mask, family); + netmask = netmask2; + + return; + } + + #endregion + + #endregion + + #region utils + + #region BitsSet + + /// + /// Count bits set to 1 in netmask + /// + /// + /// + /// + private static byte BitsSet(BigInteger netmask, AddressFamily family) + { + + string s = netmask.ToBinaryString(); + return (byte)s.Replace("0", "") + .ToCharArray() + .Length; + + } + + + /// + /// Count bits set to 1 in netmask + /// + /// + /// + public static uint BitsSet(IPAddress netmask) + { + BigInteger uintNetmask = IPNetwork.ToBigInteger(netmask); + uint bits = IPNetwork.BitsSet(uintNetmask, netmask.AddressFamily); + return bits; + } + + #endregion + + #region ValidNetmask + + /// + /// return true if netmask is a valid netmask + /// 255.255.255.0, 255.0.0.0, 255.255.240.0, ... + /// + /// + /// + /// + public static bool ValidNetmask(IPAddress netmask) + { + + if (netmask == null) + { + throw new ArgumentNullException("netmask"); + } + BigInteger uintNetmask = IPNetwork.ToBigInteger(netmask); + bool valid = IPNetwork.InternalValidNetmask(uintNetmask, netmask.AddressFamily); + return valid; + } + +#if TRAVISCI + public +#else + internal +#endif + static bool InternalValidNetmask(BigInteger netmask, AddressFamily family) + { + + if (family != AddressFamily.InterNetwork + && family != AddressFamily.InterNetworkV6) + { + throw new ArgumentException("family"); + } + + var mask = family == AddressFamily.InterNetwork + ? new BigInteger(0x0ffffffff) + : new BigInteger(new byte[]{ + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0x00 + }); + + BigInteger neg = ((~netmask) & (mask)); + bool isNetmask = ((neg + 1) & neg) == 0; + return isNetmask; + + } + + #endregion + + #region ToIPAddress + + /// + /// Transform a uint ipaddress into IPAddress object + /// + /// + /// + public static IPAddress ToIPAddress(BigInteger ipaddress, AddressFamily family) + { + + int width = family == AddressFamily.InterNetwork ? 4 : 16; + byte[] bytes = ipaddress.ToByteArray(); + byte[] bytes2 = new byte[width]; + int copy = bytes.Length > width ? width : bytes.Length; + Array.Copy(bytes, 0, bytes2, 0, copy); + Array.Reverse(bytes2); + + byte[] sized = Resize(bytes2, family); + IPAddress ip = new IPAddress(sized); + return ip; + } + +#if TRAVISCI + public +#else + internal +#endif + static byte[] Resize(byte[] bytes, AddressFamily family) + { + + if (family != AddressFamily.InterNetwork + && family != AddressFamily.InterNetworkV6) + { + throw new ArgumentException("family"); + } + + int width = family == AddressFamily.InterNetwork ? 4 : 16; + + if (bytes.Length > width) + { + throw new ArgumentException("bytes"); + } + + byte[] result = new byte[width]; + Array.Copy(bytes, 0, result, 0, bytes.Length); + return result; + } + + #endregion + + #endregion + + #region contains + + /// + /// return true if ipaddress is contained in network + /// + /// + /// + public bool Contains(IPAddress ipaddress) + { + + if (ipaddress == null) + { + throw new ArgumentNullException("ipaddress"); + } + + if (AddressFamily != ipaddress.AddressFamily) + { + return false; + } + + BigInteger uintNetwork = _network; + BigInteger uintBroadcast = _broadcast; + BigInteger uintAddress = IPNetwork.ToBigInteger(ipaddress); + + bool contains = (uintAddress >= uintNetwork + && uintAddress <= uintBroadcast); + + return contains; + + } + + [Obsolete("static Contains is deprecated, please use instance Contains.")] + public static bool Contains(IPNetwork network, IPAddress ipaddress) + { + + if (network == null) + { + throw new ArgumentNullException("network"); + } + + return network.Contains(ipaddress); + } + + /// + /// return true is network2 is fully contained in network + /// + /// + /// + public bool Contains(IPNetwork network2) + { + + if (network2 == null) + { + throw new ArgumentNullException("network2"); + } + + BigInteger uintNetwork = _network; + BigInteger uintBroadcast = _broadcast; + + BigInteger uintFirst = network2._network; + BigInteger uintLast = network2._broadcast; + + bool contains = (uintFirst >= uintNetwork + && uintLast <= uintBroadcast); + + return contains; + } + + [Obsolete("static Contains is deprecated, please use instance Contains.")] + public static bool Contains(IPNetwork network, IPNetwork network2) + { + + if (network == null) + { + throw new ArgumentNullException("network"); + } + + return network.Contains(network2); + } + + #endregion + + #region overlap + + /// + /// return true is network2 overlap network + /// + /// + /// + public bool Overlap(IPNetwork network2) + { + + if (network2 == null) + { + throw new ArgumentNullException("network2"); + } + + BigInteger uintNetwork = _network; + BigInteger uintBroadcast = _broadcast; + + BigInteger uintFirst = network2._network; + BigInteger uintLast = network2._broadcast; + + bool overlap = + (uintFirst >= uintNetwork && uintFirst <= uintBroadcast) + || (uintLast >= uintNetwork && uintLast <= uintBroadcast) + || (uintFirst <= uintNetwork && uintLast >= uintBroadcast) + || (uintFirst >= uintNetwork && uintLast <= uintBroadcast); + + return overlap; + } + + [Obsolete("static Overlap is deprecated, please use instance Overlap.")] + public static bool Overlap(IPNetwork network, IPNetwork network2) + { + + if (network == null) + { + throw new ArgumentNullException("network"); + } + + return network.Overlap(network2); + } + + #endregion + + #region ToString + + public override string ToString() + { + return string.Format("{0}/{1}", this.Network, this.Cidr); + } + + #endregion + + #region IANA block + + private static readonly Lazy _iana_ablock_reserved = new Lazy(() => IPNetwork.Parse("10.0.0.0/8")); + private static readonly Lazy _iana_bblock_reserved = new Lazy(() => IPNetwork.Parse("172.16.0.0/12")); + private static readonly Lazy _iana_cblock_reserved = new Lazy(() => IPNetwork.Parse("192.168.0.0/16")); + + /// + /// 10.0.0.0/8 + /// + /// + public static IPNetwork IANA_ABLK_RESERVED1 + { + get + { + return _iana_ablock_reserved.Value; + } + } + + /// + /// 172.12.0.0/12 + /// + /// + public static IPNetwork IANA_BBLK_RESERVED1 + { + get + { + return _iana_bblock_reserved.Value; + } + } + + /// + /// 192.168.0.0/16 + /// + /// + public static IPNetwork IANA_CBLK_RESERVED1 + { + get + { + return _iana_cblock_reserved.Value; + } + } + + /// + /// return true if ipaddress is contained in + /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1 + /// + /// + /// + public static bool IsIANAReserved(IPAddress ipaddress) + { + + if (ipaddress == null) + { + throw new ArgumentNullException("ipaddress"); + } + + return IPNetwork.IANA_ABLK_RESERVED1.Contains(ipaddress) + || IPNetwork.IANA_BBLK_RESERVED1.Contains(ipaddress) + || IPNetwork.IANA_CBLK_RESERVED1.Contains(ipaddress); + } + + /// + /// return true if ipnetwork is contained in + /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1 + /// + /// + public bool IsIANAReserved() + { + return IPNetwork.IANA_ABLK_RESERVED1.Contains(this) + || IPNetwork.IANA_BBLK_RESERVED1.Contains(this) + || IPNetwork.IANA_CBLK_RESERVED1.Contains(this); + } + + [Obsolete("static IsIANAReserved is deprecated, please use instance IsIANAReserved.")] + public static bool IsIANAReserved(IPNetwork ipnetwork) + { + + if (ipnetwork == null) + { + throw new ArgumentNullException("ipnetwork"); + } + + return ipnetwork.IsIANAReserved(); + } + + #endregion + + #region Subnet + + /// + /// Subnet a network into multiple nets of cidr mask + /// Subnet 192.168.0.0/24 into cidr 25 gives 192.168.0.0/25, 192.168.0.128/25 + /// Subnet 10.0.0.0/8 into cidr 9 gives 10.0.0.0/9, 10.128.0.0/9 + /// + /// + /// + public IPNetworkCollection Subnet(byte cidr) + { + IPNetworkCollection ipnetworkCollection = null; + IPNetwork.InternalSubnet(false, this, cidr, out ipnetworkCollection); + return ipnetworkCollection; + } + + [Obsolete("static Subnet is deprecated, please use instance Subnet.")] + public static IPNetworkCollection Subnet(IPNetwork network, byte cidr) + { + if (network == null) + { + throw new ArgumentNullException("network"); + } + return network.Subnet(cidr); + } + + /// + /// Subnet a network into multiple nets of cidr mask + /// Subnet 192.168.0.0/24 into cidr 25 gives 192.168.0.0/25, 192.168.0.128/25 + /// Subnet 10.0.0.0/8 into cidr 9 gives 10.0.0.0/9, 10.128.0.0/9 + /// + /// + /// + public bool TrySubnet(byte cidr, out IPNetworkCollection ipnetworkCollection) + { + IPNetworkCollection inc = null; + IPNetwork.InternalSubnet(true, this, cidr, out inc); + if (inc == null) + { + ipnetworkCollection = null; + return false; + } + + ipnetworkCollection = inc; + return true; + } + + [Obsolete("static TrySubnet is deprecated, please use instance TrySubnet.")] + public static bool TrySubnet(IPNetwork network, byte cidr, out IPNetworkCollection ipnetworkCollection) + { + if (network == null) + { + throw new ArgumentNullException("network"); + } + return network.TrySubnet(cidr, out ipnetworkCollection); + } + +#if TRAVISCI + public +#else + internal +#endif + static void InternalSubnet(bool trySubnet, IPNetwork network, byte cidr, out IPNetworkCollection ipnetworkCollection) + { + + if (network == null) + { + if (trySubnet == false) + { + throw new ArgumentNullException("network"); + } + ipnetworkCollection = null; + return; + } + + int maxCidr = network._family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + if (cidr > maxCidr) + { + if (trySubnet == false) + { + throw new ArgumentOutOfRangeException("cidr"); + } + ipnetworkCollection = null; + return; + } + + if (cidr < network.Cidr) + { + if (trySubnet == false) + { + throw new ArgumentException("cidr"); + } + ipnetworkCollection = null; + return; + } + + ipnetworkCollection = new IPNetworkCollection(network, cidr); + return; + } + + + + #endregion + + #region Supernet + + /// + /// Supernet two consecutive cidr equal subnet into a single one + /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 + /// 10.1.0.0/16 + 10.0.0.0/16 = 10.0.0.0/15 + /// 192.168.0.0/24 + 192.168.0.0/25 = 192.168.0.0/24 + /// + /// + /// + public IPNetwork Supernet(IPNetwork network2) + { + IPNetwork supernet = null; + IPNetwork.InternalSupernet(false, this, network2, out supernet); + return supernet; + } + + [Obsolete("static Supernet is deprecated, please use instance Supernet.")] + public static IPNetwork Supernet(IPNetwork network, IPNetwork network2) + { + return network.Supernet(network2); + } + + /// + /// Try to supernet two consecutive cidr equal subnet into a single one + /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 + /// 10.1.0.0/16 + 10.0.0.0/16 = 10.0.0.0/15 + /// 192.168.0.0/24 + 192.168.0.0/25 = 192.168.0.0/24 + /// + /// + /// + public bool TrySupernet(IPNetwork network2, out IPNetwork supernet) + { + + IPNetwork outSupernet = null; + IPNetwork.InternalSupernet(true, this, network2, out outSupernet); + bool parsed = (outSupernet != null); + supernet = outSupernet; + return parsed; + } + + [Obsolete("static TrySupernet is deprecated, please use instance TrySupernet.")] + public static bool TrySupernet(IPNetwork network, IPNetwork network2, out IPNetwork supernet) + { + if (network == null) + { + throw new ArgumentNullException("network"); + } + return network.TrySupernet(network2, out supernet); + } + +#if TRAVISCI + public +#else + internal +#endif + static void InternalSupernet(bool trySupernet, IPNetwork network1, IPNetwork network2, out IPNetwork supernet) + { + + if (network1 == null) + { + if (trySupernet == false) + { + throw new ArgumentNullException("network1"); + } + supernet = null; + return; + } + + if (network2 == null) + { + if (trySupernet == false) + { + throw new ArgumentNullException("network2"); + } + supernet = null; + return; + } + + + if (network1.Contains(network2)) + { + supernet = new IPNetwork(network1._network, network1._family, network1.Cidr); + return; + } + + if (network2.Contains(network1)) + { + supernet = new IPNetwork(network2._network, network2._family, network2.Cidr); + return; + } + + if (network1._cidr != network2._cidr) + { + if (trySupernet == false) + { + throw new ArgumentException("cidr"); + } + supernet = null; + return; + } + + IPNetwork first = (network1._network < network2._network) ? network1 : network2; + IPNetwork last = (network1._network > network2._network) ? network1 : network2; + + /// Starting from here : + /// network1 and network2 have the same cidr, + /// network1 does not contain network2, + /// network2 does not contain network1, + /// first is the lower subnet + /// last is the higher subnet + + + if ((first._broadcast + 1) != last._network) + { + if (trySupernet == false) + { + throw new ArgumentOutOfRangeException("network"); + } + supernet = null; + return; + } + + BigInteger uintSupernet = first._network; + byte cidrSupernet = (byte)(first._cidr - 1); + + IPNetwork networkSupernet = new IPNetwork(uintSupernet, first._family, cidrSupernet); + if (networkSupernet._network != first._network) + { + if (trySupernet == false) + { + throw new ArgumentException("network"); + } + supernet = null; + return; + } + supernet = networkSupernet; + return; + } + + #endregion + + #region GetHashCode + + public override int GetHashCode() + { + return string.Format("{0}|{1}|{2}", + this._ipaddress.GetHashCode(), + this._network.GetHashCode(), + this._cidr.GetHashCode()).GetHashCode(); + } + + #endregion + + #region SupernetArray + + /// + /// Supernet a list of subnet + /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 + /// 192.168.0.0/24 + 192.168.1.0/24 + 192.168.2.0/24 + 192.168.3.0/24 = 192.168.0.0/22 + /// + /// + /// + /// + public static IPNetwork[] Supernet(IPNetwork[] ipnetworks) + { + IPNetwork[] supernet; + InternalSupernet(false, ipnetworks, out supernet); + return supernet; + } + + /// + /// Supernet a list of subnet + /// 192.168.0.0/24 + 192.168.1.0/24 = 192.168.0.0/23 + /// 192.168.0.0/24 + 192.168.1.0/24 + 192.168.2.0/24 + 192.168.3.0/24 = 192.168.0.0/22 + /// + /// + /// + /// + public static bool TrySupernet(IPNetwork[] ipnetworks, out IPNetwork[] supernet) + { + bool supernetted = InternalSupernet(true, ipnetworks, out supernet); + return supernetted; + + } + +#if TRAVISCI + public +#else + internal +#endif + static bool InternalSupernet(bool trySupernet, IPNetwork[] ipnetworks, out IPNetwork[] supernet) + { + + if (ipnetworks == null) + { + if (trySupernet == false) + { + throw new ArgumentNullException("ipnetworks"); + } + supernet = null; + return false; + } + + if (ipnetworks.Length <= 0) + { + supernet = new IPNetwork[0]; + return true; + } + + List supernetted = new List(); + List ipns = IPNetwork.Array2List(ipnetworks); + Stack current = IPNetwork.List2Stack(ipns); + int previousCount = 0; + int currentCount = current.Count; + + while (previousCount != currentCount) + { + + supernetted.Clear(); + while (current.Count > 1) + { + IPNetwork ipn1 = current.Pop(); + IPNetwork ipn2 = current.Peek(); + + IPNetwork outNetwork = null; + bool success = ipn1.TrySupernet(ipn2, out outNetwork); + if (success) + { + current.Pop(); + current.Push(outNetwork); + } + else + { + supernetted.Add(ipn1); + } + } + if (current.Count == 1) + { + supernetted.Add(current.Pop()); + } + + previousCount = currentCount; + currentCount = supernetted.Count; + current = IPNetwork.List2Stack(supernetted); + + } + supernet = supernetted.ToArray(); + return true; + } + + private static Stack List2Stack(List list) + { + Stack stack = new Stack(); + list.ForEach(new Action( + delegate (IPNetwork ipn) + { + stack.Push(ipn); + } + )); + return stack; + } + + private static List Array2List(IPNetwork[] array) + { + List ipns = new List(); + ipns.AddRange(array); + IPNetwork.RemoveNull(ipns); + ipns.Sort(new Comparison( + delegate (IPNetwork ipn1, IPNetwork ipn2) + { + int networkCompare = ipn1._network.CompareTo(ipn2._network); + if (networkCompare == 0) + { + int cidrCompare = ipn1._cidr.CompareTo(ipn2._cidr); + return cidrCompare; + } + return networkCompare; + } + )); + ipns.Reverse(); + + return ipns; + } + + private static void RemoveNull(List ipns) + { + ipns.RemoveAll(new Predicate( + delegate (IPNetwork ipn) + { + if (ipn == null) + { + return true; + } + return false; + } + )); + + } + + #endregion + + #region WideSubnet + + public static IPNetwork WideSubnet(string start, string end) + { + + if (string.IsNullOrEmpty(start)) + { + throw new ArgumentNullException("start"); + } + + if (string.IsNullOrEmpty(end)) + { + throw new ArgumentNullException("end"); + } + + IPAddress startIP; + if (!IPAddress.TryParse(start, out startIP)) + { + throw new ArgumentException("start"); + } + + IPAddress endIP; + if (!IPAddress.TryParse(end, out endIP)) + { + throw new ArgumentException("end"); + } + + if (startIP.AddressFamily != endIP.AddressFamily) + { + throw new NotSupportedException("MixedAddressFamily"); + } + + IPNetwork ipnetwork = new IPNetwork(0, startIP.AddressFamily, 0); + for (byte cidr = 32; cidr >= 0; cidr--) + { + IPNetwork wideSubnet = IPNetwork.Parse(start, cidr); + if (wideSubnet.Contains(endIP)) + { + ipnetwork = wideSubnet; + break; + } + } + return ipnetwork; + + } + + public static bool TryWideSubnet(IPNetwork[] ipnetworks, out IPNetwork ipnetwork) + { + IPNetwork ipn = null; + IPNetwork.InternalWideSubnet(true, ipnetworks, out ipn); + if (ipn == null) + { + ipnetwork = null; + return false; + } + ipnetwork = ipn; + return true; + } + + public static IPNetwork WideSubnet(IPNetwork[] ipnetworks) + { + IPNetwork ipn = null; + IPNetwork.InternalWideSubnet(false, ipnetworks, out ipn); + return ipn; + } + + internal static void InternalWideSubnet(bool tryWide, IPNetwork[] ipnetworks, out IPNetwork ipnetwork) + { + + if (ipnetworks == null) + { + if (tryWide == false) + { + throw new ArgumentNullException("ipnetworks"); + } + ipnetwork = null; + return; + } + + + IPNetwork[] nnin = Array.FindAll(ipnetworks, new Predicate( + delegate (IPNetwork ipnet) { + return ipnet != null; + } + )); + + if (nnin.Length <= 0) + { + if (tryWide == false) + { + throw new ArgumentException("ipnetworks"); + } + ipnetwork = null; + return; + } + + if (nnin.Length == 1) + { + IPNetwork ipn0 = nnin[0]; + ipnetwork = ipn0; + return; + } + + Array.Sort(nnin); + IPNetwork nnin0 = nnin[0]; + BigInteger uintNnin0 = nnin0._ipaddress; + + IPNetwork nninX = nnin[nnin.Length - 1]; + IPAddress ipaddressX = nninX.Broadcast; + + AddressFamily family = ipnetworks[0]._family; + foreach (var ipnx in ipnetworks) + { + if (ipnx._family != family) + { + throw new ArgumentException("MixedAddressFamily"); + } + } + + IPNetwork ipn = new IPNetwork(0, family, 0); + for (byte cidr = nnin0._cidr; cidr >= 0; cidr--) + { + IPNetwork wideSubnet = new IPNetwork(uintNnin0, family, cidr); + if (wideSubnet.Contains(ipaddressX)) + { + ipn = wideSubnet; + break; + } + } + + ipnetwork = ipn; + return; + } + + #endregion + + #region Print + + /// + /// Print an ipnetwork in a clear representation string + /// + /// + public string Print() + { + + StringWriter sw = new StringWriter(); + + sw.WriteLine("IPNetwork : {0}", ToString()); + sw.WriteLine("Network : {0}", Network); + sw.WriteLine("Netmask : {0}", Netmask); + sw.WriteLine("Cidr : {0}", Cidr); + sw.WriteLine("Broadcast : {0}", Broadcast); + sw.WriteLine("FirstUsable : {0}", FirstUsable); + sw.WriteLine("LastUsable : {0}", LastUsable); + sw.WriteLine("Usable : {0}", Usable); + + return sw.ToString(); + } + + [Obsolete("static Print is deprecated, please use instance Print.")] + public static string Print(IPNetwork ipnetwork) + { + + if (ipnetwork == null) + { + throw new ArgumentNullException("ipnetwork"); + } + + return ipnetwork.Print(); + } + + #endregion + + #region TryGuessCidr + + /// + /// + /// Class Leading bits Default netmask + /// A (CIDR /8) 00 255.0.0.0 + /// A (CIDR /8) 01 255.0.0.0 + /// B (CIDR /16) 10 255.255.0.0 + /// C (CIDR /24) 11 255.255.255.0 + /// + /// + /// + /// + /// + public static bool TryGuessCidr(string ip, out byte cidr) + { + + IPAddress ipaddress = null; + bool parsed = IPAddress.TryParse(string.Format("{0}", ip), out ipaddress); + if (parsed == false) + { + cidr = 0; + return false; + } + + if (ipaddress.AddressFamily == AddressFamily.InterNetworkV6) + { + cidr = 64; + return true; + } + BigInteger uintIPAddress = IPNetwork.ToBigInteger(ipaddress); + uintIPAddress = uintIPAddress >> 29; + if (uintIPAddress <= 3) + { + cidr = 8; + return true; + } + else if (uintIPAddress <= 5) + { + cidr = 16; + return true; + } + else if (uintIPAddress <= 6) + { + cidr = 24; + return true; + } + + cidr = 0; + return false; + + } + + /// + /// Try to parse cidr. Have to be >= 0 and <= 32 or 128 + /// + /// + /// + /// + public static bool TryParseCidr(string sidr, AddressFamily family, out byte? cidr) + { + + byte b = 0; + if (!byte.TryParse(sidr, out b)) + { + cidr = null; + return false; + } + + IPAddress netmask = null; + if (!IPNetwork.TryToNetmask(b, family, out netmask)) + { + cidr = null; + return false; + } + + cidr = b; + return true; + } + + #endregion + + #region ListIPAddress + + [Obsolete("static ListIPAddress is deprecated, please use instance ListIPAddress.")] + public static IPAddressCollection ListIPAddress(IPNetwork ipnetwork) + { + return ipnetwork.ListIPAddress(); + } + + public IPAddressCollection ListIPAddress() + { + return new IPAddressCollection(this); + } + + #endregion + + /** + * Need a better way to do it + * +#region TrySubstractNetwork + + public static bool TrySubstractNetwork(IPNetwork[] ipnetworks, IPNetwork substract, out IEnumerable result) { + + if (ipnetworks == null) { + result = null; + return false; + } + if (ipnetworks.Length <= 0) { + result = null; + return false; + } + if (substract == null) { + result = null; + return false; + } + var results = new List(); + foreach (var ipn in ipnetworks) { + if (!Overlap(ipn, substract)) { + results.Add(ipn); + continue; + } + + var collection = ipn.Subnet(substract.Cidr); + var rtemp = new List(); + foreach(var subnet in collection) { + if (subnet != substract) { + rtemp.Add(subnet); + } + } + var supernets = Supernet(rtemp.ToArray()); + results.AddRange(supernets); + } + result = results; + return true; + } +#endregion + * **/ + + #region IComparable Members + + public static Int32 Compare(IPNetwork left, IPNetwork right) + { + // two null IPNetworks are equal + if (ReferenceEquals(left, null) && ReferenceEquals(right, null)) return 0; + + // two same IPNetworks are equal + if (ReferenceEquals(left, right)) return 0; + + // null is always sorted first + if (ReferenceEquals(left, null)) return -1; + if (ReferenceEquals(right, null)) return 1; + + // first test the network + var result = left._network.CompareTo(right._network); + if (result != 0) return result; + + // then test the cidr + result = left._cidr.CompareTo(right._cidr); + return result; + } + + public Int32 CompareTo(IPNetwork other) + { + return Compare(this, other); + } + + public Int32 CompareTo(Object obj) + { + // null is at less + if (obj == null) return 1; + + // convert to a proper Cidr object + var other = obj as IPNetwork; + + // type problem if null + if (other == null) + { + throw new ArgumentException( + "The supplied parameter is an invalid type. Please supply an IPNetwork type.", + "obj"); + } + + // perform the comparision + return CompareTo(other); + } + + #endregion + + #region IEquatable Members + + public static Boolean Equals(IPNetwork left, IPNetwork right) + { + return Compare(left, right) == 0; + } + + public Boolean Equals(IPNetwork other) + { + return Equals(this, other); + } + + public override Boolean Equals(Object obj) + { + return Equals(this, obj as IPNetwork); + } + + #endregion + + #region Operators + + public static Boolean operator ==(IPNetwork left, IPNetwork right) + { + return Equals(left, right); + } + + public static Boolean operator !=(IPNetwork left, IPNetwork right) + { + return !Equals(left, right); + } + + public static Boolean operator <(IPNetwork left, IPNetwork right) + { + return Compare(left, right) < 0; + } + + public static Boolean operator >(IPNetwork left, IPNetwork right) + { + return Compare(left, right) > 0; + } + + #endregion + + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs new file mode 100644 index 0000000000..35cff88dc2 --- /dev/null +++ b/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs @@ -0,0 +1,144 @@ +using System.Collections; +using System.Collections.Generic; +using System.Numerics; + +namespace System.Net +{ + public class IPNetworkCollection : IEnumerable, IEnumerator + { + + private BigInteger _enumerator; + private byte _cidrSubnet; + private IPNetwork _ipnetwork; + + private byte _cidr + { + get { return this._ipnetwork.Cidr; } + } + private BigInteger _broadcast + { + get { return IPNetwork.ToBigInteger(this._ipnetwork.Broadcast); } + } + private BigInteger _lastUsable + { + get { return IPNetwork.ToBigInteger(this._ipnetwork.LastUsable); } + } + private BigInteger _network + { + get { return IPNetwork.ToBigInteger(this._ipnetwork.Network); } + } +#if TRAVISCI + public +#else + internal +#endif + IPNetworkCollection(IPNetwork ipnetwork, byte cidrSubnet) + { + + int maxCidr = ipnetwork.AddressFamily == Sockets.AddressFamily.InterNetwork ? 32 : 128; + if (cidrSubnet > maxCidr) + { + throw new ArgumentOutOfRangeException("cidrSubnet"); + } + + if (cidrSubnet < ipnetwork.Cidr) + { + throw new ArgumentException("cidr"); + } + + this._cidrSubnet = cidrSubnet; + this._ipnetwork = ipnetwork; + this._enumerator = -1; + } + + #region Count, Array, Enumerator + + public BigInteger Count + { + get + { + BigInteger count = BigInteger.Pow(2, this._cidrSubnet - this._cidr); + return count; + } + } + + public IPNetwork this[BigInteger i] + { + get + { + if (i >= this.Count) + { + throw new ArgumentOutOfRangeException("i"); + } + + BigInteger last = this._ipnetwork.AddressFamily == Sockets.AddressFamily.InterNetworkV6 + ? this._lastUsable : this._broadcast; + BigInteger increment = (last - this._network) / this.Count; + BigInteger uintNetwork = this._network + ((increment + 1) * i); + IPNetwork ipn = new IPNetwork(uintNetwork, this._ipnetwork.AddressFamily, this._cidrSubnet); + return ipn; + } + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + + #region IEnumerator Members + + public IPNetwork Current + { + get { return this[this._enumerator]; } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + // nothing to dispose + return; + } + + #endregion + + #region IEnumerator Members + + object IEnumerator.Current + { + get { return this.Current; } + } + + public bool MoveNext() + { + this._enumerator++; + if (this._enumerator >= this.Count) + { + return false; + } + return true; + + } + + public void Reset() + { + this._enumerator = -1; + } + + #endregion + + #endregion + + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt b/Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt new file mode 100644 index 0000000000..45d7392ac4 --- /dev/null +++ b/Emby.Server.Implementations/Networking/IPNetwork/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2015, lduchosal +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 60da8a0124..20abaf27c2 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -11,6 +11,8 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; +using System.Numerics; namespace Emby.Server.Implementations.Networking { @@ -19,27 +21,32 @@ namespace Emby.Server.Implementations.Networking protected ILogger Logger { get; private set; } public event EventHandler NetworkChanged; + public Func LocalSubnetsFn { get; set; } - public NetworkManager(ILogger logger) + public NetworkManager(ILogger logger, IEnvironmentInfo environment) { Logger = logger; - try + // In FreeBSD these events cause a crash + if (environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.BSD) { - NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged; - } - catch (Exception ex) - { - Logger.ErrorException("Error binding to NetworkAddressChanged event", ex); - } + try + { + NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged; + } + catch (Exception ex) + { + Logger.ErrorException("Error binding to NetworkAddressChanged event", ex); + } - try - { - NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged; - } - catch (Exception ex) - { - Logger.ErrorException("Error binding to NetworkChange_NetworkAvailabilityChanged event", ex); + try + { + NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged; + } + catch (Exception ex) + { + Logger.ErrorException("Error binding to NetworkChange_NetworkAvailabilityChanged event", ex); + } } } @@ -60,6 +67,7 @@ namespace Emby.Server.Implementations.Networking lock (_localIpAddressSyncLock) { _localIpAddresses = null; + _macAddresses = null; } if (NetworkChanged != null) { @@ -67,16 +75,16 @@ namespace Emby.Server.Implementations.Networking } } - private List _localIpAddresses; + private IpAddressInfo[] _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - public List GetLocalIpAddresses() + public IpAddressInfo[] GetLocalIpAddresses() { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToList(); + var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToArray(); _localIpAddresses = addresses; @@ -119,6 +127,11 @@ namespace Emby.Server.Implementations.Networking } public bool IsInPrivateAddressSpace(string endpoint) + { + return IsInPrivateAddressSpace(endpoint, true); + } + + private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets) { if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) { @@ -146,12 +159,24 @@ namespace Emby.Server.Implementations.Networking return Is172AddressPrivate(endpoint); } - return endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || + if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase) || - //endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || - IsInPrivateAddressSpaceAndLocalSubnet(endpoint); + endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) + { + return true; + } + + return false; } public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint) @@ -238,9 +263,38 @@ namespace Emby.Server.Implementations.Networking return IsInLocalNetworkInternal(endpoint, true); } - public bool IsInLocalNetworkInternal(string endpoint, bool resolveHost) + public bool IsAddressInSubnets(string addressString, string[] subnets) { - if (string.IsNullOrWhiteSpace(endpoint)) + return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets); + } + + private bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets) + { + foreach (var subnet in subnets) + { + var normalizedSubnet = subnet.Trim(); + + if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (normalizedSubnet.IndexOf('/') != -1) + { + var ipnetwork = IPNetwork.Parse(normalizedSubnet); + if (ipnetwork.Contains(address)) + { + return true; + } + } + } + + return false; + } + + private bool IsInLocalNetworkInternal(string endpoint, bool resolveHost) + { + if (string.IsNullOrEmpty(endpoint)) { throw new ArgumentNullException("endpoint"); } @@ -250,11 +304,25 @@ namespace Emby.Server.Implementations.Networking { var addressString = address.ToString(); + var localSubnetsFn = LocalSubnetsFn; + if (localSubnetsFn != null) + { + var localSubnets = localSubnetsFn(); + foreach (var subnet in localSubnets) + { + // only validate if there's at least one valid entry + if (!string.IsNullOrWhiteSpace(subnet)) + { + return IsAddressInSubnets(address, addressString, localSubnets) || IsInPrivateAddressSpace(addressString, false); + } + } + } + int lengthMatch = 100; if (address.AddressFamily == AddressFamily.InterNetwork) { lengthMatch = 4; - if (IsInPrivateAddressSpace(addressString)) + if (IsInPrivateAddressSpace(addressString, true)) { return true; } @@ -262,7 +330,7 @@ namespace Emby.Server.Implementations.Networking else if (address.AddressFamily == AddressFamily.InterNetworkV6) { lengthMatch = 9; - if (IsInPrivateAddressSpace(endpoint)) + if (IsInPrivateAddressSpace(endpoint, true)) { return true; } @@ -353,13 +421,7 @@ namespace Emby.Server.Implementations.Networking return new List(); } - //if (!_validNetworkInterfaceTypes.Contains(network.NetworkInterfaceType)) - //{ - // return new List(); - //} - return ipProperties.UnicastAddresses - //.Where(i => i.IsDnsEligible) .Select(i => i.Address) .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6) .ToList(); @@ -408,16 +470,40 @@ namespace Emby.Server.Implementations.Networking } } - /// - /// Returns MAC Address from first Network Card in Computer - /// - /// [string] MAC Address - public string GetMacAddress() + private List _macAddresses; + public List GetMacAddresses() + { + if (_macAddresses == null) + { + _macAddresses = GetMacAddressesInternal(); + } + return _macAddresses; + } + + private List GetMacAddressesInternal() { return NetworkInterface.GetAllNetworkInterfaces() .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .Select(i => BitConverter.ToString(i.GetPhysicalAddress().GetAddressBytes())) - .FirstOrDefault(); + .Select(i => + { + try + { + var physicalAddress = i.GetPhysicalAddress(); + + if (physicalAddress == null) + { + return null; + } + + return physicalAddress.ToString(); + } + catch (Exception ex) + { + return null; + } + }) + .Where(i => i != null) + .ToList(); } /// diff --git a/Emby.Server.Implementations/News/NewsEntryPoint.cs b/Emby.Server.Implementations/News/NewsEntryPoint.cs index 74366233c6..3ce3d23151 100644 --- a/Emby.Server.Implementations/News/NewsEntryPoint.cs +++ b/Emby.Server.Implementations/News/NewsEntryPoint.cs @@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.News var requestOptions = new HttpRequestOptions { - Url = "http://emby.media/community/index.php?/blog/rss/1-media-browser-developers-blog", + Url = "https://emby.media/community/index.php?/blog/rss/1-media-browser-developers-blog", Progress = new SimpleProgress(), UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36", BufferContent = false @@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.News Name = i.Title, Description = i.Description, Url = i.Link, - UserIds = _userManager.Users.Select(u => u.Id.ToString("N")).ToList() + UserIds = _userManager.Users.Select(u => u.Id).ToArray() }, cancellationToken)); @@ -274,7 +274,6 @@ namespace Emby.Server.Implementations.News _timer.Dispose(); _timer = null; } - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs b/Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs deleted file mode 100644 index d74667c486..0000000000 --- a/Emby.Server.Implementations/Notifications/IConfigurableNotificationService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Emby.Server.Implementations.Notifications -{ - public interface IConfigurableNotificationService - { - bool IsHidden { get; } - bool IsEnabled(string notificationType); - } -} diff --git a/Emby.Server.Implementations/Notifications/InternalNotificationService.cs b/Emby.Server.Implementations/Notifications/InternalNotificationService.cs deleted file mode 100644 index 61c564f188..0000000000 --- a/Emby.Server.Implementations/Notifications/InternalNotificationService.cs +++ /dev/null @@ -1,61 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Notifications; -using System.Threading; -using System.Threading.Tasks; -using System; - -namespace Emby.Server.Implementations.Notifications -{ - public class InternalNotificationService : INotificationService, IConfigurableNotificationService - { - private readonly INotificationsRepository _repo; - - public InternalNotificationService(INotificationsRepository repo) - { - _repo = repo; - } - - public string Name - { - get { return "Dashboard Notifications"; } - } - - public Task SendNotification(UserNotification request, CancellationToken cancellationToken) - { - return _repo.AddNotification(new Notification - { - Date = request.Date, - Description = request.Description, - Level = request.Level, - Name = request.Name, - Url = request.Url, - UserId = request.User.Id.ToString("N") - - }, cancellationToken); - } - - public bool IsEnabledForUser(User user) - { - return user.Policy.IsAdministrator; - } - - public bool IsHidden - { - get { return true; } - } - - public bool IsEnabled(string notificationType) - { - if (notificationType.IndexOf("playback", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - if (notificationType.IndexOf("newlibrarycontent", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - return true; - } - } -} diff --git a/Emby.Server.Implementations/Notifications/Notifications.cs b/Emby.Server.Implementations/Notifications/Notifications.cs deleted file mode 100644 index b7e1d65593..0000000000 --- a/Emby.Server.Implementations/Notifications/Notifications.cs +++ /dev/null @@ -1,566 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Notifications; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Model.Threading; -using MediaBrowser.Model.Dto; - -namespace Emby.Server.Implementations.Notifications -{ - /// - /// Creates notifications for various system events - /// - public class Notifications : IServerEntryPoint - { - private readonly IInstallationManager _installationManager; - private readonly IUserManager _userManager; - private readonly ILogger _logger; - - private readonly ITaskManager _taskManager; - private readonly INotificationManager _notificationManager; - - private readonly ILibraryManager _libraryManager; - private readonly ISessionManager _sessionManager; - private readonly IServerApplicationHost _appHost; - private readonly ITimerFactory _timerFactory; - - private ITimer LibraryUpdateTimer { get; set; } - private readonly object _libraryChangedSyncLock = new object(); - - private readonly IConfigurationManager _config; - private readonly IDeviceManager _deviceManager; - - public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager, ITimerFactory timerFactory) - { - _installationManager = installationManager; - _userManager = userManager; - _logger = logger; - _taskManager = taskManager; - _notificationManager = notificationManager; - _libraryManager = libraryManager; - _sessionManager = sessionManager; - _appHost = appHost; - _config = config; - _deviceManager = deviceManager; - _timerFactory = timerFactory; - } - - public void Run() - { - _installationManager.PluginInstalled += _installationManager_PluginInstalled; - _installationManager.PluginUpdated += _installationManager_PluginUpdated; - _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; - _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; - - _taskManager.TaskCompleted += _taskManager_TaskCompleted; - - _userManager.UserCreated += _userManager_UserCreated; - _libraryManager.ItemAdded += _libraryManager_ItemAdded; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; - _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; - _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; - _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; - _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; - - _userManager.UserLockedOut += _userManager_UserLockedOut; - } - - async void _userManager_UserLockedOut(object sender, GenericEventArgs e) - { - var type = NotificationType.UserLockedOut.ToString(); - - var notification = new NotificationRequest - { - NotificationType = type - }; - - notification.Variables["UserName"] = e.Argument.Name; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs e) - { - var type = NotificationType.CameraImageUploaded.ToString(); - - var notification = new NotificationRequest - { - NotificationType = type - }; - - notification.Variables["DeviceName"] = e.Argument.Device.Name; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _appHost_ApplicationUpdated(object sender, GenericEventArgs e) - { - var type = NotificationType.ApplicationUpdateInstalled.ToString(); - - var notification = new NotificationRequest - { - NotificationType = type, - Url = e.Argument.infoUrl - }; - - notification.Variables["Version"] = e.Argument.versionStr; - notification.Variables["ReleaseNotes"] = e.Argument.description; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _installationManager_PluginUpdated(object sender, GenericEventArgs> e) - { - var type = NotificationType.PluginUpdateInstalled.ToString(); - - var installationInfo = e.Argument.Item1; - - var notification = new NotificationRequest - { - Description = e.Argument.Item2.description, - NotificationType = type - }; - - notification.Variables["Name"] = installationInfo.Name; - notification.Variables["Version"] = installationInfo.Version.ToString(); - notification.Variables["ReleaseNotes"] = e.Argument.Item2.description; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _installationManager_PluginInstalled(object sender, GenericEventArgs e) - { - var type = NotificationType.PluginInstalled.ToString(); - - var installationInfo = e.Argument; - - var notification = new NotificationRequest - { - Description = installationInfo.description, - NotificationType = type - }; - - notification.Variables["Name"] = installationInfo.name; - notification.Variables["Version"] = installationInfo.versionStr; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e) - { - // This notification is for users who can't auto-update (aka running as service) - if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate) - { - return; - } - - var type = NotificationType.ApplicationUpdateAvailable.ToString(); - - var notification = new NotificationRequest - { - Description = "Please see emby.media for details.", - NotificationType = type - }; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _appHost_HasPendingRestartChanged(object sender, EventArgs e) - { - if (!_appHost.HasPendingRestart) - { - return; - } - - var type = NotificationType.ServerRestartRequired.ToString(); - - var notification = new NotificationRequest - { - NotificationType = type - }; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - private NotificationOptions GetOptions() - { - return _config.GetConfiguration("notifications"); - } - - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) - { - var item = e.MediaInfo; - - if (item == null) - { - _logger.Warn("PlaybackStart reported with null media info."); - return; - } - - var video = e.Item as Video; - if (video != null && video.IsThemeMedia) - { - return; - } - - var type = GetPlaybackNotificationType(item.MediaType); - - SendPlaybackNotification(type, e); - } - - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) - { - var item = e.MediaInfo; - - if (item == null) - { - _logger.Warn("PlaybackStopped reported with null media info."); - return; - } - - var video = e.Item as Video; - if (video != null && video.IsThemeMedia) - { - return; - } - - var type = GetPlaybackStoppedNotificationType(item.MediaType); - - SendPlaybackNotification(type, e); - } - - private async void SendPlaybackNotification(string type, PlaybackProgressEventArgs e) - { - var user = e.Users.FirstOrDefault(); - - if (user != null && !GetOptions().IsEnabledToMonitorUser(type, user.Id.ToString("N"))) - { - return; - } - - var item = e.MediaInfo; - - if (e.Item != null && e.Item.IsThemeMedia) - { - // Don't report theme song or local trailer playback - return; - } - - var notification = new NotificationRequest - { - NotificationType = type - }; - - if (e.Item != null) - { - notification.Variables["ItemName"] = GetItemName(e.Item); - } - else - { - notification.Variables["ItemName"] = item.Name; - } - - notification.Variables["UserName"] = user == null ? "Unknown user" : user.Name; - notification.Variables["AppName"] = e.ClientName; - notification.Variables["DeviceName"] = e.DeviceName; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - private string GetPlaybackNotificationType(string mediaType) - { - if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.AudioPlayback.ToString(); - } - if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.GamePlayback.ToString(); - } - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.VideoPlayback.ToString(); - } - - return null; - } - - private string GetPlaybackStoppedNotificationType(string mediaType) - { - if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.AudioPlaybackStopped.ToString(); - } - if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.GamePlaybackStopped.ToString(); - } - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.VideoPlaybackStopped.ToString(); - } - - return null; - } - - private readonly List _itemsAdded = new List(); - void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (!FilterItem(e.Item)) - { - return; - } - - lock (_libraryChangedSyncLock) - { - if (LibraryUpdateTimer == null) - { - LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000, - Timeout.Infinite); - } - else - { - LibraryUpdateTimer.Change(5000, Timeout.Infinite); - } - - _itemsAdded.Add(e.Item); - } - } - - private bool FilterItem(BaseItem item) - { - if (item.IsFolder) - { - return false; - } - - if (item.LocationType == LocationType.Virtual) - { - return false; - } - - if (item is IItemByName) - { - return false; - } - - return item.SourceType == SourceType.Library; - } - - private async void LibraryUpdateTimerCallback(object state) - { - List items; - - lock (_libraryChangedSyncLock) - { - items = _itemsAdded.ToList(); - _itemsAdded.Clear(); - DisposeLibraryUpdateTimer(); - } - - items = items.Take(10).ToList(); - - foreach (var item in items) - { - var notification = new NotificationRequest - { - NotificationType = NotificationType.NewLibraryContent.ToString() - }; - - notification.Variables["Name"] = GetItemName(item); - - await SendNotification(notification, item).ConfigureAwait(false); - } - } - - public static string GetItemName(BaseItem item) - { - var name = item.Name; - var episode = item as Episode; - if (episode != null) - { - if (episode.IndexNumber.HasValue) - { - name = string.Format("Ep{0} - {1}", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); - } - if (episode.ParentIndexNumber.HasValue) - { - name = string.Format("S{0}, {1}", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture), name); - } - } - - var hasSeries = item as IHasSeries; - - if (hasSeries != null) - { - name = hasSeries.SeriesName + " - " + name; - } - - var hasArtist = item as IHasArtist; - if (hasArtist != null) - { - var artists = hasArtist.AllArtists; - - if (artists.Length > 0) - { - name = hasArtist.AllArtists[0] + " - " + name; - } - } - - return name; - } - - public static string GetItemName(BaseItemDto item) - { - var name = item.Name; - - if (!string.IsNullOrWhiteSpace(item.SeriesName)) - { - name = item.SeriesName + " - " + name; - } - - if (item.Artists != null && item.Artists.Length > 0) - { - name = item.Artists[0] + " - " + name; - } - - return name; - } - - async void _userManager_UserCreated(object sender, GenericEventArgs e) - { - var notification = new NotificationRequest - { - UserIds = new List { e.Argument.Id.ToString("N") }, - Name = "Welcome to Emby!", - Description = "Check back here for more notifications." - }; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) - { - var result = e.Result; - - if (result.Status == TaskCompletionStatus.Failed) - { - var type = NotificationType.TaskFailed.ToString(); - - var notification = new NotificationRequest - { - Description = result.ErrorMessage, - Level = NotificationLevel.Error, - NotificationType = type - }; - - notification.Variables["Name"] = result.Name; - notification.Variables["ErrorMessage"] = result.ErrorMessage; - - await SendNotification(notification, null).ConfigureAwait(false); - } - } - - async void _installationManager_PluginUninstalled(object sender, GenericEventArgs e) - { - var type = NotificationType.PluginUninstalled.ToString(); - - var plugin = e.Argument; - - var notification = new NotificationRequest - { - NotificationType = type - }; - - notification.Variables["Name"] = plugin.Name; - notification.Variables["Version"] = plugin.Version.ToString(); - - await SendNotification(notification, null).ConfigureAwait(false); - } - - async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) - { - var installationInfo = e.InstallationInfo; - - var type = NotificationType.InstallationFailed.ToString(); - - var notification = new NotificationRequest - { - Level = NotificationLevel.Error, - Description = e.Exception.Message, - NotificationType = type - }; - - notification.Variables["Name"] = installationInfo.Name; - notification.Variables["Version"] = installationInfo.Version; - - await SendNotification(notification, null).ConfigureAwait(false); - } - - private async Task SendNotification(NotificationRequest notification, BaseItem relatedItem) - { - try - { - await _notificationManager.SendNotification(notification, relatedItem, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending notification", ex); - } - } - - public void Dispose() - { - DisposeLibraryUpdateTimer(); - - _installationManager.PluginInstalled -= _installationManager_PluginInstalled; - _installationManager.PluginUpdated -= _installationManager_PluginUpdated; - _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; - _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; - - _taskManager.TaskCompleted -= _taskManager_TaskCompleted; - - _userManager.UserCreated -= _userManager_UserCreated; - _libraryManager.ItemAdded -= _libraryManager_ItemAdded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - - _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; - _appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged; - _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; - - _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; - _userManager.UserLockedOut -= _userManager_UserLockedOut; - GC.SuppressFinalize(this); - } - - private void DisposeLibraryUpdateTimer() - { - if (LibraryUpdateTimer != null) - { - LibraryUpdateTimer.Dispose(); - LibraryUpdateTimer = null; - } - } - } -} diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs deleted file mode 100644 index f3a8a18eeb..0000000000 --- a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ /dev/null @@ -1,358 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Data; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Notifications; -using SQLitePCL.pretty; -using MediaBrowser.Model.Extensions; - -namespace Emby.Server.Implementations.Notifications -{ - public class SqliteNotificationsRepository : BaseSqliteRepository, INotificationsRepository - { - protected IFileSystem FileSystem { get; private set; } - - public SqliteNotificationsRepository(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem) : base(logger) - { - FileSystem = fileSystem; - DbFilePath = Path.Combine(appPaths.DataPath, "notifications.db"); - } - - public event EventHandler NotificationAdded; - public event EventHandler NotificationsMarkedRead; - ////public event EventHandler NotificationUpdated; - - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.ErrorException("Error loading database file. Will reset and retry.", ex); - - FileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - private void InitializeInternal() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - - "create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT NULL, Url TEXT NULL, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT NULL, PRIMARY KEY (Id, UserId))", - "create index if not exists idx_Notifications1 on Notifications(Id)", - "create index if not exists idx_Notifications2 on Notifications(UserId)" - }; - - connection.RunQueries(queries); - } - } - - /// - /// Gets the notifications. - /// - /// The query. - /// NotificationResult. - public NotificationResult GetNotifications(NotificationQuery query) - { - var result = new NotificationResult(); - - var clauses = new List(); - var paramList = new List(); - - if (query.IsRead.HasValue) - { - clauses.Add("IsRead=?"); - paramList.Add(query.IsRead.Value); - } - - clauses.Add("UserId=?"); - paramList.Add(query.UserId.ToGuidBlob()); - - var whereClause = " where " + string.Join(" And ", clauses.ToArray(clauses.Count)); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray(paramList.Count)).SelectScalarInt().First(); - - var commandText = string.Format("select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause); - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var resultList = new List(); - - foreach (var row in connection.Query(commandText, paramList.ToArray(paramList.Count))) - { - resultList.Add(GetNotification(row)); - } - - result.Notifications = resultList.ToArray(resultList.Count); - } - } - - return result; - } - - public NotificationsSummary GetNotificationsSummary(string userId) - { - var result = new NotificationsSummary(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select Level from Notifications where UserId=@UserId and IsRead=@IsRead")) - { - statement.TryBind("@IsRead", false); - statement.TryBind("@UserId", userId.ToGuidBlob()); - - var levels = new List(); - - foreach (var row in statement.ExecuteQuery()) - { - levels.Add(GetLevel(row, 0)); - } - - result.UnreadCount = levels.Count; - - if (levels.Count > 0) - { - result.MaxUnreadNotificationLevel = levels.Max(); - } - } - - return result; - } - } - } - - private Notification GetNotification(IReadOnlyList reader) - { - var notification = new Notification - { - Id = reader[0].ReadGuidFromBlob().ToString("N"), - UserId = reader[1].ReadGuidFromBlob().ToString("N"), - Date = reader[2].ReadDateTime(), - Name = reader[3].ToString() - }; - - if (reader[4].SQLiteType != SQLiteType.Null) - { - notification.Description = reader[4].ToString(); - } - - if (reader[5].SQLiteType != SQLiteType.Null) - { - notification.Url = reader[5].ToString(); - } - - notification.Level = GetLevel(reader, 6); - notification.IsRead = reader[7].ToBool(); - - return notification; - } - - /// - /// Gets the level. - /// - /// The reader. - /// The index. - /// NotificationLevel. - private NotificationLevel GetLevel(IReadOnlyList reader, int index) - { - NotificationLevel level; - - var val = reader[index].ToString(); - - Enum.TryParse(val, true, out level); - - return level; - } - - /// - /// Adds the notification. - /// - /// The notification. - /// The cancellation token. - /// Task. - public async Task AddNotification(Notification notification, CancellationToken cancellationToken) - { - await ReplaceNotification(notification, cancellationToken).ConfigureAwait(false); - - if (NotificationAdded != null) - { - try - { - NotificationAdded(this, new NotificationUpdateEventArgs - { - Notification = notification - }); - } - catch (Exception ex) - { - Logger.ErrorException("Error in NotificationAdded event handler", ex); - } - } - } - - /// - /// Replaces the notification. - /// - /// The notification. - /// The cancellation token. - /// Task. - private async Task ReplaceNotification(Notification notification, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(notification.Id)) - { - notification.Id = Guid.NewGuid().ToString("N"); - } - if (string.IsNullOrEmpty(notification.UserId)) - { - throw new ArgumentException("The notification must have a user id"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - lock (WriteLock) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(conn => - { - using (var statement = conn.PrepareStatement("replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (@Id, @UserId, @Date, @Name, @Description, @Url, @Level, @IsRead, @Category, @RelatedId)")) - { - statement.TryBind("@Id", notification.Id.ToGuidBlob()); - statement.TryBind("@UserId", notification.UserId.ToGuidBlob()); - statement.TryBind("@Date", notification.Date.ToDateTimeParamValue()); - statement.TryBind("@Name", notification.Name); - statement.TryBind("@Description", notification.Description); - statement.TryBind("@Url", notification.Url); - statement.TryBind("@Level", notification.Level.ToString()); - statement.TryBind("@IsRead", notification.IsRead); - statement.TryBind("@Category", string.Empty); - statement.TryBind("@RelatedId", string.Empty); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - /// - /// Marks the read. - /// - /// The notification id list. - /// The user id. - /// if set to true [is read]. - /// The cancellation token. - /// Task. - public async Task MarkRead(IEnumerable notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) - { - var list = notificationIdList.ToList(); - var idArray = list.Select(i => new Guid(i)).ToArray(list.Count); - - await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false); - - if (NotificationsMarkedRead != null) - { - try - { - NotificationsMarkedRead(this, new NotificationReadEventArgs - { - IdList = list.ToArray(list.Count), - IsRead = isRead, - UserId = userId - }); - } - catch (Exception ex) - { - Logger.ErrorException("Error in NotificationsMarkedRead event handler", ex); - } - } - } - - public async Task MarkAllRead(string userId, bool isRead, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(conn => - { - using (var statement = conn.PrepareStatement("update Notifications set IsRead=@IsRead where UserId=@UserId")) - { - statement.TryBind("@IsRead", isRead); - statement.TryBind("@UserId", userId.ToGuidBlob()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - private async Task MarkReadInternal(IEnumerable notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(conn => - { - using (var statement = conn.PrepareStatement("update Notifications set IsRead=@IsRead where UserId=@UserId and Id=@Id")) - { - statement.TryBind("@IsRead", isRead); - statement.TryBind("@UserId", userId.ToGuidBlob()); - - foreach (var id in notificationIdList) - { - statement.Reset(); - - statement.TryBind("@Id", id.ToGuidBlob()); - - statement.MoveNext(); - } - } - - }, TransactionMode); - } - } - } - } -} diff --git a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs deleted file mode 100644 index 6e57e7f816..0000000000 --- a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Controller.Plugins; -using System.Linq; -using MediaBrowser.Model.Extensions; - -namespace Emby.Server.Implementations.Notifications -{ - /// - /// Notifies clients anytime a notification is added or udpated - /// - public class WebSocketNotifier : IServerEntryPoint - { - private readonly INotificationsRepository _notificationsRepo; - - private readonly IServerManager _serverManager; - - public WebSocketNotifier(INotificationsRepository notificationsRepo, IServerManager serverManager) - { - _notificationsRepo = notificationsRepo; - _serverManager = serverManager; - } - - public void Run() - { - _notificationsRepo.NotificationAdded += _notificationsRepo_NotificationAdded; - _notificationsRepo.NotificationsMarkedRead += _notificationsRepo_NotificationsMarkedRead; - } - - void _notificationsRepo_NotificationsMarkedRead(object sender, NotificationReadEventArgs e) - { - var list = e.IdList.ToList(); - - list.Add(e.UserId); - list.Add(e.IsRead.ToString().ToLower()); - - var msg = string.Join("|", list.ToArray(list.Count)); - - _serverManager.SendWebSocketMessage("NotificationsMarkedRead", msg); - } - - void _notificationsRepo_NotificationAdded(object sender, NotificationUpdateEventArgs e) - { - var msg = e.Notification.UserId + "|" + e.Notification.Id; - - _serverManager.SendWebSocketMessage("NotificationAdded", msg); - } - - public void Dispose() - { - _notificationsRepo.NotificationAdded -= _notificationsRepo_NotificationAdded; - GC.SuppressFinalize(this); - } - } -} diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 2a178895cc..908fa65de1 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -50,8 +50,16 @@ namespace Emby.Server.Implementations.Playlists protected override QueryResult GetItemsInternal(InternalItemsQuery query) { - query.Recursive = false; - return base.GetItemsInternal(query); + if (query.User == null) + { + query.Recursive = false; + return base.GetItemsInternal(query); + } + + query.Recursive = true; + query.IncludeItemTypes = new string[] { "Playlist" }; + query.Parent = null; + return LibraryManager.GetItemsResult(query); } } } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index 36e8b4dd89..e69b4a34e3 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -28,11 +28,11 @@ namespace Emby.Server.Implementations.Playlists { } - protected override List GetItemsWithImages(IHasMetadata item) + protected override List GetItemsWithImages(BaseItem item) { var playlist = (Playlist)item; - var items = playlist.GetManageableItems() + return playlist.GetManageableItems() .Select(i => { var subItem = i.Item2; @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Playlists return subItem; } - var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent(); + var parent = subItem.GetOwner() ?? subItem.GetParent(); if (parent != null && parent.HasImage(ImageType.Primary)) { @@ -66,9 +66,9 @@ namespace Emby.Server.Implementations.Playlists return null; }) .Where(i => i != null) - .DistinctBy(i => i.Id); - - return GetFinalItems(items); + .OrderBy(i => Guid.NewGuid()) + .DistinctBy(i => i.Id) + .ToList(); } } @@ -81,27 +81,20 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override List GetItemsWithImages(IHasMetadata item) + protected override List GetItemsWithImages(BaseItem item) { - var items = _libraryManager.GetItemList(new InternalItemsQuery + return _libraryManager.GetItemList(new InternalItemsQuery { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name }, - OrderBy = new[] { new Tuple(ItemSortBy.Random, SortOrder.Ascending) }, + OrderBy = new[] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, DtoOptions = new DtoOptions(false) }); - - return GetFinalItems(items); } - - //protected override Task CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) - //{ - // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); - //} } public class GenreImageProvider : BaseDynamicImageProvider @@ -113,21 +106,18 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override List GetItemsWithImages(IHasMetadata item) + protected override List GetItemsWithImages(BaseItem item) { - var items = _libraryManager.GetItemList(new InternalItemsQuery + return _libraryManager.GetItemList(new InternalItemsQuery { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name }, - OrderBy = new[] { new Tuple(ItemSortBy.Random, SortOrder.Ascending) }, + OrderBy = new[] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, DtoOptions = new DtoOptions(false) - }); - - return GetFinalItems(items); } //protected override Task CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index f268e9c0c1..e0a6f56ecf 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -15,6 +15,10 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Extensions; +using PlaylistsNET; +using PlaylistsNET.Content; +using PlaylistsNET.Models; +using PlaylistsNET.Utils; namespace Emby.Server.Implementations.Playlists { @@ -37,7 +41,7 @@ namespace Emby.Server.Implementations.Playlists _providerManager = providerManager; } - public IEnumerable GetPlaylists(string userId) + public IEnumerable GetPlaylists(Guid userId) { var user = _userManager.GetUserById(userId); @@ -50,14 +54,14 @@ namespace Emby.Server.Implementations.Playlists var folderName = _fileSystem.GetValidFilename(name) + " [playlist]"; - var parentFolder = GetPlaylistsFolder(null); + var parentFolder = GetPlaylistsFolder(Guid.Empty); if (parentFolder == null) { throw new ArgumentException(); } - if (string.IsNullOrWhiteSpace(options.MediaType)) + if (string.IsNullOrEmpty(options.MediaType)) { foreach (var itemId in options.ItemIdList) { @@ -68,7 +72,7 @@ namespace Emby.Server.Implementations.Playlists throw new ArgumentException("No item exists with the supplied Id"); } - if (!string.IsNullOrWhiteSpace(item.MediaType)) + if (!string.IsNullOrEmpty(item.MediaType)) { options.MediaType = item.MediaType; } @@ -87,18 +91,18 @@ namespace Emby.Server.Implementations.Playlists { options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } } - if (!string.IsNullOrWhiteSpace(options.MediaType)) + if (!string.IsNullOrEmpty(options.MediaType)) { break; } } } - if (string.IsNullOrWhiteSpace(options.MediaType)) + if (string.IsNullOrEmpty(options.MediaType)) { options.MediaType = "Audio"; } @@ -117,15 +121,17 @@ namespace Emby.Server.Implementations.Playlists var playlist = new Playlist { Name = name, - Path = path + Path = path, + Shares = new[] + { + new Share + { + UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N"), + CanEdit = true + } + } }; - playlist.Shares.Add(new Share - { - UserId = options.UserId, - CanEdit = true - }); - playlist.SetMediaType(options.MediaType); parentFolder.AddChild(playlist, CancellationToken.None); @@ -135,7 +141,7 @@ namespace Emby.Server.Implementations.Playlists if (options.ItemIdList.Length > 0) { - await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false) + AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false) { EnableImages = true }); @@ -163,24 +169,24 @@ namespace Emby.Server.Implementations.Playlists return path; } - private List GetPlaylistItems(IEnumerable itemIds, string playlistMediaType, User user, DtoOptions options) + private List GetPlaylistItems(IEnumerable itemIds, string playlistMediaType, User user, DtoOptions options) { var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null); return Playlist.GetPlaylistItems(playlistMediaType, items, user, options); } - public Task AddToPlaylist(string playlistId, IEnumerable itemIds, string userId) + public void AddToPlaylist(string playlistId, IEnumerable itemIds, Guid userId) { - var user = string.IsNullOrWhiteSpace(userId) ? null : _userManager.GetUserById(userId); + var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId); - return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false) + AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false) { EnableImages = true }); } - private async Task AddToPlaylistInternal(string playlistId, IEnumerable itemIds, User user, DtoOptions options) + private void AddToPlaylistInternal(string playlistId, IEnumerable itemIds, User user, DtoOptions options) { var playlist = _libraryManager.GetItemById(playlistId) as Playlist; @@ -197,11 +203,6 @@ namespace Emby.Server.Implementations.Playlists foreach (var item in items) { - if (string.IsNullOrWhiteSpace(item.Path)) - { - continue; - } - list.Add(LinkedChild.Create(item)); } @@ -211,6 +212,11 @@ namespace Emby.Server.Implementations.Playlists playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); + if (playlist.IsFile) + { + SavePlaylistFile(playlist); + } + _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(_fileSystem) { ForceSave = true @@ -218,7 +224,7 @@ namespace Emby.Server.Implementations.Playlists }, RefreshPriority.High); } - public async Task RemoveFromPlaylist(string playlistId, IEnumerable entryIds) + public void RemoveFromPlaylist(string playlistId, IEnumerable entryIds) { var playlist = _libraryManager.GetItemById(playlistId) as Playlist; @@ -239,6 +245,11 @@ namespace Emby.Server.Implementations.Playlists playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); + if (playlist.IsFile) + { + SavePlaylistFile(playlist); + } + _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(_fileSystem) { ForceSave = true @@ -246,7 +257,7 @@ namespace Emby.Server.Implementations.Playlists }, RefreshPriority.High); } - public async Task MoveItem(string playlistId, string entryId, int newIndex) + public void MoveItem(string playlistId, string entryId, int newIndex) { var playlist = _libraryManager.GetItemById(playlistId) as Playlist; @@ -282,9 +293,207 @@ namespace Emby.Server.Implementations.Playlists playlist.LinkedChildren = newList.ToArray(newList.Count); playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); + + if (playlist.IsFile) + { + SavePlaylistFile(playlist); + } } - public Folder GetPlaylistsFolder(string userId) + private void SavePlaylistFile(Playlist item) + { + // This is probably best done as a metatata provider, but saving a file over itself will first require some core work to prevent this from happening when not needed + var playlistPath = item.Path; + var extension = Path.GetExtension(playlistPath); + + if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase)) + { + var playlist = new WplPlaylist(); + foreach (var child in item.GetLinkedChildren()) + { + var entry = new WplPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + TrackTitle = child.Name, + AlbumTitle = child.Album + }; + + var hasAlbumArtist = child as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + } + + var hasArtist = child as IHasArtist; + if (hasArtist != null) + { + entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + playlist.PlaylistEntries.Add(entry); + } + + _fileSystem.WriteAllText(playlistPath, new WplContent().ToText(playlist)); + } + if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase)) + { + var playlist = new ZplPlaylist(); + foreach (var child in item.GetLinkedChildren()) + { + var entry = new ZplPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + TrackTitle = child.Name, + AlbumTitle = child.Album + }; + + var hasAlbumArtist = child as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + } + + var hasArtist = child as IHasArtist; + if (hasArtist != null) + { + entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + playlist.PlaylistEntries.Add(entry); + } + + _fileSystem.WriteAllText(playlistPath, new ZplContent().ToText(playlist)); + } + if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) + { + var playlist = new M3uPlaylist(); + playlist.IsExtended = true; + foreach (var child in item.GetLinkedChildren()) + { + var entry = new M3uPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + Title = child.Name, + Album = child.Album + }; + + var hasAlbumArtist = child as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + playlist.PlaylistEntries.Add(entry); + } + + _fileSystem.WriteAllText(playlistPath, new M3uContent().ToText(playlist)); + } + if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase)) + { + var playlist = new M3uPlaylist(); + playlist.IsExtended = true; + foreach (var child in item.GetLinkedChildren()) + { + var entry = new M3uPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + Title = child.Name, + Album = child.Album + }; + + var hasAlbumArtist = child as IHasAlbumArtist; + if (hasAlbumArtist != null) + { + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + } + + if (child.RunTimeTicks.HasValue) + { + entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + playlist.PlaylistEntries.Add(entry); + } + + _fileSystem.WriteAllText(playlistPath, new M3u8Content().ToText(playlist)); + } + if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase)) + { + var playlist = new PlsPlaylist(); + foreach (var child in item.GetLinkedChildren()) + { + var entry = new PlsPlaylistEntry() + { + Path = NormalizeItemPath(playlistPath, child.Path), + Title = child.Name + }; + + if (child.RunTimeTicks.HasValue) + { + entry.Length = TimeSpan.FromTicks(child.RunTimeTicks.Value); + } + playlist.PlaylistEntries.Add(entry); + } + + _fileSystem.WriteAllText(playlistPath, new PlsContent().ToText(playlist)); + } + } + + private string NormalizeItemPath(string playlistPath, string itemPath) + { + return MakeRelativePath(_fileSystem.GetDirectoryName(playlistPath), itemPath); + } + + private static String MakeRelativePath(string folderPath, string fileAbsolutePath) + { + if (String.IsNullOrEmpty(folderPath)) throw new ArgumentNullException("folderPath"); + if (String.IsNullOrEmpty(fileAbsolutePath)) throw new ArgumentNullException("filePath"); + + if (!folderPath.EndsWith(Path.DirectorySeparatorChar.ToString())) + { + folderPath = folderPath + Path.DirectorySeparatorChar; + } + + Uri folderUri = new Uri(folderPath); + Uri fileAbsoluteUri = new Uri(fileAbsolutePath); + + if (folderUri.Scheme != fileAbsoluteUri.Scheme) { return fileAbsolutePath; } // path can't be made relative. + + Uri relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri); + String relativePath = Uri.UnescapeDataString(relativeUri.ToString()); + + if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.CurrentCultureIgnoreCase)) + { + relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + + return relativePath; + } + + private static string UnEscape(string content) + { + if (content == null) return content; + return content.Replace("&", "&").Replace("'", "'").Replace(""", "\"").Replace(">", ">").Replace("<", "<"); + } + + private static string Escape(string content) + { + if (content == null) return null; + return content.Replace("&", "&").Replace("'", "'").Replace("\"", """).Replace(">", ">").Replace("<", "<"); + } + + public Folder GetPlaylistsFolder(Guid userId) { var typeName = "PlaylistsFolder"; diff --git a/Emby.Server.Implementations/Playlists/PlaylistsDynamicFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsDynamicFolder.cs deleted file mode 100644 index 2ce835576e..0000000000 --- a/Emby.Server.Implementations/Playlists/PlaylistsDynamicFolder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.IO; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Playlists -{ - public class PlaylistsDynamicFolder : IVirtualFolderCreator - { - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - public PlaylistsDynamicFolder(IApplicationPaths appPaths, IFileSystem fileSystem) - { - _appPaths = appPaths; - _fileSystem = fileSystem; - } - - public BasePluginFolder GetFolder() - { - var path = Path.Combine(_appPaths.DataPath, "playlists"); - - _fileSystem.CreateDirectory(path); - - return new PlaylistsFolder - { - Path = path - }; - } - } -} diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs new file mode 100644 index 0000000000..ce29b52f6f --- /dev/null +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -0,0 +1,74 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Reflection; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations +{ + public class ResourceFileManager : IResourceFileManager + { + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly IHttpResultFactory _resultFactory; + + public ResourceFileManager(IHttpResultFactory resultFactory, ILogger logger, IFileSystem fileSystem) + { + _resultFactory = resultFactory; + _logger = logger; + _fileSystem = fileSystem; + } + + public Stream GetResourceFileStream(string basePath, string virtualPath) + { + return _fileSystem.GetFileStream(GetResourcePath(basePath, virtualPath), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true); + } + + public Task GetStaticFileResult(IRequest request, string basePath, string virtualPath, string contentType, TimeSpan? cacheDuration) + { + return _resultFactory.GetStaticFileResult(request, GetResourcePath(basePath, virtualPath)); + } + + public string ReadAllText(string basePath, string virtualPath) + { + return _fileSystem.ReadAllText(GetResourcePath(basePath, virtualPath)); + } + + private string GetResourcePath(string basePath, string virtualPath) + { + var fullPath = Path.Combine(basePath, virtualPath.Replace('/', _fileSystem.DirectorySeparatorChar)); + + try + { + fullPath = _fileSystem.GetFullPath(fullPath); + } + catch (Exception ex) + { + _logger.ErrorException("Error in Path.GetFullPath", ex); + } + + // Don't allow file system access outside of the source folder + if (!_fileSystem.ContainsSubPath(basePath, fullPath)) + { + throw new SecurityException("Access denied"); + } + + return fullPath; + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index fe0652f668..7a5efded3f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { Type = TaskTriggerInfo.TriggerDaily, TimeOfDayTicks = TimeSpan.FromHours(2).Ticks, - MaxRuntimeMs = Convert.ToInt32(TimeSpan.FromHours(4).TotalMilliseconds) + MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks } }; } @@ -133,9 +133,9 @@ namespace Emby.Server.Implementations.ScheduledTasks try { - var chapters = _itemRepo.GetChapters(video.Id); + var chapters = _itemRepo.GetChapters(video); - var success = await _encodingManager.RefreshChapterImages(video, directoryService, chapters, extract, true, CancellationToken.None); + var success = await _encodingManager.RefreshChapterImages(video, directoryService, chapters, extract, true, cancellationToken).ConfigureAwait(false); if (!success) { diff --git a/Emby.Server.Implementations/ScheduledTasks/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/DailyTrigger.cs index 1ba5d4329c..a2779c7cdb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/DailyTrigger.cs @@ -18,20 +18,17 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The time of day. public TimeSpan TimeOfDay { get; set; } + /// + /// Gets or sets the options of this task. + /// + public TaskOptions TaskOptions { get; set; } + /// /// Gets or sets the timer. /// /// The timer. private Timer Timer { get; set; } - /// - /// Gets the execution properties of this task. - /// - /// - /// The execution properties of this task. - /// - public TaskExecutionOptions TaskOptions { get; set; } - /// /// Stars waiting for the trigger action /// @@ -75,7 +72,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Occurs when [triggered]. /// - public event EventHandler> Triggered; + public event EventHandler Triggered; /// /// Called when [triggered]. @@ -84,7 +81,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (Triggered != null) { - Triggered(this, new GenericEventArgs(TaskOptions)); + Triggered(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/IntervalTrigger.cs index d09765e34d..dcccb252a3 100644 --- a/Emby.Server.Implementations/ScheduledTasks/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/IntervalTrigger.cs @@ -18,20 +18,17 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The interval. public TimeSpan Interval { get; set; } + /// + /// Gets or sets the options of this task. + /// + public TaskOptions TaskOptions { get; set; } + /// /// Gets or sets the timer. /// /// The timer. private Timer Timer { get; set; } - /// - /// Gets the execution properties of this task. - /// - /// - /// The execution properties of this task. - /// - public TaskExecutionOptions TaskOptions { get; set; } - private DateTime _lastStartDate; /// @@ -93,7 +90,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Occurs when [triggered]. /// - public event EventHandler> Triggered; + public event EventHandler Triggered; /// /// Called when [triggered]. @@ -105,7 +102,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (Triggered != null) { _lastStartDate = DateTime.UtcNow; - Triggered(this, new GenericEventArgs(TaskOptions)); + Triggered(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs index 9f887ba030..6911126386 100644 --- a/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Plugin Update Task /// - public class PluginUpdateTask : IScheduledTask + public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask { /// /// The _logger @@ -71,14 +71,13 @@ namespace Emby.Server.Implementations.ScheduledTasks var numComplete = 0; - // Create tasks for each one - var tasks = packagesToInstall.Select(i => Task.Run(async () => + foreach (var package in packagesToInstall) { cancellationToken.ThrowIfCancellationRequested(); try { - await _installationManager.InstallPackage(i, true, new SimpleProgress(), cancellationToken).ConfigureAwait(false); + await _installationManager.InstallPackage(package, true, new SimpleProgress(), cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -90,11 +89,11 @@ namespace Emby.Server.Implementations.ScheduledTasks } catch (HttpException ex) { - _logger.ErrorException("Error downloading {0}", ex, i.name); + _logger.ErrorException("Error downloading {0}", ex, package.name); } catch (IOException ex) { - _logger.ErrorException("Error updating {0}", ex, i.name); + _logger.ErrorException("Error updating {0}", ex, package.name); } // Update progress @@ -106,11 +105,7 @@ namespace Emby.Server.Implementations.ScheduledTasks progress.Report(90 * percent + 10); } - })); - - cancellationToken.ThrowIfCancellationRequested(); - - await Task.WhenAll(tasks).ConfigureAwait(false); + } progress.Report(100); } @@ -137,5 +132,11 @@ namespace Emby.Server.Implementations.ScheduledTasks { get { return "Application"; } } + + public bool IsHidden => true; + + public bool IsEnabled => true; + + public bool IsLogged => true; } } \ No newline at end of file diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index bdc29c16b7..f6397b6700 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -160,7 +160,7 @@ namespace Emby.Server.Implementations.ScheduledTasks _lastExecutionResult = value; var path = GetHistoryFilePath(); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); lock (_lastExecutionResultSyncLock) { @@ -236,7 +236,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The _triggers /// - private Tuple[] _triggers; + private Tuple[] _triggers; /// /// Gets the triggers that define when the task will run /// @@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The source of the event. /// The instance containing the event data. - async void trigger_Triggered(object sender, GenericEventArgs e) + async void trigger_Triggered(object sender, EventArgs e) { var trigger = (ITaskTrigger)sender; @@ -365,7 +365,7 @@ namespace Emby.Server.Implementations.ScheduledTasks trigger.Stop(); - TaskManager.QueueScheduledTask(ScheduledTask, e.Argument); + TaskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions); await Task.Delay(1000).ConfigureAwait(false); @@ -380,7 +380,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Task options. /// Task. /// Cannot execute a Task that is already running - public async Task Execute(TaskExecutionOptions options) + public async Task Execute(TaskOptions options) { var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false)); @@ -397,7 +397,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } } - private async Task ExecuteInternal(TaskExecutionOptions options) + private async Task ExecuteInternal(TaskOptions options) { // Cancel the current execution, if any if (CurrentCancellationTokenSource != null) @@ -422,14 +422,12 @@ namespace Emby.Server.Implementations.ScheduledTasks try { - if (options != null && options.MaxRuntimeMs.HasValue) + if (options != null && options.MaxRuntimeTicks.HasValue) { - CurrentCancellationTokenSource.CancelAfter(options.MaxRuntimeMs.Value); + CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value)); } - var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress); - - await localTask.ConfigureAwait(false); + await ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress).ConfigureAwait(false); status = TaskCompletionStatus.Completed; } @@ -563,13 +561,35 @@ namespace Emby.Server.Implementations.ScheduledTasks catch (FileNotFoundException) { // File doesn't exist. No biggie. Return defaults. - return ScheduledTask.GetDefaultTriggers().ToArray(); } catch (DirectoryNotFoundException) { // File doesn't exist. No biggie. Return defaults. } - return ScheduledTask.GetDefaultTriggers().ToArray(); + catch + { + + } + return GetDefaultTriggers(); + } + + private TaskTriggerInfo[] GetDefaultTriggers() + { + try + { + return ScheduledTask.GetDefaultTriggers().ToArray(); + } + catch + { + return new TaskTriggerInfo[] + { + new TaskTriggerInfo + { + IntervalTicks = TimeSpan.FromDays(1).Ticks, + Type = TaskTriggerInfo.TriggerInterval + } + }; + } } /// @@ -580,7 +600,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var path = GetConfigurationFilePath(); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); JsonSerializer.SerializeToFile(triggers, path); } @@ -625,7 +645,6 @@ namespace Emby.Server.Implementations.ScheduledTasks public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// @@ -705,9 +724,9 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Invalid trigger type: + info.Type private ITaskTrigger GetTrigger(TaskTriggerInfo info) { - var options = new TaskExecutionOptions + var options = new TaskOptions { - MaxRuntimeMs = info.MaxRuntimeMs + MaxRuntimeTicks = info.MaxRuntimeTicks }; if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/ScheduledTasks/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/StartupTrigger.cs index d708c905dc..20a4304cc8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/StartupTrigger.cs @@ -14,12 +14,9 @@ namespace Emby.Server.Implementations.ScheduledTasks public int DelayMs { get; set; } /// - /// Gets the execution properties of this task. + /// Gets or sets the options of this task. /// - /// - /// The execution properties of this task. - /// - public TaskExecutionOptions TaskOptions { get; set; } + public TaskOptions TaskOptions { get; set; } public StartupTrigger() { @@ -51,7 +48,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Occurs when [triggered]. /// - public event EventHandler> Triggered; + public event EventHandler Triggered; /// /// Called when [triggered]. @@ -60,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (Triggered != null) { - Triggered(this, new GenericEventArgs(TaskOptions)); + Triggered(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/SystemEventTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/SystemEventTrigger.cs index 976754a40e..c4623bf5b3 100644 --- a/Emby.Server.Implementations/ScheduledTasks/SystemEventTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/SystemEventTrigger.cs @@ -19,12 +19,9 @@ namespace Emby.Server.Implementations.ScheduledTasks public SystemEvent SystemEvent { get; set; } /// - /// Gets the execution properties of this task. + /// Gets or sets the options of this task. /// - /// - /// The execution properties of this task. - /// - public TaskExecutionOptions TaskOptions { get; set; } + public TaskOptions TaskOptions { get; set; } private readonly ISystemEvents _systemEvents; @@ -70,7 +67,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Occurs when [triggered]. /// - public event EventHandler> Triggered; + public event EventHandler Triggered; /// /// Called when [triggered]. @@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (Triggered != null) { - Triggered(this, new GenericEventArgs(TaskOptions)); + Triggered(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index c0fcb459d1..8963693ab5 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -32,8 +32,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The _task queue /// - private readonly ConcurrentQueue> _taskQueue = - new ConcurrentQueue>(); + private readonly ConcurrentQueue> _taskQueue = + new ConcurrentQueue>(); /// /// Gets or sets the json serializer. @@ -114,6 +114,10 @@ namespace Emby.Server.Implementations.ScheduledTasks { var path = Path.Combine(ApplicationPaths.CachePath, "startuptasks.txt"); + // ToDo: Fix this shit + if (!File.Exists(path)) + return; + List lines; try @@ -126,7 +130,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (task != null) { - QueueScheduledTask(task, new TaskExecutionOptions()); + QueueScheduledTask(task, new TaskOptions()); } } @@ -143,7 +147,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// /// Task options. - public void CancelIfRunningAndQueue(TaskExecutionOptions options) + public void CancelIfRunningAndQueue(TaskOptions options) where T : IScheduledTask { var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); @@ -155,7 +159,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public void CancelIfRunningAndQueue() where T : IScheduledTask { - CancelIfRunningAndQueue(new TaskExecutionOptions()); + CancelIfRunningAndQueue(new TaskOptions()); } /// @@ -174,7 +178,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// /// Task options - public void QueueScheduledTask(TaskExecutionOptions options) + public void QueueScheduledTask(TaskOptions options) where T : IScheduledTask { var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T)); @@ -192,7 +196,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public void QueueScheduledTask() where T : IScheduledTask { - QueueScheduledTask(new TaskExecutionOptions()); + QueueScheduledTask(new TaskOptions()); } public void QueueIfNotRunning() @@ -202,7 +206,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (task.State != TaskState.Running) { - QueueScheduledTask(new TaskExecutionOptions()); + QueueScheduledTask(new TaskOptions()); } } @@ -225,7 +229,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (scheduledTask.State == TaskState.Idle) { - Execute(scheduledTask, new TaskExecutionOptions()); + Execute(scheduledTask, new TaskOptions()); } } } @@ -236,7 +240,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The task. /// The task options. - public void QueueScheduledTask(IScheduledTask task, TaskExecutionOptions options) + public void QueueScheduledTask(IScheduledTask task, TaskOptions options) { var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == task.GetType()); @@ -255,7 +259,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The task. /// The task options. - private void QueueScheduledTask(IScheduledTaskWorker task, TaskExecutionOptions options) + private void QueueScheduledTask(IScheduledTaskWorker task, TaskOptions options) { var type = task.ScheduledTask.GetType(); @@ -269,7 +273,7 @@ namespace Emby.Server.Implementations.ScheduledTasks return; } - _taskQueue.Enqueue(new Tuple(type, options)); + _taskQueue.Enqueue(new Tuple(type, options)); } } @@ -297,7 +301,6 @@ namespace Emby.Server.Implementations.ScheduledTasks public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// @@ -317,7 +320,7 @@ namespace Emby.Server.Implementations.ScheduledTasks ((ScheduledTaskWorker)task).Cancel(); } - public Task Execute(IScheduledTaskWorker task, TaskExecutionOptions options) + public Task Execute(IScheduledTaskWorker task, TaskOptions options) { return ((ScheduledTaskWorker)task).Execute(options); } @@ -362,9 +365,9 @@ namespace Emby.Server.Implementations.ScheduledTasks // Execute queued tasks lock (_taskQueue) { - var list = new List>(); + var list = new List>(); - Tuple item; + Tuple item; while (_taskQueue.TryDequeue(out item)) { if (list.All(i => i.Item1 != item.Item1)) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 701358fd45..05fb08447a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks // No biggie here. Nothing to delete } - return Task.FromResult(true); + return Task.CompletedTask; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index f98b096596..d5a7ccadb6 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks progress.Report(100); - return Task.FromResult(true); + return Task.CompletedTask; } public string Key diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs index 032fa05a0e..fbc3a309ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs @@ -58,11 +58,9 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks progress.Report(0); - LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging + return LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug - : LogSeverity.Info); - - return Task.FromResult(true); + : LogSeverity.Info, cancellationToken); } /// @@ -71,7 +69,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// The name. public string Name { - get { return "Start new log file"; } + get { return "Rotate log file"; } } public string Key { get; } @@ -96,7 +94,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public bool IsHidden { - get { return true; } + get { return false; } } public bool IsEnabled diff --git a/Emby.Server.Implementations/ScheduledTasks/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/WeeklyTrigger.cs index 1a944ebf2c..82b4499172 100644 --- a/Emby.Server.Implementations/ScheduledTasks/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/WeeklyTrigger.cs @@ -24,12 +24,9 @@ namespace Emby.Server.Implementations.ScheduledTasks public DayOfWeek DayOfWeek { get; set; } /// - /// Gets the execution properties of this task. + /// Gets or sets the options of this task. /// - /// - /// The execution properties of this task. - /// - public TaskExecutionOptions TaskOptions { get; set; } + public TaskOptions TaskOptions { get; set; } /// /// Gets or sets the timer. @@ -100,7 +97,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Occurs when [triggered]. /// - public event EventHandler> Triggered; + public event EventHandler Triggered; /// /// Called when [triggered]. @@ -109,7 +106,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (Triggered != null) { - Triggered(this, new GenericEventArgs(TaskOptions)); + Triggered(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index b1877d776b..45f7f1e958 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -11,19 +11,21 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using SQLitePCL.pretty; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Devices; namespace Emby.Server.Implementations.Security { public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository { - private readonly IServerApplicationPaths _appPaths; + private readonly IServerConfigurationManager _config; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths) + public AuthenticationRepository(ILogger logger, IServerConfigurationManager config) : base(logger) { - _appPaths = appPaths; - DbFilePath = Path.Combine(appPaths.DataPath, "authentication.db"); + _config = config; + DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); } public void Initialize() @@ -32,67 +34,85 @@ namespace Emby.Server.Implementations.Security { RunDefaultInitialization(connection); + var tableNewlyCreated = !TableExists(connection, "Tokens"); + string[] queries = { - "create table if not exists AccessTokens (Id GUID PRIMARY KEY NOT NULL, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", - "create index if not exists idx_AccessTokens on AccessTokens(Id)" + "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)", + "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)", + + "drop index if exists idx_AccessTokens", + "drop index if exists Tokens1", + "drop index if exists Tokens2", + "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)", + "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)", + "create index if not exists Devices1 on Devices (Id)" }; connection.RunQueries(queries); - connection.RunInTransaction(db => - { - var existingColumnNames = GetColumnNames(db, "AccessTokens"); - - AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames); - - }, TransactionMode); + TryMigrate(connection, tableNewlyCreated); } } - public void Create(AuthenticationInfo info, CancellationToken cancellationToken) + private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated) { - info.Id = Guid.NewGuid().ToString("N"); + try + { + if (tableNewlyCreated && TableExists(connection, "AccessTokens")) + { + connection.RunInTransaction(db => + { + var existingColumnNames = GetColumnNames(db, "AccessTokens"); - Update(info, cancellationToken); + AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames); + AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames); + AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames); + + }, TransactionMode); + + connection.RunQueries(new[] + { + "update accesstokens set DateLastActivity=DateCreated where DateLastActivity is null", + "update accesstokens set DeviceName='Unknown' where DeviceName is null", + "update accesstokens set AppName='Unknown' where AppName is null", + "update accesstokens set AppVersion='1' where AppVersion is null", + "INSERT INTO Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) SELECT AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity FROM AccessTokens where deviceid not null and devicename not null and appname not null and isactive=1" + }); + } + } + catch (Exception ex) + { + Logger.ErrorException("Error migrating authentication database", ex); + } } - public void Update(AuthenticationInfo info, CancellationToken cancellationToken) + public void Create(AuthenticationInfo info) { if (info == null) { throw new ArgumentNullException("info"); } - cancellationToken.ThrowIfCancellationRequested(); - using (WriteLock.Write()) { using (var connection = CreateConnection()) { connection.RunInTransaction(db => { - using (var statement = db.PrepareStatement("replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)")) + using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) { - statement.TryBind("@Id", info.Id.ToGuidBlob()); statement.TryBind("@AccessToken", info.AccessToken); statement.TryBind("@DeviceId", info.DeviceId); statement.TryBind("@AppName", info.AppName); statement.TryBind("@AppVersion", info.AppVersion); statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", info.UserId); - statement.TryBind("@IsActive", info.IsActive); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserName", info.UserName); + statement.TryBind("@IsActive", true); statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - - if (info.DateRevoked.HasValue) - { - statement.TryBind("@DateRevoked", info.DateRevoked.Value.ToDateTimeParamValue()); - } - else - { - statement.TryBindNull("@DateRevoked"); - } + statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); statement.MoveNext(); } @@ -102,29 +122,83 @@ namespace Emby.Server.Implementations.Security } } - private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + public void Update(AuthenticationInfo info) + { + if (info == null) + { + throw new ArgumentNullException("entry"); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) + { + statement.TryBind("@Id", info.Id); + + statement.TryBind("@AccessToken", info.AccessToken); + + statement.TryBind("@DeviceId", info.DeviceId); + statement.TryBind("@AppName", info.AppName); + statement.TryBind("@AppVersion", info.AppVersion); + statement.TryBind("@DeviceName", info.DeviceName); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserName", info.UserName); + statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); + statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); + + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + + public void Delete(AuthenticationInfo info) + { + if (info == null) + { + throw new ArgumentNullException("entry"); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) + { + statement.TryBind("@Id", info.Id); + + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + + private const string BaseSelectText = "select Tokens.Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, DateCreated, DateLastActivity, Devices.CustomName from Tokens left join Devices on Tokens.DeviceId=Devices.Id"; private void BindAuthenticationQueryParams(AuthenticationInfoQuery query, IStatement statement) { - if (!string.IsNullOrWhiteSpace(query.AccessToken)) + if (!string.IsNullOrEmpty(query.AccessToken)) { statement.TryBind("@AccessToken", query.AccessToken); } - if (!string.IsNullOrWhiteSpace(query.UserId)) + if (!query.UserId.Equals(Guid.Empty)) { - statement.TryBind("@UserId", query.UserId); + statement.TryBind("@UserId", query.UserId.ToString("N")); } - if (!string.IsNullOrWhiteSpace(query.DeviceId)) + if (!string.IsNullOrEmpty(query.DeviceId)) { statement.TryBind("@DeviceId", query.DeviceId); } - - if (query.IsActive.HasValue) - { - statement.TryBind("@IsActive", query.IsActive.Value); - } } public QueryResult Get(AuthenticationInfoQuery query) @@ -138,26 +212,19 @@ namespace Emby.Server.Implementations.Security var whereClauses = new List(); - var startIndex = query.StartIndex ?? 0; - - if (!string.IsNullOrWhiteSpace(query.AccessToken)) + if (!string.IsNullOrEmpty(query.AccessToken)) { whereClauses.Add("AccessToken=@AccessToken"); } - if (!string.IsNullOrWhiteSpace(query.UserId)) - { - whereClauses.Add("UserId=@UserId"); - } - - if (!string.IsNullOrWhiteSpace(query.DeviceId)) + if (!string.IsNullOrEmpty(query.DeviceId)) { whereClauses.Add("DeviceId=@DeviceId"); } - if (query.IsActive.HasValue) + if (!query.UserId.Equals(Guid.Empty)) { - whereClauses.Add("IsActive=@IsActive"); + whereClauses.Add("UserId=@UserId"); } if (query.HasUser.HasValue) @@ -176,28 +243,23 @@ namespace Emby.Server.Implementations.Security string.Empty : " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); - if (startIndex > 0) + commandText += whereTextWithoutPaging; + + commandText += " ORDER BY DateLastActivity desc"; + + if (query.Limit.HasValue || query.StartIndex.HasValue) { - var pagingWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); + var offset = query.StartIndex ?? 0; - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})", - pagingWhereText, - startIndex.ToString(_usCulture))); - } + if (query.Limit.HasValue || offset > 0) + { + commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); + } - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); - - commandText += whereText; - - commandText += " ORDER BY DateCreated"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + if (offset > 0) + { + commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); + } } var list = new List(); @@ -212,7 +274,7 @@ namespace Emby.Server.Implementations.Security var statementTexts = new List(); statementTexts.Add(commandText); - statementTexts.Add("select count (Id) from AccessTokens" + whereTextWithoutPaging); + statementTexts.Add("select count (Id) from Tokens" + whereTextWithoutPaging); var statements = PrepareAllSafe(db, statementTexts) .ToList(); @@ -244,38 +306,11 @@ namespace Emby.Server.Implementations.Security } } - public AuthenticationInfo Get(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseSelectText + " where Id=@Id"; - - using (var statement = connection.PrepareStatement(commandText)) - { - statement.BindParameters["@Id"].Bind(id.ToGuidBlob()); - - foreach (var row in statement.ExecuteQuery()) - { - return Get(row); - } - return null; - } - } - } - } - private AuthenticationInfo Get(IReadOnlyList reader) { var info = new AuthenticationInfo { - Id = reader[0].ReadGuidFromBlob().ToString("N"), + Id = reader[0].ToInt64(), AccessToken = reader[1].ToString() }; @@ -301,18 +336,95 @@ namespace Emby.Server.Implementations.Security if (reader[6].SQLiteType != SQLiteType.Null) { - info.UserId = reader[6].ToString(); + info.UserId = new Guid(reader[6].ToString()); + } + + if (reader[7].SQLiteType != SQLiteType.Null) + { + info.UserName = reader[7].ToString(); } - info.IsActive = reader[7].ToBool(); info.DateCreated = reader[8].ReadDateTime(); if (reader[9].SQLiteType != SQLiteType.Null) { - info.DateRevoked = reader[9].ReadDateTime(); + info.DateLastActivity = reader[9].ReadDateTime(); + } + else + { + info.DateLastActivity = info.DateCreated; + } + + if (reader[10].SQLiteType != SQLiteType.Null) + { + info.DeviceName = reader[10].ToString(); } return info; } + + public DeviceOptions GetDeviceOptions(string deviceId) + { + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + return connection.RunInTransaction(db => + { + using (var statement = PrepareStatementSafe(db, "select CustomName from Devices where Id=@DeviceId")) + { + statement.TryBind("@DeviceId", deviceId); + + var result = new DeviceOptions(); + + foreach (var row in statement.ExecuteQuery()) + { + if (row[0].SQLiteType != SQLiteType.Null) + { + result.CustomName = row[0].ToString(); + } + } + + return result; + } + + }, ReadTransactionMode); + } + } + } + + public void UpdateDeviceOptions(string deviceId, DeviceOptions options) + { + if (options == null) + { + throw new ArgumentNullException("options"); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) + { + statement.TryBind("@Id", deviceId); + + if (string.IsNullOrWhiteSpace(options.CustomName)) + { + statement.TryBindNull("@CustomName"); + } + else + { + statement.TryBind("@CustomName", options.CustomName); + } + + statement.MoveNext(); + } + + }, TransactionMode); + } + } + } } } diff --git a/Emby.Server.Implementations/Security/MBLicenseFile.cs b/Emby.Server.Implementations/Security/MBLicenseFile.cs index dc0e8b1613..1810cbcd25 100644 --- a/Emby.Server.Implementations/Security/MBLicenseFile.cs +++ b/Emby.Server.Implementations/Security/MBLicenseFile.cs @@ -22,12 +22,8 @@ namespace Emby.Server.Implementations.Security get { return _regKey; } set { - if (value != _regKey) - { - //if key is changed - clear out our saved validations - _updateRecords.Clear(); - _regKey = value; - } + _updateRecords.Clear(); + _regKey = value; } } @@ -114,14 +110,14 @@ namespace Emby.Server.Implementations.Security { lock (_fileLock) { - _fileSystem.WriteAllBytes(licenseFile, new byte[] { }); + _fileSystem.WriteAllBytes(licenseFile, Array.Empty()); } } catch (IOException) { lock (_fileLock) { - _fileSystem.WriteAllBytes(licenseFile, new byte[] { }); + _fileSystem.WriteAllBytes(licenseFile, Array.Empty()); } } } diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs index 615ffa1f4b..c9c68703ea 100644 --- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -26,30 +26,11 @@ namespace Emby.Server.Implementations.Security private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate"; private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register"; - /// - /// The _is MB supporter - /// - private bool? _isMbSupporter; - /// - /// The _is MB supporter initialized - /// - private bool _isMbSupporterInitialized; - /// - /// The _is MB supporter sync lock - /// - private object _isMbSupporterSyncLock = new object(); - - /// - /// Gets a value indicating whether this instance is MB supporter. - /// - /// true if this instance is MB supporter; otherwise, false. - public bool IsMBSupporter + public async Task IsSupporter() { - get - { - LazyInitializer.EnsureInitialized(ref _isMbSupporter, ref _isMbSupporterInitialized, ref _isMbSupporterSyncLock, () => GetSupporterRegistrationStatus().Result.IsRegistered); - return _isMbSupporter.Value; - } + var result = await GetRegistrationStatusInternal("MBSupporter", false, _appHost.ApplicationVersion.ToString(), CancellationToken.None).ConfigureAwait(false); + + return result.IsRegistered; } private MBLicenseFile _licenseFile; @@ -66,15 +47,6 @@ namespace Emby.Server.Implementations.Security private readonly IFileSystem _fileSystem; private readonly ICryptoProvider _cryptographyProvider; - private IEnumerable _registeredEntities; - protected IEnumerable RegisteredEntities - { - get - { - return _registeredEntities ?? (_registeredEntities = _appHost.GetExports()); - } - } - /// /// Initializes a new instance of the class. /// @@ -95,46 +67,13 @@ namespace Emby.Server.Implementations.Security _logger = logManager.GetLogger("SecurityManager"); } - /// - /// Load all registration info for all entities that require registration - /// - /// - public async Task LoadAllRegistrationInfo() - { - var tasks = new List(); - - ResetSupporterInfo(); - tasks.AddRange(RegisteredEntities.Select(i => i.LoadRegistrationInfoAsync())); - await Task.WhenAll(tasks); - } - /// /// Gets the registration status. /// This overload supports existing plug-ins. /// - /// The feature. - /// The MB2 equivalent. - /// Task{MBRegistrationRecord}. - public Task GetRegistrationStatus(string feature, string mb2Equivalent = null) + public Task GetRegistrationStatus(string feature) { - return GetRegistrationStatusInternal(feature, mb2Equivalent); - } - - /// - /// Gets the registration status. - /// - /// The feature. - /// The MB2 equivalent. - /// The version of this feature - /// Task{MBRegistrationRecord}. - public Task GetRegistrationStatus(string feature, string mb2Equivalent, string version) - { - return GetRegistrationStatusInternal(feature, mb2Equivalent, version); - } - - private Task GetSupporterRegistrationStatus() - { - return GetRegistrationStatusInternal("MBSupporter", null, _appHost.ApplicationVersion.ToString()); + return GetRegistrationStatusInternal(feature, false, null, CancellationToken.None); } /// @@ -149,20 +88,24 @@ namespace Emby.Server.Implementations.Security } set { - var newValue = value; - if (newValue != null) - { - newValue = newValue.Trim(); - } + throw new Exception("Please call UpdateSupporterKey"); + } + } - if (newValue != LicenseFile.RegKey) - { - LicenseFile.RegKey = newValue; - LicenseFile.Save(); + public async Task UpdateSupporterKey(string newValue) + { + if (newValue != null) + { + newValue = newValue.Trim(); + } - // re-load registration info - Task.Run(() => LoadAllRegistrationInfo()); - } + if (!string.Equals(newValue, LicenseFile.RegKey, StringComparison.Ordinal)) + { + LicenseFile.RegKey = newValue; + LicenseFile.Save(); + + // Reset this + await GetRegistrationStatusInternal("MBSupporter", true, _appHost.ApplicationVersion.ToString(), CancellationToken.None).ConfigureAwait(false); } } @@ -187,7 +130,7 @@ namespace Emby.Server.Implementations.Security { using (var response = await _httpClient.Post(options).ConfigureAwait(false)) { - var reg = _jsonSerializer.DeserializeFromStream(response.Content); + var reg = await _jsonSerializer.DeserializeFromStreamAsync(response.Content).ConfigureAwait(false); if (reg == null) { @@ -197,7 +140,7 @@ namespace Emby.Server.Implementations.Security } if (!String.IsNullOrEmpty(reg.key)) { - SupporterKey = reg.key; + await UpdateSupporterKey(reg.key).ConfigureAwait(false); } } @@ -241,97 +184,113 @@ namespace Emby.Server.Implementations.Security } } - private async Task GetRegistrationStatusInternal(string feature, - string mb2Equivalent = null, - string version = null) + private SemaphoreSlim _regCheckLock = new SemaphoreSlim(1, 1); + + private async Task GetRegistrationStatusInternal(string feature, bool forceCallToServer, string version, CancellationToken cancellationToken) { - var regInfo = LicenseFile.GetRegInfo(feature); - var lastChecked = regInfo == null ? DateTime.MinValue : regInfo.LastChecked; - var expDate = regInfo == null ? DateTime.MinValue : regInfo.ExpirationDate; + await _regCheckLock.WaitAsync(cancellationToken).ConfigureAwait(false); - var maxCacheDays = 14; - var nextCheckDate = new [] { expDate, lastChecked.AddDays(maxCacheDays) }.Min(); - - if (nextCheckDate > DateTime.UtcNow.AddDays(maxCacheDays)) + try { - nextCheckDate = DateTime.MinValue; - } + var regInfo = LicenseFile.GetRegInfo(feature); + var lastChecked = regInfo == null ? DateTime.MinValue : regInfo.LastChecked; + var expDate = regInfo == null ? DateTime.MinValue : regInfo.ExpirationDate; - //check the reg file first to alleviate strain on the MB admin server - must actually check in every 30 days tho - var reg = new RegRecord - { - // Cache the result for up to a week - registered = regInfo != null && nextCheckDate >= DateTime.UtcNow && expDate >= DateTime.UtcNow, - expDate = expDate - }; + var maxCacheDays = 14; + var nextCheckDate = new[] { expDate, lastChecked.AddDays(maxCacheDays) }.Min(); - var success = reg.registered; - - if (!(lastChecked > DateTime.UtcNow.AddDays(-1)) || !reg.registered) - { - var data = new Dictionary + if (nextCheckDate > DateTime.UtcNow.AddDays(maxCacheDays)) { - { "feature", feature }, - { "key", SupporterKey }, - { "mac", _appHost.SystemId }, - { "systemid", _appHost.SystemId }, - { "mb2equiv", mb2Equivalent }, - { "ver", version }, + nextCheckDate = DateTime.MinValue; + } + + //check the reg file first to alleviate strain on the MB admin server - must actually check in every 30 days tho + var reg = new RegRecord + { + // Cache the result for up to a week + registered = regInfo != null && nextCheckDate >= DateTime.UtcNow && expDate >= DateTime.UtcNow, + expDate = expDate + }; + + var key = SupporterKey; + + if (!forceCallToServer && string.IsNullOrWhiteSpace(key)) + { + return new MBRegistrationRecord(); + } + + var success = reg.registered; + + if (!(lastChecked > DateTime.UtcNow.AddDays(-1)) || (!reg.registered)) + { + var data = new Dictionary + { + { "feature", feature }, + { "key", key }, + { "mac", _appHost.SystemId }, + { "systemid", _appHost.SystemId }, + { "ver", version }, { "platform", _appHost.OperatingSystemDisplayName } }; - try - { - var options = new HttpRequestOptions + try { - Url = MBValidateUrl, - - // Seeing block length errors - EnableHttpCompression = false, - BufferContent = false - }; - - options.SetPostData(data); - - using (var response = (await _httpClient.Post(options).ConfigureAwait(false))) - { - using (var json = response.Content) + var options = new HttpRequestOptions { - reg = _jsonSerializer.DeserializeFromStream(json); - success = true; + Url = MBValidateUrl, + + // Seeing block length errors + EnableHttpCompression = false, + BufferContent = false, + CancellationToken = cancellationToken + }; + + options.SetPostData(data); + + using (var response = (await _httpClient.Post(options).ConfigureAwait(false))) + { + using (var json = response.Content) + { + reg = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false); + success = true; + } } - } - if (reg.registered) - { - _logger.Info("Registered for feature {0}", feature); - LicenseFile.AddRegCheck(feature, reg.expDate); - } - else - { - _logger.Info("Not registered for feature {0}", feature); - LicenseFile.RemoveRegCheck(feature); - } + if (reg.registered) + { + _logger.Info("Registered for feature {0}", feature); + LicenseFile.AddRegCheck(feature, reg.expDate); + } + else + { + _logger.Info("Not registered for feature {0}", feature); + LicenseFile.RemoveRegCheck(feature); + } + } + catch (Exception e) + { + _logger.ErrorException("Error checking registration status of {0}", e, feature); + } } - catch (Exception e) + + var record = new MBRegistrationRecord { - _logger.ErrorException("Error checking registration status of {0}", e, feature); - } + IsRegistered = reg.registered, + ExpirationDate = reg.expDate, + RegChecked = true, + RegError = !success + }; + + record.TrialVersion = IsInTrial(reg.expDate, record.RegChecked, record.IsRegistered); + record.IsValid = !record.RegChecked || record.IsRegistered || record.TrialVersion; + + return record; } - - var record = new MBRegistrationRecord + finally { - IsRegistered = reg.registered, - ExpirationDate = reg.expDate, - RegChecked = true, - RegError = !success - }; - - record.TrialVersion = IsInTrial(reg.expDate, record.RegChecked, record.IsRegistered); - record.IsValid = !record.RegChecked || record.IsRegistered || record.TrialVersion; - - return record; + _regCheckLock.Release(); + } } private bool IsInTrial(DateTime expirationDate, bool regChecked, bool isRegistered) @@ -346,14 +305,5 @@ namespace Emby.Server.Implementations.Security return isInTrial && !isRegistered; } - - /// - /// Resets the supporter info. - /// - private void ResetSupporterInfo() - { - _isMbSupporter = null; - _isMbSupporterInitialized = false; - } } } \ No newline at end of file diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index c9db336890..26371d21d5 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -3,6 +3,7 @@ using System.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using System.Threading.Tasks; namespace Emby.Common.Implementations.Serialization { @@ -60,7 +61,7 @@ namespace Emby.Common.Implementations.Serialization throw new ArgumentNullException("file"); } - using (Stream stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + using (Stream stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { SerializeToStream(obj, stream); } @@ -68,7 +69,7 @@ namespace Emby.Common.Implementations.Serialization private Stream OpenFile(string path) { - _logger.Debug("Deserializing file {0}", path); + //_logger.Debug("Deserializing file {0}", path); return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072); } @@ -135,6 +136,21 @@ namespace Emby.Common.Implementations.Serialization return ServiceStack.Text.JsonSerializer.DeserializeFromStream(stream); } + public async Task DeserializeFromStreamAsync(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + using (var reader = new StreamReader(stream)) + { + var json = await reader.ReadToEndAsync().ConfigureAwait(false); + + return ServiceStack.Text.JsonSerializer.DeserializeFromString(json); + } + } + /// /// Deserializes from string. /// @@ -174,6 +190,26 @@ namespace Emby.Common.Implementations.Serialization return ServiceStack.Text.JsonSerializer.DeserializeFromStream(type, stream); } + public async Task DeserializeFromStreamAsync(Stream stream, Type type) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (type == null) + { + throw new ArgumentNullException("type"); + } + + using (var reader = new StreamReader(stream)) + { + var json = await reader.ReadToEndAsync().ConfigureAwait(false); + + return ServiceStack.Text.JsonSerializer.DeserializeFromString(json, type); + } + } + /// /// Configures this instance. /// @@ -184,6 +220,18 @@ namespace Emby.Common.Implementations.Serialization ServiceStack.Text.JsConfig.IncludeNullValues = false; ServiceStack.Text.JsConfig.AlwaysUseUtc = true; ServiceStack.Text.JsConfig.AssumeUtc = true; + + ServiceStack.Text.JsConfig.SerializeFn = SerializeGuid; + } + + private string SerializeGuid(Guid guid) + { + if (guid.Equals(Guid.Empty)) + { + return null; + } + + return guid.ToString("N"); } /// diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 3e3f7e0d77..1686a548bc 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -57,26 +57,6 @@ namespace Emby.Server.Implementations } } - /// - /// The _ibn path - /// - private string _ibnPath; - /// - /// Gets the path to the Images By Name directory - /// - /// The images by name path. - public string ItemsByNamePath - { - get - { - return _ibnPath ?? (_ibnPath = Path.Combine(ProgramDataPath, "ImagesByName")); - } - set - { - _ibnPath = value; - } - } - /// /// Gets the path to the People directory /// @@ -85,7 +65,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "People"); + return Path.Combine(InternalMetadataPath, "People"); } } @@ -93,7 +73,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "artists"); + return Path.Combine(InternalMetadataPath, "artists"); } } @@ -105,7 +85,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "Genre"); + return Path.Combine(InternalMetadataPath, "Genre"); } } @@ -117,7 +97,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "MusicGenre"); + return Path.Combine(InternalMetadataPath, "MusicGenre"); } } @@ -129,7 +109,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "Studio"); + return Path.Combine(InternalMetadataPath, "Studio"); } } @@ -141,7 +121,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "Year"); + return Path.Combine(InternalMetadataPath, "Year"); } } @@ -153,7 +133,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "general"); + return Path.Combine(InternalMetadataPath, "general"); } } @@ -165,7 +145,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "ratings"); + return Path.Combine(InternalMetadataPath, "ratings"); } } @@ -177,7 +157,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "mediainfo"); + return Path.Combine(InternalMetadataPath, "mediainfo"); } } @@ -193,12 +173,21 @@ namespace Emby.Server.Implementations } } + private string _defaultTranscodingTempPath; + public string DefaultTranscodingTempPath + { + get + { + return _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp")); + } + } + private string _transcodingTempPath; public string TranscodingTempPath { get { - return _transcodingTempPath ?? (_transcodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp")); + return _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath); } set { @@ -210,17 +199,26 @@ namespace Emby.Server.Implementations { var path = TranscodingTempPath; - try + if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase)) { - Directory.CreateDirectory(path); - return path; - } - catch - { - path = Path.Combine(ProgramDataPath, "transcoding-temp"); - Directory.CreateDirectory(path); - return path; + try + { + Directory.CreateDirectory(path); + + var testPath = Path.Combine(path, Guid.NewGuid().ToString()); + Directory.CreateDirectory(testPath); + Directory.Delete(testPath); + + return path; + } + catch + { + } } + + path = DefaultTranscodingTempPath; + Directory.CreateDirectory(path); + return path; } /// @@ -231,7 +229,7 @@ namespace Emby.Server.Implementations { get { - return Path.Combine(ItemsByNamePath, "GameGenre"); + return Path.Combine(InternalMetadataPath, "GameGenre"); } } @@ -247,5 +245,15 @@ namespace Emby.Server.Implementations _internalMetadataPath = value; } } + + private const string _virtualInternalMetadataPath = "%MetadataPath%"; + public string VirtualInternalMetadataPath + { + get + { + return _virtualInternalMetadataPath; + } + } + } } diff --git a/Emby.Server.Implementations/ServerManager/ServerManager.cs b/Emby.Server.Implementations/ServerManager/ServerManager.cs deleted file mode 100644 index b267f928b6..0000000000 --- a/Emby.Server.Implementations/ServerManager/ServerManager.cs +++ /dev/null @@ -1,357 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Text; - -namespace Emby.Server.Implementations.ServerManager -{ - /// - /// Manages the Http Server, Udp Server and WebSocket connections - /// - public class ServerManager : IServerManager - { - /// - /// Both the Ui and server will have a built-in HttpServer. - /// People will inevitably want remote control apps so it's needed in the Ui too. - /// - /// The HTTP server. - private IHttpServer HttpServer { get; set; } - - /// - /// Gets or sets the json serializer. - /// - /// The json serializer. - private readonly IJsonSerializer _jsonSerializer; - - /// - /// The web socket connections - /// - private readonly List _webSocketConnections = new List(); - /// - /// Gets the web socket connections. - /// - /// The web socket connections. - public IEnumerable WebSocketConnections - { - get { return _webSocketConnections; } - } - - public event EventHandler> WebSocketConnected; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// The _application host - /// - private readonly IServerApplicationHost _applicationHost; - - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } - - /// - /// Gets the web socket listeners. - /// - /// The web socket listeners. - private readonly List _webSocketListeners = new List(); - - private bool _disposed; - private readonly IMemoryStreamFactory _memoryStreamProvider; - private readonly ITextEncoding _textEncoding; - - /// - /// Initializes a new instance of the class. - /// - /// The application host. - /// The json serializer. - /// The logger. - /// The configuration manager. - /// applicationHost - public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding) - { - if (applicationHost == null) - { - throw new ArgumentNullException("applicationHost"); - } - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - _logger = logger; - _jsonSerializer = jsonSerializer; - _applicationHost = applicationHost; - ConfigurationManager = configurationManager; - _memoryStreamProvider = memoryStreamProvider; - _textEncoding = textEncoding; - } - - /// - /// Starts this instance. - /// - public void Start(string[] urlPrefixes) - { - ReloadHttpServer(urlPrefixes); - } - - /// - /// Restarts the Http Server, or starts it if not currently running - /// - private void ReloadHttpServer(string[] urlPrefixes) - { - _logger.Info("Loading Http Server"); - - try - { - HttpServer = _applicationHost.Resolve(); - HttpServer.StartServer(urlPrefixes); - } - catch (Exception ex) - { - var msg = string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase) - ? "The http server is unable to start due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system." - : "Error starting Http Server"; - - _logger.ErrorException(msg, ex); - - throw; - } - - HttpServer.WebSocketConnected += HttpServer_WebSocketConnected; - } - - /// - /// Handles the WebSocketConnected event of the HttpServer control. - /// - /// The source of the event. - /// The instance containing the event data. - void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e) - { - if (_disposed) - { - return; - } - - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger, _memoryStreamProvider, _textEncoding) - { - OnReceive = ProcessWebSocketMessageReceived, - Url = e.Url, - QueryString = e.QueryString ?? new QueryParamCollection() - }; - - _webSocketConnections.Add(connection); - - if (WebSocketConnected != null) - { - EventHelper.FireEventIfNotNull(WebSocketConnected, this, new GenericEventArgs (connection), _logger); - } - } - - /// - /// Processes the web socket message received. - /// - /// The result. - private async void ProcessWebSocketMessageReceived(WebSocketMessageInfo result) - { - if (_disposed) - { - return; - } - - //_logger.Debug("Websocket message received: {0}", result.MessageType); - - var tasks = _webSocketListeners.Select(i => Task.Run(async () => - { - try - { - await i.ProcessMessage(result).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType ?? string.Empty); - } - })); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - /// - /// Sends a message to all clients currently connected via a web socket - /// - /// - /// Type of the message. - /// The data. - /// Task. - public void SendWebSocketMessage(string messageType, T data) - { - SendWebSocketMessage(messageType, () => data); - } - - /// - /// Sends a message to all clients currently connected via a web socket - /// - /// - /// Type of the message. - /// The function that generates the data to send, if there are any connected clients - public void SendWebSocketMessage(string messageType, Func dataFunction) - { - SendWebSocketMessageAsync(messageType, dataFunction, CancellationToken.None); - } - - /// - /// Sends a message to all clients currently connected via a web socket - /// - /// - /// Type of the message. - /// The function that generates the data to send, if there are any connected clients - /// The cancellation token. - /// Task. - /// messageType - public Task SendWebSocketMessageAsync(string messageType, Func dataFunction, CancellationToken cancellationToken) - { - return SendWebSocketMessageAsync(messageType, dataFunction, _webSocketConnections, cancellationToken); - } - - /// - /// Sends the web socket message async. - /// - /// - /// Type of the message. - /// The data function. - /// The connections. - /// The cancellation token. - /// Task. - /// messageType - /// or - /// dataFunction - /// or - /// cancellationToken - private async Task SendWebSocketMessageAsync(string messageType, Func dataFunction, IEnumerable connections, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(messageType)) - { - throw new ArgumentNullException("messageType"); - } - - if (dataFunction == null) - { - throw new ArgumentNullException("dataFunction"); - } - - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var connectionsList = connections.Where(s => s.State == WebSocketState.Open).ToList(); - - if (connectionsList.Count > 0) - { - _logger.Info("Sending web socket message {0}", messageType); - - var message = new WebSocketMessage { MessageType = messageType, Data = dataFunction() }; - var json = _jsonSerializer.SerializeToString(message); - - var tasks = connectionsList.Select(s => Task.Run(() => - { - try - { - s.SendAsync(json, cancellationToken); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending web socket message {0} to {1}", ex, messageType, s.RemoteEndPoint); - } - - }, cancellationToken)); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - } - - /// - /// Disposes the current HttpServer - /// - private void DisposeHttpServer() - { - _logger.Info("Disposing web socket connections"); - foreach (var socket in _webSocketConnections) - { - // Dispose the connection - socket.Dispose(); - } - - _webSocketConnections.Clear(); - - if (HttpServer != null) - { - HttpServer.WebSocketConnected -= HttpServer_WebSocketConnected; - - _logger.Info("Disposing http server"); - - HttpServer.Dispose(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - _disposed = true; - - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - DisposeHttpServer(); - } - } - - /// - /// Adds the web socket listeners. - /// - /// The listeners. - public void AddWebSocketListeners(IEnumerable listeners) - { - _webSocketListeners.AddRange(listeners); - } - } -} diff --git a/Emby.Server.Implementations/Services/RequestHelper.cs b/Emby.Server.Implementations/Services/RequestHelper.cs index 7538d3102f..711ba8bbce 100644 --- a/Emby.Server.Implementations/Services/RequestHelper.cs +++ b/Emby.Server.Implementations/Services/RequestHelper.cs @@ -1,12 +1,13 @@ using System; using System.IO; using Emby.Server.Implementations.HttpServer; +using System.Threading.Tasks; namespace Emby.Server.Implementations.Services { public class RequestHelper { - public static Func GetRequestReader(HttpListenerHost host, string contentType) + public static Func> GetRequestReader(HttpListenerHost host, string contentType) { switch (GetContentTypeWithoutEncoding(contentType)) { diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 22e1bc4aa5..16de1a0833 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Services { public static class ResponseHelper { - public static async Task WriteToResponse(IResponse response, IRequest request, object result, CancellationToken cancellationToken) + public static Task WriteToResponse(IResponse response, IRequest request, object result, CancellationToken cancellationToken) { if (result == null) { @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Services } response.SetContentLength(0); - return; + return Task.CompletedTask; } var httpResult = result as IHttpResult; @@ -46,18 +46,6 @@ namespace Emby.Server.Implementations.Services // httpResult.ContentType = defaultContentType; //} //response.ContentType = httpResult.ContentType; - - if (httpResult.Cookies != null) - { - var httpRes = response as IHttpResponse; - if (httpRes != null) - { - foreach (var cookie in httpResult.Cookies) - { - httpRes.SetCookie(cookie); - } - } - } } var responseOptions = result as IHasHeaders; @@ -90,32 +78,26 @@ namespace Emby.Server.Implementations.Services var asyncStreamWriter = result as IAsyncStreamWriter; if (asyncStreamWriter != null) { - await asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken).ConfigureAwait(false); - return; + return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); } var streamWriter = result as IStreamWriter; if (streamWriter != null) { streamWriter.WriteTo(response.OutputStream); - return; + return Task.CompletedTask; } var fileWriter = result as FileWriter; if (fileWriter != null) { - await fileWriter.WriteToAsync(response, cancellationToken).ConfigureAwait(false); - return; + return fileWriter.WriteToAsync(response, cancellationToken); } var stream = result as Stream; if (stream != null) { - using (stream) - { - await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); - return; - } + return CopyStream(stream, response.OutputStream); } var bytes = result as byte[]; @@ -126,9 +108,9 @@ namespace Emby.Server.Implementations.Services if (bytes.Length > 0) { - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); } - return; + return Task.CompletedTask; } var responseText = result as string; @@ -138,12 +120,20 @@ namespace Emby.Server.Implementations.Services response.SetContentLength(bytes.Length); if (bytes.Length > 0) { - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); } - return; + return Task.CompletedTask; } - await WriteObject(request, result, response).ConfigureAwait(false); + return WriteObject(request, result, response); + } + + private static async Task CopyStream(Stream src, Stream dest) + { + using (src) + { + await src.CopyToAsync(dest).ConfigureAwait(false); + } } public static async Task WriteObject(IRequest request, object result, IResponse response) diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index 3fd6d88f81..3726c9f6b0 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Services // mi.ReturnType // : Type.GetType(requestType.FullName + "Response"); - RegisterRestPaths(appHost, requestType); + RegisterRestPaths(appHost, requestType, serviceType); appHost.AddServiceInfo(serviceType, requestType); } @@ -68,14 +68,14 @@ namespace Emby.Server.Implementations.Services return null; } - public readonly Dictionary> RestPathMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); + public readonly RestPath.RestPathMap RestPathMap = new RestPath.RestPathMap(); - public void RegisterRestPaths(HttpListenerHost appHost, Type requestType) + public void RegisterRestPaths(HttpListenerHost appHost, Type requestType, Type serviceType) { var attrs = appHost.GetRouteAttributes(requestType); foreach (RouteAttribute attr in attrs) { - var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.IsHidden, attr.Summary, attr.Description); + var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, serviceType, attr.Path, attr.Verbs, attr.IsHidden, attr.Summary, attr.Description); RegisterRestPath(restPath); } @@ -114,19 +114,20 @@ namespace Emby.Server.Implementations.Services } var bestScore = -1; + RestPath bestMatch = null; foreach (var restPath in firstMatches) { var score = restPath.MatchScore(httpMethod, matchUsingPathParts, logger); - if (score > bestScore) bestScore = score; + if (score > bestScore) + { + bestScore = score; + bestMatch = restPath; + } } - if (bestScore > 0) + if (bestScore > 0 && bestMatch != null) { - foreach (var restPath in firstMatches) - { - if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts, logger)) - return restPath; - } + return bestMatch; } } @@ -136,19 +137,21 @@ namespace Emby.Server.Implementations.Services if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue; var bestScore = -1; + RestPath bestMatch = null; foreach (var restPath in firstMatches) { var score = restPath.MatchScore(httpMethod, matchUsingPathParts, logger); - if (score > bestScore) bestScore = score; - } - if (bestScore > 0) - { - foreach (var restPath in firstMatches) + if (score > bestScore) { - if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts, logger)) - return restPath; + bestScore = score; + bestMatch = restPath; } } + + if (bestScore > 0 && bestMatch != null) + { + return bestMatch; + } } return null; diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 5709d3e0a8..79b57438ca 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Services } } - public static async Task Execute(Type serviceType, IRequest request, object instance, object requestDto, string requestName) + public static Task Execute(Type serviceType, IRequest request, object instance, object requestDto, string requestName) { var actionName = request.Verb ?? "POST"; @@ -82,7 +82,10 @@ namespace Emby.Server.Implementations.Services foreach (var requestFilter in actionContext.RequestFilters) { requestFilter.RequestFilter(request, request.Response, requestDto); - if (request.Response.IsClosed) return null; + if (request.Response.IsClosed) + { + Task.FromResult(null); + } } } @@ -91,17 +94,56 @@ namespace Emby.Server.Implementations.Services var taskResponse = response as Task; if (taskResponse != null) { - await taskResponse.ConfigureAwait(false); - response = ServiceHandler.GetTaskResult(taskResponse); + return GetTaskResult(taskResponse); } - return response; + return Task.FromResult(response); } var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower(); throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetMethodName(), expectedMethodName, serviceType.GetMethodName())); } + private static async Task GetTaskResult(Task task) + { + try + { + var taskObject = task as Task; + if (taskObject != null) + { + return await taskObject.ConfigureAwait(false); + } + + await task.ConfigureAwait(false); + + var type = task.GetType().GetTypeInfo(); + if (!type.IsGenericType) + { + return null; + } + + var resultProperty = type.GetDeclaredProperty("Result"); + if (resultProperty == null) + { + return null; + } + + var result = resultProperty.GetValue(task); + + // hack alert + if (result.GetType().Name.IndexOf("voidtaskresult", StringComparison.OrdinalIgnoreCase) != -1) + { + return null; + } + + return result; + } + catch (TypeAccessException) + { + return null; //return null for void Task's + } + } + public static List Reset(Type serviceType) { var actions = new List(); diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index d500595ce4..e76857a8df 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -11,55 +11,7 @@ namespace Emby.Server.Implementations.Services { public class ServiceHandler { - public async Task HandleResponseAsync(object response) - { - var taskResponse = response as Task; - - if (taskResponse == null) - { - return response; - } - - await taskResponse.ConfigureAwait(false); - - var taskResult = GetTaskResult(taskResponse); - - var subTask = taskResult as Task; - if (subTask != null) - { - taskResult = GetTaskResult(subTask); - } - - return taskResult; - } - - internal static object GetTaskResult(Task task) - { - try - { - var taskObject = task as Task; - if (taskObject != null) - { - return taskObject.Result; - } - - task.Wait(); - - var type = task.GetType().GetTypeInfo(); - if (!type.IsGenericType) - { - return null; - } - - return type.GetDeclaredProperty("Result").GetValue(task); - } - catch (TypeAccessException) - { - return null; //return null for void Task's - } - } - - protected static object CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType) + protected static Task CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType) { if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0) { @@ -69,7 +21,7 @@ namespace Emby.Server.Implementations.Services return deserializer(requestType, httpReq.InputStream); } } - return host.CreateInstance(requestType); + return Task.FromResult(host.CreateInstance(requestType)); } public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, ILogger logger, out string contentType) @@ -137,14 +89,11 @@ namespace Emby.Server.Implementations.Services if (ResponseContentType != null) httpReq.ResponseContentType = ResponseContentType; - var request = httpReq.Dto = CreateRequest(appHost, httpReq, restPath, logger); + var request = httpReq.Dto = await CreateRequest(appHost, httpReq, restPath, logger).ConfigureAwait(false); appHost.ApplyRequestFilters(httpReq, httpRes, request); - var rawResponse = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false); - - //var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false); - var response = rawResponse; + var response = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false); // Apply response filters foreach (var responseFilter in appHost.ResponseFilters) @@ -155,38 +104,37 @@ namespace Emby.Server.Implementations.Services await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false); } - public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) + public static async Task CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) { var requestType = restPath.RequestType; if (RequireqRequestStream(requestType)) { // Used by IRequiresRequestStream - var request = ServiceHandler.CreateRequest(httpReq, restPath, GetRequestParams(httpReq), host.CreateInstance(requestType)); + var requestParams = await GetRequestParams(httpReq).ConfigureAwait(false); + var request = ServiceHandler.CreateRequest(httpReq, restPath, requestParams, host.CreateInstance(requestType)); var rawReq = (IRequiresRequestStream)request; rawReq.RequestStream = httpReq.InputStream; return rawReq; } + else + { + var requestParams = await GetFlattenedRequestParams(httpReq).ConfigureAwait(false); - var requestParams = GetFlattenedRequestParams(httpReq); - return CreateRequest(host, httpReq, restPath, requestParams); + var requestDto = await CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType).ConfigureAwait(false); + + return CreateRequest(httpReq, restPath, requestParams, requestDto); + } } - private static bool RequireqRequestStream(Type requestType) + public static bool RequireqRequestStream(Type requestType) { var requiresRequestStreamTypeInfo = typeof(IRequiresRequestStream).GetTypeInfo(); return requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo()); } - public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, Dictionary requestParams) - { - var requestDto = CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType); - - return CreateRequest(httpReq, restPath, requestParams, requestDto); - } - public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary requestParams, object requestDto) { string contentType; @@ -200,7 +148,7 @@ namespace Emby.Server.Implementations.Services /// /// Duplicate Params are given a unique key by appending a #1 suffix /// - private static Dictionary GetRequestParams(IRequest request) + private static async Task> GetRequestParams(IRequest request) { var map = new Dictionary(); @@ -224,7 +172,7 @@ namespace Emby.Server.Implementations.Services if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { - var formData = request.FormData; + var formData = await request.GetFormData().ConfigureAwait(false); if (formData != null) { foreach (var name in formData.Keys) @@ -258,7 +206,7 @@ namespace Emby.Server.Implementations.Services /// /// Duplicate params have their values joined together in a comma-delimited string /// - private static Dictionary GetFlattenedRequestParams(IRequest request) + private static async Task> GetFlattenedRequestParams(IRequest request) { var map = new Dictionary(); @@ -270,7 +218,7 @@ namespace Emby.Server.Implementations.Services if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { - var formData = request.FormData; + var formData = await request.GetFormData().ConfigureAwait(false); if (formData != null) { foreach (var name in formData.Keys) diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 0ca36df19d..282269e7b1 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -48,6 +48,8 @@ namespace Emby.Server.Implementations.Services public Type RequestType { get; private set; } + public Type ServiceType { get; private set; } + public string Path { get { return this.restPath; } } public string Summary { get; private set; } @@ -56,6 +58,11 @@ namespace Emby.Server.Implementations.Services public int Priority { get; set; } //passed back to RouteAttribute + public IEnumerable PathVariables + { + get { return this.variablesNames.Where(e => !string.IsNullOrWhiteSpace(e)); } + } + public static string[] GetPathPartsForMatching(string pathInfo) { return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); @@ -93,9 +100,10 @@ namespace Emby.Server.Implementations.Services return list; } - public RestPath(Func createInstanceFn, Func> getParseFn, Type requestType, string path, string verbs, bool isHidden = false, string summary = null, string description = null) + public RestPath(Func createInstanceFn, Func> getParseFn, Type requestType, Type serviceType, string path, string verbs, bool isHidden = false, string summary = null, string description = null) { this.RequestType = requestType; + this.ServiceType = serviceType; this.Summary = summary; this.IsHidden = isHidden; this.Description = description; @@ -558,5 +566,12 @@ namespace Emby.Server.Implementations.Services return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap); } + + public class RestPathMap : SortedDictionary> + { + public RestPathMap() : base(StringComparer.OrdinalIgnoreCase) + { + } + } } } \ No newline at end of file diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs deleted file mode 100644 index fc2bdbd557..0000000000 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.Services -{ - [Route("/swagger", "GET", Summary = "Gets the swagger specifications")] - [Route("/swagger.json", "GET", Summary = "Gets the swagger specifications")] - public class GetSwaggerSpec : IReturn - { - } - - public class SwaggerSpec - { - public string swagger { get; set; } - public string[] schemes { get; set; } - public SwaggerInfo info { get; set; } - public string host { get; set; } - public string basePath { get; set; } - public SwaggerTag[] tags { get; set; } - public IDictionary> paths { get; set; } - public Dictionary definitions { get; set; } - public SwaggerComponents components { get; set; } - } - - public class SwaggerComponents - { - public Dictionary securitySchemes { get; set; } - } - - public class SwaggerSecurityScheme - { - public string name { get; set; } - public string type { get; set; } - public string @in { get; set; } - } - - public class SwaggerInfo - { - public string description { get; set; } - public string version { get; set; } - public string title { get; set; } - public string termsOfService { get; set; } - - public SwaggerConcactInfo contact { get; set; } - } - - public class SwaggerConcactInfo - { - public string email { get; set; } - public string name { get; set; } - public string url { get; set; } - } - - public class SwaggerTag - { - public string description { get; set; } - public string name { get; set; } - } - - public class SwaggerMethod - { - public string summary { get; set; } - public string description { get; set; } - public string[] tags { get; set; } - public string operationId { get; set; } - public string[] consumes { get; set; } - public string[] produces { get; set; } - public SwaggerParam[] parameters { get; set; } - public Dictionary responses { get; set; } - public Dictionary[] security { get; set; } - } - - public class SwaggerParam - { - public string @in { get; set; } - public string name { get; set; } - public string description { get; set; } - public bool required { get; set; } - public string type { get; set; } - public string collectionFormat { get; set; } - } - - public class SwaggerResponse - { - public string description { get; set; } - - // ex. "$ref":"#/definitions/Pet" - public Dictionary schema { get; set; } - } - - public class SwaggerDefinition - { - public string type { get; set; } - public Dictionary properties { get; set; } - } - - public class SwaggerProperty - { - public string type { get; set; } - public string format { get; set; } - public string description { get; set; } - public string[] @enum { get; set; } - public string @default { get; set; } - } - - public class SwaggerService : IService, IRequiresRequest - { - private SwaggerSpec _spec; - - public IRequest Request { get; set; } - - public object Get(GetSwaggerSpec request) - { - return _spec ?? (_spec = GetSpec()); - } - - private SwaggerSpec GetSpec() - { - string host = null; - Uri uri; - if (Uri.TryCreate(Request.RawUrl, UriKind.Absolute, out uri)) - { - host = uri.Host; - } - - var securitySchemes = new Dictionary(); - - securitySchemes["api_key"] = new SwaggerSecurityScheme - { - name = "api_key", - type = "apiKey", - @in = "query" - }; - - var spec = new SwaggerSpec - { - schemes = new[] { "http" }, - tags = GetTags(), - swagger = "2.0", - info = new SwaggerInfo - { - title = "Emby Server API", - version = "1.0.0", - description = "Explore the Emby Server API", - contact = new SwaggerConcactInfo - { - name = "Emby Developer Community", - url = "https://emby.media/community/index.php?/forum/47-developer-api" - }, - termsOfService = "https://emby.media/terms" - }, - paths = GetPaths(), - definitions = GetDefinitions(), - basePath = "/emby", - host = host, - - components = new SwaggerComponents - { - securitySchemes = securitySchemes - } - }; - - return spec; - } - - - private SwaggerTag[] GetTags() - { - return new SwaggerTag[] { }; - } - - private Dictionary GetDefinitions() - { - return new Dictionary(); - } - - private IDictionary> GetPaths() - { - var paths = new SortedDictionary>(); - - var all = ServiceController.Instance.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList(); - - foreach (var current in all) - { - foreach (var info in current.Value) - { - if (info.IsHidden) - { - continue; - } - - if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - if (info.Path.StartsWith("/emby", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - paths[info.Path] = GetPathInfo(info); - } - } - - return paths; - } - - private Dictionary GetPathInfo(RestPath info) - { - var result = new Dictionary(); - - foreach (var verb in info.Verbs) - { - var responses = new Dictionary - { - }; - - responses["200"] = new SwaggerResponse - { - description = "OK" - }; - - var security = new List>(); - - var apiKeySecurity = new Dictionary(); - apiKeySecurity["api_key"] = new string[] { }; - - security.Add(apiKeySecurity); - - result[verb.ToLower()] = new SwaggerMethod - { - summary = info.Summary, - description = info.Description, - produces = new[] - { - "application/json" - }, - consumes = new[] - { - "application/json" - }, - operationId = info.RequestType.Name, - tags = new string[] { }, - - parameters = new SwaggerParam[] { }, - - responses = responses, - - security = security.ToArray() - }; - } - - return result; - } - } -} diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index c7346789a0..ba9889c413 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Services return type.IsGenericParameter ? "'" + typeName : typeName; } - public static string LeftPart(string strVal, string needle) + private static string LeftPart(string strVal, string needle) { if (strVal == null) return null; var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/Session/FirebaseSessionController.cs b/Emby.Server.Implementations/Session/FirebaseSessionController.cs new file mode 100644 index 0000000000..cfe513305e --- /dev/null +++ b/Emby.Server.Implementations/Session/FirebaseSessionController.cs @@ -0,0 +1,131 @@ +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Net; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Serialization; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Text; +using MediaBrowser.Common; + +namespace Emby.Server.Implementations.Session +{ + public class FirebaseSessionController : ISessionController + { + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; + private readonly ISessionManager _sessionManager; + + public SessionInfo Session { get; private set; } + + private readonly string _token; + + private IApplicationHost _appHost; + private string _senderId; + private string _applicationId; + + public FirebaseSessionController(IHttpClient httpClient, + IApplicationHost appHost, + IJsonSerializer json, + SessionInfo session, + string token, ISessionManager sessionManager) + { + _httpClient = httpClient; + _json = json; + _appHost = appHost; + Session = session; + _token = token; + _sessionManager = sessionManager; + + _applicationId = _appHost.GetValue("firebase_applicationid"); + _senderId = _appHost.GetValue("firebase_senderid"); + } + + public static bool IsSupported(IApplicationHost appHost) + { + return !string.IsNullOrEmpty(appHost.GetValue("firebase_applicationid")) && !string.IsNullOrEmpty(appHost.GetValue("firebase_senderid")); + } + + public bool IsSessionActive + { + get + { + return (DateTime.UtcNow - Session.LastActivityDate).TotalDays <= 3; + } + } + + public bool SupportsMediaControl + { + get { return true; } + } + + public async Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) + { + if (!IsSessionActive) + { + return; + } + + if (string.IsNullOrEmpty(_senderId) || string.IsNullOrEmpty(_applicationId)) + { + return; + } + + foreach (var controller in allControllers) + { + // Don't send if there's an active web socket connection + if ((controller is WebSocketController) && controller.IsSessionActive) + { + return; + } + } + + var msg = new WebSocketMessage + { + Data = data, + MessageType = name, + MessageId = messageId, + ServerId = _appHost.SystemId + }; + + var req = new FirebaseBody + { + to = _token, + data = msg + }; + + var byteArray = Encoding.UTF8.GetBytes(_json.SerializeToString(req)); + + var enableLogging = false; + +#if DEBUG + enableLogging = true; +#endif + + var options = new HttpRequestOptions + { + Url = "https://fcm.googleapis.com/fcm/send", + RequestContentType = "application/json", + RequestContentBytes = byteArray, + CancellationToken = cancellationToken, + LogRequest = enableLogging, + LogResponse = enableLogging, + LogErrors = enableLogging + }; + + options.RequestHeaders["Authorization"] = string.Format("key={0}", _applicationId); + options.RequestHeaders["Sender"] = string.Format("id={0}", _senderId); + + using (var response = await _httpClient.Post(options).ConfigureAwait(false)) + { + + } + } + } + + internal class FirebaseBody + { + public string to { get; set; } + public WebSocketMessage data { get; set; } + } +} diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 6725cd7af6..ff9b3fefc8 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -36,10 +36,6 @@ namespace Emby.Server.Implementations.Session _sessionManager = sessionManager; } - public void OnActivity() - { - } - private string PostUrl { get @@ -52,7 +48,7 @@ namespace Emby.Server.Implementations.Session { get { - return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 10; + return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 5; } } @@ -61,49 +57,29 @@ namespace Emby.Server.Implementations.Session get { return true; } } - private Task SendMessage(string name, CancellationToken cancellationToken) + private Task SendMessage(string name, string messageId, CancellationToken cancellationToken) { - return SendMessage(name, new Dictionary(), cancellationToken); + return SendMessage(name, messageId, new Dictionary(), cancellationToken); } - private async Task SendMessage(string name, - Dictionary args, - CancellationToken cancellationToken) + private Task SendMessage(string name, string messageId, Dictionary args, CancellationToken cancellationToken) { + args["messageId"] = messageId; var url = PostUrl + "/" + name + ToQueryString(args); - using ((await _httpClient.Post(new HttpRequestOptions + return SendRequest(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, BufferContent = false - - }).ConfigureAwait(false))) - { - - } + }); } - public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) + private Task SendPlayCommand(PlayRequest command, string messageId, CancellationToken cancellationToken) { var dict = new Dictionary(); - dict["ItemIds"] = string.Join(",", command.ItemIds); + dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N")).ToArray()); if (command.StartPositionTicks.HasValue) { @@ -121,15 +97,15 @@ namespace Emby.Server.Implementations.Session { dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture); } - if (!string.IsNullOrWhiteSpace(command.MediaSourceId)) + if (!string.IsNullOrEmpty(command.MediaSourceId)) { dict["MediaSourceId"] = command.MediaSourceId; } - return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken); + return SendMessage(command.PlayCommand.ToString(), messageId, dict, cancellationToken); } - public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) + private Task SendPlaystateCommand(PlaystateRequest command, string messageId, CancellationToken cancellationToken) { var args = new Dictionary(); @@ -143,60 +119,74 @@ namespace Emby.Server.Implementations.Session args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture); } - return SendMessage(command.Command.ToString(), args, cancellationToken); + return SendMessage(command.Command.ToString(), messageId, args, cancellationToken); } - public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) + private string[] _supportedMessages = new string[] { }; + public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) { - return SendMessage("LibraryChanged", info, cancellationToken); + if (!IsSessionActive) + { + return Task.CompletedTask; + } + + if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) + { + return SendPlayCommand(data as PlayRequest, messageId, cancellationToken); + } + if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) + { + return SendPlaystateCommand(data as PlaystateRequest, messageId, cancellationToken); + } + if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) + { + var command = data as GeneralCommand; + return SendMessage(command.Name, messageId, command.Arguments, cancellationToken); + } + + if (!_supportedMessages.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + return Task.CompletedTask; + } + + var url = PostUrl + "/" + name; + + url += "?messageId=" + messageId; + + var options = new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + BufferContent = false + }; + + if (data != null) + { + if (typeof(T) == typeof(string)) + { + var str = data as String; + if (!string.IsNullOrEmpty(str)) + { + options.RequestContent = str; + options.RequestContentType = "application/json"; + } + } + else + { + options.RequestContent = _json.SerializeToString(data); + options.RequestContentType = "application/json"; + } + } + + return SendRequest(options); } - public Task SendRestartRequiredNotification(CancellationToken cancellationToken) + private async Task SendRequest(HttpRequestOptions options) { - return SendMessage("RestartRequired", cancellationToken); - } + using (var response = await _httpClient.Post(options).ConfigureAwait(false)) + { - public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendServerShutdownNotification(CancellationToken cancellationToken) - { - return SendMessage("ServerShuttingDown", cancellationToken); - } - - public Task SendServerRestartNotification(CancellationToken cancellationToken) - { - return SendMessage("ServerRestarting", cancellationToken); - } - - public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) - { - return SendMessage(command.Name, command.Arguments, cancellationToken); - } - - public Task SendMessage(string name, T data, CancellationToken cancellationToken) - { - return Task.FromResult(true); - //var url = PostUrl + "/" + name; - - //var options = new HttpRequestOptions - //{ - // Url = url, - // CancellationToken = cancellationToken, - // BufferContent = false - //}; - - //options.RequestContent = _json.SerializeToString(data); - //options.RequestContentType = "application/json"; - - //return _httpClient.Post(new HttpRequestOptions - //{ - // Url = url, - // CancellationToken = cancellationToken, - // BufferContent = false - //}); + } } private string ToQueryString(Dictionary nvc) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6b70f2cda3..9db4f4423a 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -30,13 +30,14 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Threading; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Authentication; namespace Emby.Server.Implementations.Session { /// /// Class SessionManager /// - public class SessionManager : ISessionManager + public class SessionManager : ISessionManager, IDisposable { /// /// The _user data repository @@ -71,7 +72,7 @@ namespace Emby.Server.Implementations.Session public event EventHandler> AuthenticationFailed; - public event EventHandler> AuthenticationSucceeded; + public event EventHandler> AuthenticationSucceeded; /// /// Occurs when [playback start]. @@ -91,10 +92,6 @@ namespace Emby.Server.Implementations.Session public event EventHandler SessionEnded; public event EventHandler SessionActivity; - private IEnumerable _sessionFactories = new List(); - - private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); - public SessionManager(IUserDataManager userDataManager, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, ITimerFactory timerFactory) { _userDataManager = userDataManager; @@ -111,28 +108,41 @@ namespace Emby.Server.Implementations.Session _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; _timerFactory = timerFactory; - _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated; } - void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs e) + private void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs> e) { foreach (var session in Sessions) { - if (string.Equals(session.DeviceId, e.Argument.Id)) + if (string.Equals(session.DeviceId, e.Argument.Item1)) { - session.DeviceName = e.Argument.Name; + if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName)) + { + session.HasCustomDeviceName = true; + session.DeviceName = e.Argument.Item2.CustomName; + } + else + { + session.HasCustomDeviceName = false; + } } } } - /// - /// Adds the parts. - /// - /// The session factories. - public void AddParts(IEnumerable sessionFactories) + private bool _disposed; + public void Dispose() { - _sessionFactories = sessionFactories.ToList(); + _disposed = true; + _deviceManager.DeviceOptionsUpdated -= _deviceManager_DeviceOptionsUpdated; + } + + public void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } } /// @@ -146,60 +156,46 @@ namespace Emby.Server.Implementations.Session private void OnSessionStarted(SessionInfo info) { - EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs - { - SessionInfo = info - - }, _logger); - - if (!string.IsNullOrWhiteSpace(info.DeviceId)) + if (!string.IsNullOrEmpty(info.DeviceId)) { var capabilities = GetSavedCapabilities(info.DeviceId); if (capabilities != null) { - info.AppIconUrl = capabilities.IconUrl; ReportCapabilities(info, capabilities, false); } } + + EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs + { + SessionInfo = info + + }, _logger); } - private async void OnSessionEnded(SessionInfo info) + private void OnSessionEnded(SessionInfo info) { - try - { - await SendSessionEndedNotification(info, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendSessionEndedNotification", ex); - } - EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs { SessionInfo = info }, _logger); - var disposable = info.SessionController as IDisposable; - - if (disposable != null) - { - _logger.Debug("Disposing session controller {0}", disposable.GetType().Name); - - try - { - disposable.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing session controller", ex); - } - } - info.Dispose(); } + public void UpdateDeviceName(string sessionId, string deviceName) + { + var session = GetSession(sessionId); + + var key = GetSessionKey(session.Client, session.DeviceId); + + if (session != null) + { + session.DeviceName = deviceName; + } + } + /// /// Logs the user activity. /// @@ -212,13 +208,15 @@ namespace Emby.Server.Implementations.Session /// Task. /// user /// - public async Task LogSessionActivity(string appName, + public SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) { + CheckDisposed(); + if (string.IsNullOrEmpty(appName)) { throw new ArgumentNullException("appName"); @@ -231,13 +229,9 @@ namespace Emby.Server.Implementations.Session { throw new ArgumentNullException("deviceId"); } - if (string.IsNullOrEmpty(deviceName)) - { - throw new ArgumentNullException("deviceName"); - } var activityDate = DateTime.UtcNow; - var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user); var lastActivityDate = session.LastActivityDate; session.LastActivityDate = activityDate; @@ -268,40 +262,39 @@ namespace Emby.Server.Implementations.Session }, _logger); } - var controller = session.SessionController; - if (controller != null) - { - controller.OnActivity(); - } - return session; } - public async void ReportSessionEnded(string sessionId) + public void CloseIfNeeded(SessionInfo session) { - await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - try + if (!session.SessionControllers.Any(i => i.IsSessionActive)) { - var session = GetSession(sessionId, false); + var key = GetSessionKey(session.Client, session.DeviceId); - if (session != null) - { - var key = GetSessionKey(session.Client, session.DeviceId); + SessionInfo removed; + _activeConnections.TryRemove(key, out removed); - SessionInfo removed; - _activeConnections.TryRemove(key, out removed); - - OnSessionEnded(session); - } - } - finally - { - _sessionLock.Release(); + OnSessionEnded(session); } } - private Task GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId) + public void ReportSessionEnded(string sessionId) + { + CheckDisposed(); + var session = GetSession(sessionId, false); + + if (session != null) + { + var key = GetSessionKey(session.Client, session.DeviceId); + + SessionInfo removed; + _activeConnections.TryRemove(key, out removed); + + OnSessionEnded(session); + } + } + + private Task GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId) { return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None); } @@ -311,16 +304,16 @@ namespace Emby.Server.Implementations.Session /// private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime) { - if (string.IsNullOrWhiteSpace(info.MediaSourceId)) + if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId; + info.MediaSourceId = info.ItemId.ToString("N"); } - if (!string.IsNullOrWhiteSpace(info.ItemId) && info.Item == null && libraryItem != null) + if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) { var current = session.NowPlayingItem; - if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) + if (current == null || !info.ItemId.Equals(current.Id)) { var runtimeTicks = libraryItem.RunTimeTicks; @@ -328,7 +321,7 @@ namespace Emby.Server.Implementations.Session var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); + mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); if (mediaSource != null) { @@ -364,6 +357,14 @@ namespace Emby.Server.Implementations.Session session.PlayState.SubtitleStreamIndex = info.SubtitleStreamIndex; session.PlayState.PlayMethod = info.PlayMethod; session.PlayState.RepeatMode = info.RepeatMode; + session.PlaylistItemId = info.PlaylistItemId; + + var nowPlayingQueue = info.NowPlayingQueue; + + if (nowPlayingQueue != null) + { + session.NowPlayingQueue = nowPlayingQueue; + } } /// @@ -397,99 +398,89 @@ namespace Emby.Server.Implementations.Session /// The remote end point. /// The user. /// SessionInfo. - private async Task GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + private SessionInfo GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) { - if (string.IsNullOrWhiteSpace(deviceId)) + CheckDisposed(); + + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException("deviceId"); } var key = GetSessionKey(appName, deviceId); - await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + CheckDisposed(); + + SessionInfo sessionInfo = _activeConnections.GetOrAdd(key, k => + { + return CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user); + }); + + sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserName = user == null ? null : user.Name; + sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.RemoteEndPoint = remoteEndPoint; + sessionInfo.Client = appName; + + if (!sessionInfo.HasCustomDeviceName || string.IsNullOrEmpty(sessionInfo.DeviceName)) + { + sessionInfo.DeviceName = deviceName; + } + + sessionInfo.ApplicationVersion = appVersion; + + if (user == null) + { + sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; + } + + return sessionInfo; + } + + private SessionInfo CreateSession(string key, string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + { + var sessionInfo = new SessionInfo(this, _logger) + { + Client = appName, + DeviceId = deviceId, + ApplicationVersion = appVersion, + Id = key.GetMD5().ToString("N"), + ServerId = _appHost.SystemId + }; - var userId = user == null ? (Guid?)null : user.Id; var username = user == null ? null : user.Name; - try + sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserName = username; + sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.RemoteEndPoint = remoteEndPoint; + + if (string.IsNullOrEmpty(deviceName)) { - SessionInfo sessionInfo; - DeviceInfo device = null; - - if (!_activeConnections.TryGetValue(key, out sessionInfo)) - { - sessionInfo = new SessionInfo(this, _logger) - { - Client = appName, - DeviceId = deviceId, - ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N") - }; - - sessionInfo.DeviceName = deviceName; - sessionInfo.UserId = userId; - sessionInfo.UserName = username; - sessionInfo.RemoteEndPoint = remoteEndPoint; - - OnSessionStarted(sessionInfo); - - _activeConnections.TryAdd(key, sessionInfo); - - if (!string.IsNullOrEmpty(deviceId)) - { - var userIdString = userId.HasValue ? userId.Value.ToString("N") : null; - device = _deviceManager.RegisterDevice(deviceId, deviceName, appName, appVersion, userIdString); - } - } - - device = device ?? _deviceManager.GetDevice(deviceId); - - if (device == null) - { - var userIdString = userId.HasValue ? userId.Value.ToString("N") : null; - device = _deviceManager.RegisterDevice(deviceId, deviceName, appName, appVersion, userIdString); - } - - if (device != null) - { - if (!string.IsNullOrEmpty(device.CustomName)) - { - deviceName = device.CustomName; - } - } + deviceName = "Network Device"; + } + var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + if (string.IsNullOrEmpty(deviceOptions.CustomName)) + { sessionInfo.DeviceName = deviceName; - sessionInfo.UserId = userId; - sessionInfo.UserName = username; - sessionInfo.RemoteEndPoint = remoteEndPoint; - sessionInfo.ApplicationVersion = appVersion; - - if (!userId.HasValue) - { - sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; - } - - if (sessionInfo.SessionController == null) - { - sessionInfo.SessionController = _sessionFactories - .Select(i => i.GetSessionController(sessionInfo)) - .FirstOrDefault(i => i != null); - } - - return sessionInfo; } - finally + else { - _sessionLock.Release(); + sessionInfo.DeviceName = deviceOptions.CustomName; + sessionInfo.HasCustomDeviceName = true; } + + OnSessionStarted(sessionInfo); + return sessionInfo; } private List GetUsers(SessionInfo session) { var users = new List(); - if (session.UserId.HasValue) + if (!session.UserId.Equals(Guid.Empty)) { - var user = _userManager.GetUserById(session.UserId.Value); + var user = _userManager.GetUserById(session.UserId); if (user == null) { @@ -498,11 +489,9 @@ namespace Emby.Server.Implementations.Session users.Add(user); - var additionalUsers = session.AdditionalUsers + users.AddRange(session.AdditionalUsers .Select(i => _userManager.GetUserById(i.UserId)) - .Where(i => i != null); - - users.AddRange(additionalUsers); + .Where(i => i != null)); } return users; @@ -546,7 +535,7 @@ namespace Emby.Server.Implementations.Session await OnPlaybackStopped(new PlaybackStopInfo { Item = session.NowPlayingItem, - ItemId = session.NowPlayingItem == null ? null : session.NowPlayingItem.Id, + ItemId = session.NowPlayingItem == null ? Guid.Empty : session.NowPlayingItem.Id, SessionId = session.Id, MediaSourceId = session.PlayState == null ? null : session.PlayState.MediaSourceId, PositionTicks = session.PlayState == null ? null : session.PlayState.PositionTicks @@ -568,12 +557,10 @@ namespace Emby.Server.Implementations.Session } } - private BaseItem GetNowPlayingItem(SessionInfo session, string itemId) + private BaseItem GetNowPlayingItem(SessionInfo session, Guid itemId) { - var idGuid = new Guid(itemId); - var item = session.FullNowPlayingItem; - if (item != null && item.Id == idGuid) + if (item != null && item.Id.Equals(itemId)) { return item; } @@ -593,6 +580,8 @@ namespace Emby.Server.Implementations.Session /// info public async Task OnPlaybackStart(PlaybackStartInfo info) { + CheckDisposed(); + if (info == null) { throw new ArgumentNullException("info"); @@ -600,7 +589,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) + var libraryItem = info.ItemId.Equals(Guid.Empty) ? null : GetNowPlayingItem(session, info.ItemId); @@ -619,7 +608,7 @@ namespace Emby.Server.Implementations.Session { foreach (var user in users) { - OnPlaybackStart(user.Id, libraryItem); + OnPlaybackStart(user, libraryItem); } } @@ -633,12 +622,11 @@ namespace Emby.Server.Implementations.Session MediaInfo = info.Item, DeviceName = session.DeviceName, ClientName = session.Client, - DeviceId = session.DeviceId + DeviceId = session.DeviceId, + Session = session }, _logger); - await SendPlaybackStartNotification(session, CancellationToken.None).ConfigureAwait(false); - StartIdleCheckTimer(); } @@ -647,9 +635,9 @@ namespace Emby.Server.Implementations.Session /// /// The user identifier. /// The item. - private void OnPlaybackStart(Guid userId, IHasUserData item) + private void OnPlaybackStart(User user, BaseItem item) { - var data = _userDataManager.GetUserData(userId, item); + var data = _userDataManager.GetUserData(user, item); data.PlayCount++; data.LastPlayedDate = DateTime.UtcNow; @@ -666,7 +654,7 @@ namespace Emby.Server.Implementations.Session data.Played = false; } - _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None); + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None); } public Task OnPlaybackProgress(PlaybackProgressInfo info) @@ -679,6 +667,8 @@ namespace Emby.Server.Implementations.Session /// public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) { + CheckDisposed(); + if (info == null) { throw new ArgumentNullException("info"); @@ -686,7 +676,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) + var libraryItem = info.ItemId.Equals(Guid.Empty) ? null : GetNowPlayingItem(session, info.ItemId); @@ -694,7 +684,8 @@ namespace Emby.Server.Implementations.Session var users = GetUsers(session); - if (libraryItem != null) + // only update saved user data on actual check-ins, not automated ones + if (libraryItem != null && !isAutomated) { foreach (var user in users) { @@ -714,7 +705,8 @@ namespace Emby.Server.Implementations.Session DeviceId = session.DeviceId, IsPaused = info.IsPaused, PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated + IsAutomated = isAutomated, + Session = session }, _logger); @@ -728,39 +720,70 @@ namespace Emby.Server.Implementations.Session private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) { - var data = _userDataManager.GetUserData(user.Id, item); + var data = _userDataManager.GetUserData(user, item); var positionTicks = info.PositionTicks; + var changed = false; + if (positionTicks.HasValue) { _userDataManager.UpdatePlayState(item, data, positionTicks.Value); - - UpdatePlaybackSettings(user, info, data); - - _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None); + changed = true; } + + var tracksChanged = UpdatePlaybackSettings(user, info, data); + if (!tracksChanged) + { + changed = true; + } + + if (changed) + { + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None); + } + } - private void UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) + private bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) { + var changed = false; + if (user.Configuration.RememberAudioSelections) { - data.AudioStreamIndex = info.AudioStreamIndex; + if (data.AudioStreamIndex != info.AudioStreamIndex) + { + data.AudioStreamIndex = info.AudioStreamIndex; + changed = true; + } } else { - data.AudioStreamIndex = null; + if (data.AudioStreamIndex.HasValue) + { + data.AudioStreamIndex = null; + changed = true; + } } if (user.Configuration.RememberSubtitleSelections) { - data.SubtitleStreamIndex = info.SubtitleStreamIndex; + if (data.SubtitleStreamIndex != info.SubtitleStreamIndex) + { + data.SubtitleStreamIndex = info.SubtitleStreamIndex; + changed = true; + } } else { - data.SubtitleStreamIndex = null; + if (data.SubtitleStreamIndex.HasValue) + { + data.SubtitleStreamIndex = null; + changed = true; + } } + + return changed; } /// @@ -772,6 +795,8 @@ namespace Emby.Server.Implementations.Session /// positionTicks public async Task OnPlaybackStopped(PlaybackStopInfo info) { + CheckDisposed(); + if (info == null) { throw new ArgumentNullException("info"); @@ -786,28 +811,28 @@ namespace Emby.Server.Implementations.Session session.StopAutomaticProgress(); - var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) + var libraryItem = info.ItemId.Equals(Guid.Empty) ? null : GetNowPlayingItem(session, info.ItemId); // Normalize - if (string.IsNullOrWhiteSpace(info.MediaSourceId)) + if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId; + info.MediaSourceId = info.ItemId.ToString("N"); } - if (!string.IsNullOrWhiteSpace(info.ItemId) && info.Item == null && libraryItem != null) + if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) { var current = session.NowPlayingItem; - if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) + if (current == null || !info.ItemId.Equals(current.Id)) { MediaSourceInfo mediaSource = null; var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); + mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } info.Item = GetItemInfo(libraryItem, mediaSource); @@ -829,6 +854,13 @@ namespace Emby.Server.Implementations.Session msString); } + if (info.NowPlayingQueue != null) + { + session.NowPlayingQueue = info.NowPlayingQueue; + } + + session.PlaylistItemId = info.PlaylistItemId; + RemoveNowPlayingItem(session); var users = GetUsers(session); @@ -838,11 +870,11 @@ namespace Emby.Server.Implementations.Session { foreach (var user in users) { - playedToCompletion = OnPlaybackStopped(user.Id, libraryItem, info.PositionTicks, info.Failed); + playedToCompletion = OnPlaybackStopped(user, libraryItem, info.PositionTicks, info.Failed); } } - if (!string.IsNullOrWhiteSpace(info.LiveStreamId)) + if (!string.IsNullOrEmpty(info.LiveStreamId)) { try { @@ -864,20 +896,19 @@ namespace Emby.Server.Implementations.Session MediaInfo = info.Item, DeviceName = session.DeviceName, ClientName = session.Client, - DeviceId = session.DeviceId + DeviceId = session.DeviceId, + Session = session }, _logger); - - await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false); } - private bool OnPlaybackStopped(Guid userId, BaseItem item, long? positionTicks, bool playbackFailed) + private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) { bool playedToCompletion = false; if (!playbackFailed) { - var data = _userDataManager.GetUserData(userId, item); + var data = _userDataManager.GetUserData(user, item); if (positionTicks.HasValue) { @@ -892,7 +923,7 @@ namespace Emby.Server.Implementations.Session playedToCompletion = true; } - _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None); + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None); } return playedToCompletion; @@ -932,6 +963,8 @@ namespace Emby.Server.Implementations.Session public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { + CheckDisposed(); + var generalCommand = new GeneralCommand { Name = GeneralCommandType.DisplayMessage.ToString() @@ -950,22 +983,37 @@ namespace Emby.Server.Implementations.Session public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken) { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); - if (!string.IsNullOrWhiteSpace(controllingSessionId)) + if (!string.IsNullOrEmpty(controllingSessionId)) { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); } - return session.SessionController.SendGeneralCommand(command, cancellationToken); + return SendMessageToSession(session, "GeneralCommand", command, cancellationToken); + } + + private async Task SendMessageToSession(SessionInfo session, string name, T data, CancellationToken cancellationToken) + { + var controllers = session.SessionControllers.ToArray(); + var messageId = Guid.NewGuid().ToString("N"); + + foreach (var controller in controllers) + { + await controller.SendMessage(name, messageId, data, controllers, cancellationToken).ConfigureAwait(false); + } } public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); - var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null; + var user = !session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(session.UserId) : null; List items; @@ -994,7 +1042,7 @@ namespace Emby.Server.Implementations.Session command.PlayCommand = PlayCommand.PlayNow; } - command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray(items.Count); + command.ItemIds = items.Select(i => i.Id).ToArray(items.Count); if (user != null) { @@ -1004,11 +1052,6 @@ namespace Emby.Server.Implementations.Session } } - if (items.Any(i => !session.PlayableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("{0} is unable to play the requested media type.", session.DeviceName ?? session.Id)); - } - if (user != null && command.ItemIds.Length == 1 && user.Configuration.EnableNextEpisodeAutoPlay) { var episode = _libraryManager.GetItemById(command.ItemIds[0]) as Episode; @@ -1027,26 +1070,26 @@ namespace Emby.Server.Implementations.Session if (episodes.Count > 0) { - command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray(episodes.Count); + command.ItemIds = episodes.Select(i => i.Id).ToArray(episodes.Count); } } } } - if (!string.IsNullOrWhiteSpace(controllingSessionId)) + if (!string.IsNullOrEmpty(controllingSessionId)) { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (controllingSession.UserId.HasValue) + if (!controllingSession.UserId.Equals(Guid.Empty)) { - command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + command.ControllingUserId = controllingSession.UserId; } } - await session.SessionController.SendPlayCommand(command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private List TranslateItemForPlayback(string id, User user) + private IList TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); @@ -1060,7 +1103,7 @@ namespace Emby.Server.Implementations.Session if (byName != null) { - var items = byName.GetTaggedItems(new InternalItemsQuery(user) + return byName.GetTaggedItems(new InternalItemsQuery(user) { IsFolder = false, Recursive = true, @@ -1072,19 +1115,16 @@ namespace Emby.Server.Implementations.Session ItemFields.SortName } }, - IsVirtualItem = false + IsVirtualItem = false, + OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } }); - - return FilterToSingleMediaType(items) - .OrderBy(i => i.SortName) - .ToList(); } if (item.IsFolder) { var folder = (Folder)item; - var itemsResult = folder.GetItemList(new InternalItemsQuery(user) + return folder.GetItemList(new InternalItemsQuery(user) { Recursive = true, IsFolder = false, @@ -1096,28 +1136,16 @@ namespace Emby.Server.Implementations.Session ItemFields.SortName } }, - IsVirtualItem = false + IsVirtualItem = false, + OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } }); - - return FilterToSingleMediaType(itemsResult) - .OrderBy(i => i.SortName) - .ToList(); } return new List { item }; } - private IEnumerable FilterToSingleMediaType(IEnumerable items) - { - return items - .Where(i => !string.IsNullOrWhiteSpace(i.MediaType)) - .ToLookup(i => i.MediaType, StringComparer.OrdinalIgnoreCase) - .OrderByDescending(i => i.Count()) - .FirstOrDefault(); - } - - private IEnumerable TranslateItemForInstantMix(string id, User user) + private IEnumerable TranslateItemForInstantMix(Guid id, User user) { var item = _libraryManager.GetItemById(id); @@ -1146,19 +1174,21 @@ namespace Emby.Server.Implementations.Session public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken) { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); - if (!string.IsNullOrWhiteSpace(controllingSessionId)) + if (!string.IsNullOrEmpty(controllingSessionId)) { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (controllingSession.UserId.HasValue) + if (!controllingSession.UserId.Equals(Guid.Empty)) { - command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + command.ControllingUserId = controllingSession.UserId.ToString("N"); } } - return session.SessionController.SendPlaystateCommand(command, cancellationToken); + return SendMessageToSession(session, "Playstate", command, cancellationToken); } private void AssertCanControl(SessionInfo session, SessionInfo controllingSession) @@ -1180,20 +1210,22 @@ namespace Emby.Server.Implementations.Session /// Task. public async Task SendRestartRequiredNotification(CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + CheckDisposed(); + + var sessions = Sessions.ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendRestartRequiredNotification(cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "RestartRequired", string.Empty, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in SendRestartRequiredNotification.", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -1205,20 +1237,22 @@ namespace Emby.Server.Implementations.Session /// Task. public Task SendServerShutdownNotification(CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + CheckDisposed(); + + var sessions = Sessions.ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendServerShutdownNotification(cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "ServerShuttingDown", string.Empty, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in SendServerShutdownNotification.", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } @@ -1230,85 +1264,24 @@ namespace Emby.Server.Implementations.Session /// Task. public Task SendServerRestartNotification(CancellationToken cancellationToken) { + CheckDisposed(); + _logger.Debug("Beginning SendServerRestartNotification"); - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + var sessions = Sessions.ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendServerRestartNotification(cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "ServerRestarting", string.Empty, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in SendServerRestartNotification.", ex); } - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - public Task SendSessionEndedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken) - { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var dto = GetSessionInfoDto(sessionInfo); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await session.SessionController.SendSessionEndedNotification(dto, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendSessionEndedNotification.", ex); - } - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - public Task SendPlaybackStartNotification(SessionInfo sessionInfo, CancellationToken cancellationToken) - { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var dto = GetSessionInfoDto(sessionInfo); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await session.SessionController.SendPlaybackStartNotification(dto, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendPlaybackStartNotification.", ex); - } - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - public Task SendPlaybackStoppedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken) - { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var dto = GetSessionInfoDto(sessionInfo); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await session.SessionController.SendPlaybackStoppedNotification(dto, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendPlaybackStoppedNotification.", ex); - } - - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } @@ -1320,16 +1293,18 @@ namespace Emby.Server.Implementations.Session /// The user identifier. /// Cannot modify additional users without authenticating first. /// The requested user is already the primary user of the session. - public void AddAdditionalUser(string sessionId, string userId) + public void AddAdditionalUser(string sessionId, Guid userId) { + CheckDisposed(); + var session = GetSession(sessionId); - if (session.UserId.HasValue && session.UserId.Value == new Guid(userId)) + if (session.UserId.Equals(userId)) { throw new ArgumentException("The requested user is already the primary user of the session."); } - if (session.AdditionalUsers.All(i => new Guid(i.UserId) != new Guid(userId))) + if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId))) { var user = _userManager.GetUserById(userId); @@ -1352,16 +1327,18 @@ namespace Emby.Server.Implementations.Session /// The user identifier. /// Cannot modify additional users without authenticating first. /// The requested user is already the primary user of the session. - public void RemoveAdditionalUser(string sessionId, string userId) + public void RemoveAdditionalUser(string sessionId, Guid userId) { + CheckDisposed(); + var session = GetSession(sessionId); - if (session.UserId.HasValue && session.UserId.Value == new Guid(userId)) + if (session.UserId.Equals(userId)) { throw new ArgumentException("The requested user is already the primary user of the session."); } - var user = session.AdditionalUsers.FirstOrDefault(i => new Guid(i.UserId) == new Guid(userId)); + var user = session.AdditionalUsers.FirstOrDefault(i => i.UserId.Equals(userId)); if (user != null) { @@ -1389,12 +1366,13 @@ namespace Emby.Server.Implementations.Session private async Task AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) { + CheckDisposed(); + User user = null; - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { - var idGuid = new Guid(request.UserId); user = _userManager.Users - .FirstOrDefault(i => i.Id == idGuid); + .FirstOrDefault(i => i.Id == request.UserId); } if (user == null) @@ -1405,14 +1383,10 @@ namespace Emby.Server.Implementations.Session if (user != null) { - if (!user.IsParentalScheduleAllowed()) + // TODO: Move this to userManager? + if (!string.IsNullOrEmpty(request.DeviceId)) { - throw new SecurityException("User is not allowed access at this time."); - } - - if (!string.IsNullOrWhiteSpace(request.DeviceId)) - { - if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), request.DeviceId)) + if (!_deviceManager.CanAccessDevice(user, request.DeviceId)) { throw new SecurityException("User is not allowed access from this device."); } @@ -1421,7 +1395,7 @@ namespace Emby.Server.Implementations.Session if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint, true).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.RemoteEndPoint, true).ConfigureAwait(false); if (result == null) { @@ -1433,72 +1407,95 @@ namespace Emby.Server.Implementations.Session user = result; } - var token = GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName); + var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs(request), _logger); - - var session = await LogSessionActivity(request.App, + var session = LogSessionActivity(request.App, request.AppVersion, request.DeviceId, request.DeviceName, request.RemoteEndPoint, - user) - .ConfigureAwait(false); + user); - return new AuthenticationResult + var returnResult = new AuthenticationResult { User = _userManager.GetUserDto(user, request.RemoteEndPoint), - SessionInfo = GetSessionInfoDto(session), + SessionInfo = session, AccessToken = token, ServerId = _appHost.SystemId }; + + EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs(returnResult), _logger); + + return returnResult; } - - private string GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName) + private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { var existing = _authRepo.Get(new AuthenticationInfoQuery { DeviceId = deviceId, - IsActive = true, - UserId = userId, + UserId = user.Id, Limit = 1 - }); - if (existing.Items.Length > 0) + }).Items.FirstOrDefault(); + + var allExistingForDevice = _authRepo.Get(new AuthenticationInfoQuery { - var token = existing.Items[0].AccessToken; - _logger.Info("Reissuing access token: " + token); - return token; + DeviceId = deviceId + + }).Items; + + foreach (var auth in allExistingForDevice) + { + if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) + { + try + { + Logout(auth); + } + catch + { + + } + } } + if (existing != null) + { + _logger.Info("Reissuing access token: " + existing.AccessToken); + return existing.AccessToken; + } + + var now = DateTime.UtcNow; + var newToken = new AuthenticationInfo { AppName = app, AppVersion = appVersion, - DateCreated = DateTime.UtcNow, + DateCreated = now, + DateLastActivity = now, DeviceId = deviceId, DeviceName = deviceName, - UserId = userId, - IsActive = true, - AccessToken = Guid.NewGuid().ToString("N") + UserId = user.Id, + AccessToken = Guid.NewGuid().ToString("N"), + UserName = user.Name }; - _logger.Info("Creating new access token for user {0}", userId); - _authRepo.Create(newToken, CancellationToken.None); + _logger.Info("Creating new access token for user {0}", user.Id); + _authRepo.Create(newToken); return newToken.AccessToken; } public void Logout(string accessToken) { - if (string.IsNullOrWhiteSpace(accessToken)) + CheckDisposed(); + + if (string.IsNullOrEmpty(accessToken)) { throw new ArgumentNullException("accessToken"); } - _logger.Info("Logging out access token {0}", accessToken); - var existing = _authRepo.Get(new AuthenticationInfoQuery { Limit = 1, @@ -1508,33 +1505,41 @@ namespace Emby.Server.Implementations.Session if (existing != null) { - existing.IsActive = false; + Logout(existing); + } + } - _authRepo.Update(existing, CancellationToken.None); + public void Logout(AuthenticationInfo existing) + { + CheckDisposed(); - var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); + _logger.Info("Logging out access token {0}", existing.AccessToken); - foreach (var session in sessions) + _authRepo.Delete(existing); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var session in sessions) + { + try { - try - { - ReportSessionEnded(session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error reporting session ended", ex); - } + ReportSessionEnded(session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error reporting session ended", ex); } } } - public void RevokeUserTokens(string userId, string currentAccessToken) + public void RevokeUserTokens(Guid userId, string currentAccessToken) { + CheckDisposed(); + var existing = _authRepo.Get(new AuthenticationInfoQuery { - IsActive = true, UserId = userId }); @@ -1542,7 +1547,7 @@ namespace Emby.Server.Implementations.Session { if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase)) { - Logout(info.AccessToken); + Logout(info); } } } @@ -1559,6 +1564,8 @@ namespace Emby.Server.Implementations.Session /// The capabilities. public void ReportCapabilities(string sessionId, ClientCapabilities capabilities) { + CheckDisposed(); + var session = GetSession(sessionId); ReportCapabilities(session, capabilities, true); @@ -1570,24 +1577,22 @@ namespace Emby.Server.Implementations.Session { session.Capabilities = capabilities; - if (!string.IsNullOrWhiteSpace(capabilities.MessageCallbackUrl)) + if (!string.IsNullOrEmpty(capabilities.PushToken)) { - var controller = session.SessionController as HttpSessionController; - - if (controller == null) + if (string.Equals(capabilities.PushTokenType, "firebase", StringComparison.OrdinalIgnoreCase) && FirebaseSessionController.IsSupported(_appHost)) { - session.SessionController = new HttpSessionController(_httpClient, _jsonSerializer, session, capabilities.MessageCallbackUrl, this); + EnsureFirebaseController(session, capabilities.PushToken); } } - EventHelper.FireEventIfNotNull(CapabilitiesChanged, this, new SessionEventArgs - { - SessionInfo = session - - }, _logger); - if (saveCapabilities) { + EventHelper.FireEventIfNotNull(CapabilitiesChanged, this, new SessionEventArgs + { + SessionInfo = session + + }, _logger); + try { SaveCapabilities(session.DeviceId, capabilities); @@ -1599,6 +1604,11 @@ namespace Emby.Server.Implementations.Session } } + private void EnsureFirebaseController(SessionInfo session, string token) + { + session.EnsureController(s => new FirebaseSessionController(_httpClient, _appHost, _jsonSerializer, s, token, this)); + } + private ClientCapabilities GetSavedCapabilities(string deviceId) { return _deviceManager.GetCapabilities(deviceId); @@ -1609,45 +1619,6 @@ namespace Emby.Server.Implementations.Session _deviceManager.SaveCapabilities(deviceId, capabilities); } - public SessionInfoDto GetSessionInfoDto(SessionInfo session) - { - var dto = new SessionInfoDto - { - Client = session.Client, - DeviceId = session.DeviceId, - DeviceName = session.DeviceName, - Id = session.Id, - LastActivityDate = session.LastActivityDate, - NowViewingItem = session.NowViewingItem, - ApplicationVersion = session.ApplicationVersion, - PlayableMediaTypes = session.PlayableMediaTypes, - AdditionalUsers = session.AdditionalUsers, - SupportedCommands = session.SupportedCommands, - UserName = session.UserName, - NowPlayingItem = session.NowPlayingItem, - SupportsRemoteControl = session.SupportsMediaControl, - PlayState = session.PlayState, - AppIconUrl = session.AppIconUrl, - TranscodingInfo = session.NowPlayingItem == null ? null : session.TranscodingInfo - }; - - dto.ServerId = _appHost.SystemId; - - if (session.UserId.HasValue) - { - dto.UserId = session.UserId.Value.ToString("N"); - - var user = _userManager.GetUserById(session.UserId.Value); - - if (user != null) - { - dto.UserPrimaryImageTag = GetImageCacheTag(user, ImageType.Primary); - } - } - - return dto; - } - private DtoOptions _itemInfoDtoOptions; /// @@ -1672,7 +1643,6 @@ namespace Emby.Server.Implementations.Session var fields = dtoOptions.Fields.ToList(); fields.Remove(ItemFields.BasicSyncInfo); - fields.Remove(ItemFields.SyncInfo); fields.Remove(ItemFields.CanDelete); fields.Remove(ItemFields.CanDownload); fields.Remove(ItemFields.ChildCount); @@ -1682,7 +1652,6 @@ namespace Emby.Server.Implementations.Session fields.Remove(ItemFields.DateLastSaved); fields.Remove(ItemFields.DisplayPreferencesId); fields.Remove(ItemFields.Etag); - fields.Remove(ItemFields.ExternalEtag); fields.Remove(ItemFields.InheritedParentalRatingValue); fields.Remove(ItemFields.ItemCounts); fields.Remove(ItemFields.MediaSourceCount); @@ -1698,8 +1667,7 @@ namespace Emby.Server.Implementations.Session fields.Remove(ItemFields.Settings); fields.Remove(ItemFields.SortName); fields.Remove(ItemFields.Tags); - fields.Remove(ItemFields.ThemeSongIds); - fields.Remove(ItemFields.ThemeVideoIds); + fields.Remove(ItemFields.ExtraIds); dtoOptions.Fields = fields.ToArray(fields.Count); @@ -1729,14 +1697,9 @@ namespace Emby.Server.Implementations.Session } } - private string GetDtoId(BaseItem item) - { - return _dtoService.GetDtoId(item); - } - public void ReportNowViewingItem(string sessionId, string itemId) { - if (string.IsNullOrWhiteSpace(itemId)) + if (string.IsNullOrEmpty(itemId)) { throw new ArgumentNullException("itemId"); } @@ -1776,46 +1739,31 @@ namespace Emby.Server.Implementations.Session string.Equals(i.Client, client)); } - public Task GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) + public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) { throw new ArgumentNullException("info"); } - var user = string.IsNullOrWhiteSpace(info.UserId) + var user = info.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(info.UserId); - appVersion = string.IsNullOrWhiteSpace(appVersion) + appVersion = string.IsNullOrEmpty(appVersion) ? info.AppVersion : appVersion; var deviceName = info.DeviceName; var appName = info.AppName; - if (!string.IsNullOrWhiteSpace(deviceId)) - { - // Replace the info from the token with more recent info - var device = _deviceManager.GetDevice(deviceId); - if (device != null) - { - deviceName = device.Name; - appName = device.AppName; - - if (!string.IsNullOrWhiteSpace(device.AppVersion)) - { - appVersion = device.AppVersion; - } - } - } - else + if (string.IsNullOrEmpty(deviceId)) { deviceId = info.DeviceId; } // Prevent argument exception - if (string.IsNullOrWhiteSpace(appVersion)) + if (string.IsNullOrEmpty(appVersion)) { appVersion = "1"; } @@ -1823,7 +1771,7 @@ namespace Emby.Server.Implementations.Session return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user); } - public Task GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { var result = _authRepo.Get(new AuthenticationInfoQuery { @@ -1834,7 +1782,7 @@ namespace Emby.Server.Implementations.Session if (info == null) { - return Task.FromResult(null); + return null; } return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null); @@ -1842,51 +1790,115 @@ namespace Emby.Server.Implementations.Session public Task SendMessageToAdminSessions(string name, T data, CancellationToken cancellationToken) { - var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList(); + CheckDisposed(); + + var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToList(); return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); } - public Task SendMessageToUserSessions(List userIds, string name, T data, - CancellationToken cancellationToken) + public Task SendMessageToUserSessions(List userIds, string name, Func dataFn, CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && userIds.Any(i.ContainsUser)).ToList(); + CheckDisposed(); + + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); + + if (sessions.Count == 0) + { + return Task.CompletedTask; + } + + var data = dataFn(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendMessage(name, data, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error sending message", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } - public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, - CancellationToken cancellationToken) + public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); + CheckDisposed(); + + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendMessage(name, data, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error sending message", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } + + public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); + + var tasks = sessions.Select(session => Task.Run(async () => + { + try + { + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); + } + + }, cancellationToken)).ToArray(); + + return Task.WhenAll(tasks); + } + + public Task SendMessageToUserDeviceAndAdminSessions(string deviceId, string name, T data, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)) + .ToList(); + + var tasks = sessions.Select(session => Task.Run(async () => + { + try + { + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); + } + + }, cancellationToken)).ToArray(); + + return Task.WhenAll(tasks); + } + + private bool IsAdminSession(SessionInfo s) + { + var user = _userManager.GetUserById(s.UserId); + + return user != null && user.Policy.IsAdministrator; + } } } \ No newline at end of file diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5af843dbd..9ab4753fb9 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -18,11 +18,6 @@ namespace Emby.Server.Implementations.Session /// public class SessionWebSocketListener : IWebSocketListener, IDisposable { - /// - /// The _true task result - /// - private readonly Task _trueTaskResult = Task.FromResult(true); - /// /// The _session manager /// @@ -39,7 +34,6 @@ namespace Emby.Server.Implementations.Session private readonly IJsonSerializer _json; private readonly IHttpServer _httpServer; - private readonly IServerManager _serverManager; /// @@ -50,32 +44,22 @@ namespace Emby.Server.Implementations.Session /// The json. /// The HTTP server. /// The server manager. - public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json, IHttpServer httpServer, IServerManager serverManager) + public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json, IHttpServer httpServer) { _sessionManager = sessionManager; _logger = logManager.GetLogger(GetType().Name); _json = json; _httpServer = httpServer; - _serverManager = serverManager; - serverManager.WebSocketConnected += _serverManager_WebSocketConnected; + httpServer.WebSocketConnected += _serverManager_WebSocketConnected; } - async void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) { - var session = await GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint).ConfigureAwait(false); + var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); if (session != null) { - var controller = session.SessionController as WebSocketController; - - if (controller == null) - { - controller = new WebSocketController(session, _logger, _sessionManager); - } - - controller.AddWebSocket(e.Argument); - - session.SessionController = controller; + EnsureController(session, e.Argument); } else { @@ -83,7 +67,7 @@ namespace Emby.Server.Implementations.Session } } - private Task GetSession(QueryParamCollection queryString, string remoteEndpoint) + private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint) { if (queryString == null) { @@ -93,7 +77,7 @@ namespace Emby.Server.Implementations.Session var token = queryString["api_key"]; if (string.IsNullOrWhiteSpace(token)) { - return Task.FromResult(null); + return null; } var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); @@ -101,8 +85,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { - _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; - GC.SuppressFinalize(this); + _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; } /// @@ -112,350 +95,15 @@ namespace Emby.Server.Implementations.Session /// Task. public Task ProcessMessage(WebSocketMessageInfo message) { - if (string.Equals(message.MessageType, "Identity", StringComparison.OrdinalIgnoreCase)) - { - ProcessIdentityMessage(message); - } - else if (string.Equals(message.MessageType, "Context", StringComparison.OrdinalIgnoreCase)) - { - ProcessContextMessage(message); - } - else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase)) - { - OnPlaybackStart(message); - } - else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase)) - { - OnPlaybackProgress(message); - } - else if (string.Equals(message.MessageType, "PlaybackStopped", StringComparison.OrdinalIgnoreCase)) - { - OnPlaybackStopped(message); - } - else if (string.Equals(message.MessageType, "ReportPlaybackStart", StringComparison.OrdinalIgnoreCase)) - { - ReportPlaybackStart(message); - } - else if (string.Equals(message.MessageType, "ReportPlaybackProgress", StringComparison.OrdinalIgnoreCase)) - { - ReportPlaybackProgress(message); - } - else if (string.Equals(message.MessageType, "ReportPlaybackStopped", StringComparison.OrdinalIgnoreCase)) - { - ReportPlaybackStopped(message); - } - - return _trueTaskResult; + return Task.CompletedTask; } - /// - /// Processes the identity message. - /// - /// The message. - private async void ProcessIdentityMessage(WebSocketMessageInfo message) + private void EnsureController(SessionInfo session, IWebSocketConnection connection) { - _logger.Debug("Received Identity message: " + message.Data); + var controllerInfo = session.EnsureController(s => new WebSocketController(s, _logger, _sessionManager)); - var vals = message.Data.Split('|'); - - if (vals.Length < 3) - { - _logger.Error("Client sent invalid identity message."); - return; - } - - var client = vals[0]; - var deviceId = vals[1]; - var version = vals[2]; - var deviceName = vals.Length > 3 ? vals[3] : string.Empty; - - var session = _sessionManager.GetSession(deviceId, client, version); - - if (session == null && !string.IsNullOrEmpty(deviceName)) - { - _logger.Debug("Logging session activity"); - - session = await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false); - } - - if (session != null) - { - var controller = session.SessionController as WebSocketController; - - if (controller == null) - { - controller = new WebSocketController(session, _logger, _sessionManager); - } - - controller.AddWebSocket(message.Connection); - - session.SessionController = controller; - } - else - { - _logger.Warn("Unable to determine session based on identity message: {0}", message.Data); - } - } - - /// - /// Processes the context message. - /// - /// The message. - private void ProcessContextMessage(WebSocketMessageInfo message) - { - var session = GetSessionFromMessage(message); - - if (session != null) - { - var vals = message.Data.Split('|'); - - var itemId = vals[1]; - - if (!string.IsNullOrWhiteSpace(itemId)) - { - _sessionManager.ReportNowViewingItem(session.Id, itemId); - } - } - } - - /// - /// Gets the session from message. - /// - /// The message. - /// SessionInfo. - private SessionInfo GetSessionFromMessage(WebSocketMessageInfo message) - { - var result = _sessionManager.Sessions.FirstOrDefault(i => - { - var controller = i.SessionController as WebSocketController; - - if (controller != null) - { - if (controller.Sockets.Any(s => s.Id == message.Connection.Id)) - { - return true; - } - } - - return false; - - }); - - if (result == null) - { - _logger.Error("Unable to find session based on web socket message"); - } - - return result; - } - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - /// - /// Reports the playback start. - /// - /// The message. - private void OnPlaybackStart(WebSocketMessageInfo message) - { - _logger.Debug("Received PlaybackStart message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var vals = message.Data.Split('|'); - - var itemId = vals[0]; - - var canSeek = true; - - if (vals.Length > 1) - { - canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase); - } - if (vals.Length > 2) - { - // vals[2] used to be QueueableMediaTypes - } - - var info = new PlaybackStartInfo - { - CanSeek = canSeek, - ItemId = itemId, - SessionId = session.Id - }; - - if (vals.Length > 3) - { - info.MediaSourceId = vals[3]; - } - - if (vals.Length > 4 && !string.IsNullOrWhiteSpace(vals[4])) - { - info.AudioStreamIndex = int.Parse(vals[4], _usCulture); - } - - if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5])) - { - info.SubtitleStreamIndex = int.Parse(vals[5], _usCulture); - } - - _sessionManager.OnPlaybackStart(info); - } - } - - private void ReportPlaybackStart(WebSocketMessageInfo message) - { - _logger.Debug("Received ReportPlaybackStart message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var info = _json.DeserializeFromString(message.Data); - - info.SessionId = session.Id; - - _sessionManager.OnPlaybackStart(info); - } - } - - private void ReportPlaybackProgress(WebSocketMessageInfo message) - { - //_logger.Debug("Received ReportPlaybackProgress message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var info = _json.DeserializeFromString(message.Data); - - info.SessionId = session.Id; - - _sessionManager.OnPlaybackProgress(info); - } - } - - /// - /// Reports the playback progress. - /// - /// The message. - private void OnPlaybackProgress(WebSocketMessageInfo message) - { - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var vals = message.Data.Split('|'); - - var itemId = vals[0]; - - long? positionTicks = null; - - if (vals.Length > 1) - { - long pos; - - if (long.TryParse(vals[1], out pos)) - { - positionTicks = pos; - } - } - - var isPaused = vals.Length > 2 && string.Equals(vals[2], "true", StringComparison.OrdinalIgnoreCase); - var isMuted = vals.Length > 3 && string.Equals(vals[3], "true", StringComparison.OrdinalIgnoreCase); - - var info = new PlaybackProgressInfo - { - ItemId = itemId, - PositionTicks = positionTicks, - IsMuted = isMuted, - IsPaused = isPaused, - SessionId = session.Id - }; - - if (vals.Length > 4) - { - info.MediaSourceId = vals[4]; - } - - if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5])) - { - info.VolumeLevel = int.Parse(vals[5], _usCulture); - } - - if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[6])) - { - info.AudioStreamIndex = int.Parse(vals[6], _usCulture); - } - - if (vals.Length > 7 && !string.IsNullOrWhiteSpace(vals[7])) - { - info.SubtitleStreamIndex = int.Parse(vals[7], _usCulture); - } - - _sessionManager.OnPlaybackProgress(info); - } - } - - private void ReportPlaybackStopped(WebSocketMessageInfo message) - { - _logger.Debug("Received ReportPlaybackStopped message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var info = _json.DeserializeFromString(message.Data); - - info.SessionId = session.Id; - - _sessionManager.OnPlaybackStopped(info); - } - } - - /// - /// Reports the playback stopped. - /// - /// The message. - private void OnPlaybackStopped(WebSocketMessageInfo message) - { - _logger.Debug("Received PlaybackStopped message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var vals = message.Data.Split('|'); - - var itemId = vals[0]; - - long? positionTicks = null; - - if (vals.Length > 1) - { - long pos; - - if (long.TryParse(vals[1], out pos)) - { - positionTicks = pos; - } - } - - var info = new PlaybackStopInfo - { - ItemId = itemId, - PositionTicks = positionTicks, - SessionId = session.Id - }; - - if (vals.Length > 2) - { - info.MediaSourceId = vals[2]; - } - - _sessionManager.OnPlaybackStopped(info); - } + var controller = (WebSocketController)controllerInfo.Item1; + controller.AddWebSocket(connection); } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index b13eb6116d..ddac9660f2 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Net.WebSockets; namespace Emby.Server.Implementations.Session { @@ -40,28 +41,14 @@ namespace Emby.Server.Implementations.Session get { return HasOpenSockets; } } - private bool _isActive; - private DateTime _lastActivityDate; public bool IsSessionActive { get { - if (HasOpenSockets) - { - return true; - } - - //return false; - return _isActive && (DateTime.UtcNow - _lastActivityDate).TotalMinutes <= 10; + return HasOpenSockets; } } - public void OnActivity() - { - _isActive = true; - _lastActivityDate = DateTime.UtcNow; - } - private IEnumerable GetActiveSockets() { return Sockets @@ -81,209 +68,40 @@ namespace Emby.Server.Implementations.Session void connection_Closed(object sender, EventArgs e) { - if (!GetActiveSockets().Any()) - { - _isActive = false; + var connection = (IWebSocketConnection)sender; + var sockets = Sockets.ToList(); + sockets.Remove(connection); - try - { - _sessionManager.ReportSessionEnded(Session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error reporting session ended.", ex); - } - } + Sockets = sockets; + + _sessionManager.CloseIfNeeded(Session); } - private IWebSocketConnection GetActiveSocket() + public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) { var socket = GetActiveSockets() .FirstOrDefault(); if (socket == null) { - throw new InvalidOperationException("The requested session does not have an open web socket."); + return Task.CompletedTask; } - return socket; - } - - public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) - { - return SendMessageInternal(new WebSocketMessage - { - MessageType = "Play", - Data = command - - }, cancellationToken); - } - - public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) - { - return SendMessageInternal(new WebSocketMessage - { - MessageType = "Playstate", - Data = command - - }, cancellationToken); - } - - public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "LibraryChanged", - Data = info - - }, cancellationToken); - } - - /// - /// Sends the restart required message. - /// - /// The information. - /// The cancellation token. - /// Task. - public Task SendRestartRequiredNotification(CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "RestartRequired", - Data = string.Empty - - }, cancellationToken); - } - - - /// - /// Sends the user data change info. - /// - /// The info. - /// The cancellation token. - /// Task. - public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "UserDataChanged", - Data = info - - }, cancellationToken); - } - - /// - /// Sends the server shutdown notification. - /// - /// The cancellation token. - /// Task. - public Task SendServerShutdownNotification(CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "ServerShuttingDown", - Data = string.Empty - - }, cancellationToken); - } - - /// - /// Sends the server restart notification. - /// - /// The cancellation token. - /// Task. - public Task SendServerRestartNotification(CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "ServerRestarting", - Data = string.Empty - - }, cancellationToken); - } - - public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) - { - return SendMessageInternal(new WebSocketMessage - { - MessageType = "GeneralCommand", - Data = command - - }, cancellationToken); - } - - public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "SessionEnded", - Data = sessionInfo - - }, cancellationToken); - } - - public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "PlaybackStart", - Data = sessionInfo - - }, cancellationToken); - } - - public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage - { - MessageType = "PlaybackStopped", - Data = sessionInfo - - }, cancellationToken); - } - - public Task SendMessage(string name, T data, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage + return socket.SendAsync(new WebSocketMessage { Data = data, - MessageType = name + MessageType = name, + MessageId = messageId }, cancellationToken); } - private Task SendMessageInternal(WebSocketMessage message, CancellationToken cancellationToken) - { - var socket = GetActiveSocket(); - - return socket.SendAsync(message, cancellationToken); - } - - private Task SendMessagesInternal(WebSocketMessage message, CancellationToken cancellationToken) - { - var tasks = GetActiveSockets().Select(i => Task.Run(async () => - { - try - { - await i.SendAsync(message, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending web socket message", ex); - } - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - public void Dispose() { foreach (var socket in Sockets.ToList()) { socket.Closed -= connection_Closed; } - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Social/SharingManager.cs b/Emby.Server.Implementations/Social/SharingManager.cs deleted file mode 100644 index 23ce7492ad..0000000000 --- a/Emby.Server.Implementations/Social/SharingManager.cs +++ /dev/null @@ -1,101 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Social; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Social -{ - public class SharingManager : ISharingManager - { - private readonly ISharingRepository _repository; - private readonly IServerConfigurationManager _config; - private readonly ILibraryManager _libraryManager; - private readonly IServerApplicationHost _appHost; - - public SharingManager(ISharingRepository repository, IServerConfigurationManager config, ILibraryManager libraryManager, IServerApplicationHost appHost) - { - _repository = repository; - _config = config; - _libraryManager = libraryManager; - _appHost = appHost; - } - - public async Task CreateShare(string itemId, string userId) - { - if (string.IsNullOrWhiteSpace(itemId)) - { - throw new ArgumentNullException("itemId"); - } - if (string.IsNullOrWhiteSpace(userId)) - { - throw new ArgumentNullException("userId"); - } - - var item = _libraryManager.GetItemById(itemId); - - if (item == null) - { - throw new ResourceNotFoundException(); - } - - var externalUrl = (await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false)).WanAddress; - - if (string.IsNullOrWhiteSpace(externalUrl)) - { - throw new InvalidOperationException("No external server address is currently available."); - } - - var info = new SocialShareInfo - { - Id = Guid.NewGuid().ToString("N"), - ExpirationDate = DateTime.UtcNow.AddDays(_config.Configuration.SharingExpirationDays), - ItemId = itemId, - UserId = userId - }; - - AddShareInfo(info, externalUrl); - - _repository.CreateShare(info); - - return info; - } - - private string GetTitle(BaseItem item) - { - return item.Name; - } - - public SocialShareInfo GetShareInfo(string id) - { - var info = _repository.GetShareInfo(id); - - AddShareInfo(info, _appHost.GetPublicSystemInfo(CancellationToken.None).Result.WanAddress); - - return info; - } - - private void AddShareInfo(SocialShareInfo info, string externalUrl) - { - info.ImageUrl = externalUrl + "/Social/Shares/Public/" + info.Id + "/Image"; - info.Url = externalUrl + "/emby/web/shared.html?id=" + info.Id; - - var item = _libraryManager.GetItemById(info.ItemId); - - if (item != null) - { - info.Overview = item.Overview; - info.Name = GetTitle(item); - } - } - - public void DeleteShare(string id) - { - _repository.DeleteShare(id); - } - } -} diff --git a/Emby.Server.Implementations/Social/SharingRepository.cs b/Emby.Server.Implementations/Social/SharingRepository.cs deleted file mode 100644 index 3c9e1024f8..0000000000 --- a/Emby.Server.Implementations/Social/SharingRepository.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Emby.Server.Implementations.Data; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Social; -using SQLitePCL.pretty; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Social -{ - public class SharingRepository : BaseSqliteRepository, ISharingRepository - { - protected IFileSystem FileSystem { get; private set; } - - public SharingRepository(ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem) - : base(logger) - { - FileSystem = fileSystem; - DbFilePath = Path.Combine(appPaths.DataPath, "shares.db"); - } - - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.ErrorException("Error loading database file. Will reset and retry.", ex); - - FileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - /// - /// Opens the connection to the database - /// - /// Task. - private void InitializeInternal() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - - "create table if not exists Shares (Id GUID NOT NULL, ItemId TEXT NOT NULL, UserId TEXT NOT NULL, ExpirationDate DateTime NOT NULL, PRIMARY KEY (Id))", - "create index if not exists idx_Shares on Shares(Id)", - - "pragma shrink_memory" - }; - - connection.RunQueries(queries); - } - } - - public void CreateShare(SocialShareInfo info) - { - if (info == null) - { - throw new ArgumentNullException("info"); - } - if (string.IsNullOrWhiteSpace(info.Id)) - { - throw new ArgumentNullException("info.Id"); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - var commandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (?, ?, ?, ?)"; - - db.Execute(commandText, - info.Id.ToGuidBlob(), - info.ItemId, - info.UserId, - info.ExpirationDate.ToDateTimeParamValue()); - }, TransactionMode); - } - } - } - - public SocialShareInfo GetShareInfo(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = ?"; - - var paramList = new List(); - paramList.Add(id.ToGuidBlob()); - - foreach (var row in connection.Query(commandText, paramList.ToArray(paramList.Count))) - { - return GetSocialShareInfo(row); - } - } - } - - return null; - } - - private SocialShareInfo GetSocialShareInfo(IReadOnlyList reader) - { - var info = new SocialShareInfo(); - - info.Id = reader[0].ReadGuidFromBlob().ToString("N"); - info.ItemId = reader[1].ToString(); - info.UserId = reader[2].ToString(); - info.ExpirationDate = reader[3].ReadDateTime(); - - return info; - } - - public void DeleteShare(string id) - { - - } - } -} diff --git a/Emby.Server.Implementations/SystemEvents.cs b/Emby.Server.Implementations/SystemEvents.cs index dfff92f1ef..c1e6dc1da9 100644 --- a/Emby.Server.Implementations/SystemEvents.cs +++ b/Emby.Server.Implementations/SystemEvents.cs @@ -17,34 +17,6 @@ namespace Emby.Server.Implementations public SystemEvents(ILogger logger) { _logger = logger; - Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding; - } - - private void SystemEvents_SessionEnding(object sender, Microsoft.Win32.SessionEndingEventArgs e) - { - switch (e.Reason) - { - case Microsoft.Win32.SessionEndReasons.Logoff: - EventHelper.FireEventIfNotNull(SessionLogoff, this, EventArgs.Empty, _logger); - break; - case Microsoft.Win32.SessionEndReasons.SystemShutdown: - EventHelper.FireEventIfNotNull(SystemShutdown, this, EventArgs.Empty, _logger); - break; - } - } - - private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e) - { - switch (e.Mode) - { - case Microsoft.Win32.PowerModes.Resume: - EventHelper.FireEventIfNotNull(Resume, this, EventArgs.Empty, _logger); - break; - case Microsoft.Win32.PowerModes.Suspend: - EventHelper.FireEventIfNotNull(Suspend, this, EventArgs.Empty, _logger); - break; - } } } } diff --git a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs deleted file mode 100644 index 764df8baf2..0000000000 --- a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; -using MediaBrowser.Model.Xml; -using MediaBrowser.Providers.TV; - -namespace Emby.Server.Implementations.TV -{ - class SeriesGroup : List, IGrouping - { - public string Key { get; set; } - } - - class SeriesPostScanTask : ILibraryPostScanTask, IHasOrder - { - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly ILocalizationManager _localization; - private readonly IFileSystem _fileSystem; - private readonly IXmlReaderSettingsFactory _xmlSettings; - - public SeriesPostScanTask(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, ILocalizationManager localization, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlSettings) - { - _libraryManager = libraryManager; - _logger = logger; - _config = config; - _localization = localization; - _fileSystem = fileSystem; - _xmlSettings = xmlSettings; - } - - public Task Run(IProgress progress, CancellationToken cancellationToken) - { - return RunInternal(progress, cancellationToken); - } - - private Task RunInternal(IProgress progress, CancellationToken cancellationToken) - { - var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() - { - IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true, - GroupByPresentationUniqueKey = false, - DtoOptions = new DtoOptions(true) - - }).Cast().ToList(); - - var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList(); - - return new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem, _xmlSettings).Run(seriesGroups, true, cancellationToken); - } - - internal static IEnumerable> FindSeriesGroups(List seriesList) - { - var links = seriesList.ToDictionary(s => s, s => seriesList.Where(c => c != s && ShareProviderId(s, c)).ToList()); - - var visited = new HashSet(); - - foreach (var series in seriesList) - { - if (!visited.Contains(series)) - { - var group = new SeriesGroup(); - FindAllLinked(series, visited, links, group); - - group.Key = group.Select(s => s.PresentationUniqueKey).FirstOrDefault(id => !string.IsNullOrEmpty(id)); - - yield return group; - } - } - } - - private static void FindAllLinked(Series series, HashSet visited, IDictionary> linksMap, List results) - { - results.Add(series); - visited.Add(series); - - var links = linksMap[series]; - - foreach (var s in links) - { - if (!visited.Contains(s)) - { - FindAllLinked(s, visited, linksMap, results); - } - } - } - - private static bool ShareProviderId(Series a, Series b) - { - return string.Equals(a.PresentationUniqueKey, b.PresentationUniqueKey, StringComparison.Ordinal); - } - - public int Order - { - get - { - // Run after tvdb update task - return 1; - } - } - } - - public class CleanMissingEpisodesEntryPoint : IServerEntryPoint - { - private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly ILocalizationManager _localization; - private readonly IFileSystem _fileSystem; - private readonly object _libraryChangedSyncLock = new object(); - private const int LibraryUpdateDuration = 180000; - private readonly ITaskManager _taskManager; - private readonly IXmlReaderSettingsFactory _xmlSettings; - private readonly ITimerFactory _timerFactory; - - public CleanMissingEpisodesEntryPoint(ILibraryManager libraryManager, IServerConfigurationManager config, ILogger logger, ILocalizationManager localization, IFileSystem fileSystem, ITaskManager taskManager, IXmlReaderSettingsFactory xmlSettings, ITimerFactory timerFactory) - { - _libraryManager = libraryManager; - _config = config; - _logger = logger; - _localization = localization; - _fileSystem = fileSystem; - _taskManager = taskManager; - _xmlSettings = xmlSettings; - _timerFactory = timerFactory; - } - - private ITimer LibraryUpdateTimer { get; set; } - - public void Run() - { - _libraryManager.ItemAdded += _libraryManager_ItemAdded; - } - - private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (!FilterItem(e.Item)) - { - return; - } - - lock (_libraryChangedSyncLock) - { - if (LibraryUpdateTimer == null) - { - LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); - } - else - { - LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); - } - } - } - - private async void LibraryUpdateTimerCallback(object state) - { - try - { - if (MissingEpisodeProvider.IsRunning) - { - return; - } - - if (_libraryManager.IsScanRunning) - { - return; - } - - var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() - { - IncludeItemTypes = new[] { typeof(Series).Name }, - Recursive = true, - GroupByPresentationUniqueKey = false, - DtoOptions = new DtoOptions(true) - - }).Cast().ToList(); - - var seriesGroups = SeriesPostScanTask.FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList(); - - await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem, _xmlSettings) - .Run(seriesGroups, false, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SeriesPostScanTask", ex); - } - } - - private bool FilterItem(BaseItem item) - { - return item is Episode && item.LocationType != LocationType.Virtual; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - if (LibraryUpdateTimer != null) - { - LibraryUpdateTimer.Dispose(); - LibraryUpdateTimer = null; - } - - _libraryManager.ItemAdded -= _libraryManager_ItemAdded; - } - } - } -} diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index d92245a677..1f9cb91645 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -37,48 +37,50 @@ namespace Emby.Server.Implementations.TV } string presentationUniqueKey = null; - int? limit = null; - if (!string.IsNullOrWhiteSpace(request.SeriesId)) + if (!string.IsNullOrEmpty(request.SeriesId)) { var series = _libraryManager.GetItemById(request.SeriesId) as Series; if (series != null) { presentationUniqueKey = GetUniqueSeriesKey(series); - limit = 1; } } - if (!string.IsNullOrWhiteSpace(presentationUniqueKey)) + if (!string.IsNullOrEmpty(presentationUniqueKey)) { return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request); } - var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); + var parentIdGuid = string.IsNullOrEmpty(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); - List parents; + BaseItem[] parents; if (parentIdGuid.HasValue) { var parent = _libraryManager.GetItemById(parentIdGuid.Value); - parents = new List(); + if (parent != null) { - parents.Add(parent); + parents = new[] { parent }; + } + else + { + parents = Array.Empty(); } } else { - parents = user.RootFolder.GetChildren(user, true) + parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N"))) - .ToList(); + .ToArray(); } return GetNextUp(request, parents, dtoOptions); } - public QueryResult GetNextUp(NextUpQuery request, List parentsFolders, DtoOptions dtoOptions) + public QueryResult GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions dtoOptions) { var user = _userManager.GetUserById(request.UserId); @@ -89,7 +91,7 @@ namespace Emby.Server.Implementations.TV string presentationUniqueKey = null; int? limit = null; - if (!string.IsNullOrWhiteSpace(request.SeriesId)) + if (!string.IsNullOrEmpty(request.SeriesId)) { var series = _libraryManager.GetItemById(request.SeriesId) as Series; @@ -100,7 +102,7 @@ namespace Emby.Server.Implementations.TV } } - if (!string.IsNullOrWhiteSpace(presentationUniqueKey)) + if (!string.IsNullOrEmpty(presentationUniqueKey)) { return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request); } @@ -113,7 +115,7 @@ namespace Emby.Server.Implementations.TV var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - OrderBy = new[] { new Tuple(ItemSortBy.DatePlayed, SortOrder.Descending) }, + OrderBy = new[] { new ValueTuple(ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions @@ -126,7 +128,7 @@ namespace Emby.Server.Implementations.TV }, GroupBySeriesPresentationUniqueKey = true - }, parentsFolders).Cast().Select(GetUniqueSeriesKey); + }, parentsFolders.ToList()).Cast().Select(GetUniqueSeriesKey); // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items, dtoOptions); @@ -146,7 +148,7 @@ namespace Emby.Server.Implementations.TV // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) - var alwaysEnableFirstEpisode = !string.IsNullOrWhiteSpace(request.SeriesId); + var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId); var anyFound = false; return allNextUp @@ -190,7 +192,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, - OrderBy = new[] { new Tuple(ItemSortBy.SortName, SortOrder.Descending) }, + OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, ParentIndexNumberNotEquals = 0, @@ -212,7 +214,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, - OrderBy = new[] { new Tuple(ItemSortBy.SortName, SortOrder.Ascending) }, + OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, IsPlayed = false, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/TextEncoding/TextEncoding.cs b/Emby.Server.Implementations/TextEncoding/TextEncoding.cs index 9eb9be7eaa..ea87a95398 100644 --- a/Emby.Server.Implementations/TextEncoding/TextEncoding.cs +++ b/Emby.Server.Implementations/TextEncoding/TextEncoding.cs @@ -193,6 +193,8 @@ namespace Emby.Server.Implementations.TextEncoding switch (language.ToLower()) { + case "tha": + return "windows-874"; case "hun": return "windows-1252"; case "pol": @@ -203,6 +205,7 @@ namespace Emby.Server.Implementations.TextEncoding case "hrv": case "rum": case "ron": + case "rom": case "rup": return "windows-1250"; // albanian diff --git a/Emby.Server.Implementations/Threading/CommonTimer.cs b/Emby.Server.Implementations/Threading/CommonTimer.cs index bb67325d14..9451b07f32 100644 --- a/Emby.Server.Implementations/Threading/CommonTimer.cs +++ b/Emby.Server.Implementations/Threading/CommonTimer.cs @@ -31,7 +31,6 @@ namespace Emby.Server.Implementations.Threading public void Dispose() { _timer.Dispose(); - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 28de80da16..f195ca7104 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -234,7 +234,6 @@ namespace Emby.Server.Implementations.Udp public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 51acfee881..435bcfd043 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -19,6 +19,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; +using MediaBrowser.Controller.Configuration; namespace Emby.Server.Implementations.Updates { @@ -111,7 +112,7 @@ namespace Emby.Server.Implementations.Updates private readonly IHttpClient _httpClient; private readonly IJsonSerializer _jsonSerializer; private readonly ISecurityManager _securityManager; - private readonly IConfigurationManager _config; + private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; /// @@ -125,7 +126,7 @@ namespace Emby.Server.Implementations.Updates // netframework or netcore private readonly string _packageRuntime; - public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IConfigurationManager config, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, string packageRuntime) + public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IServerConfigurationManager config, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, string packageRuntime) { if (logger == null) { @@ -189,7 +190,7 @@ namespace Emby.Server.Implementations.Updates { cancellationToken.ThrowIfCancellationRequested(); - var packages = _jsonSerializer.DeserializeFromStream(json); + var packages = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false); return FilterPackages(packages, packageType, applicationVersion); } @@ -203,8 +204,6 @@ namespace Emby.Server.Implementations.Updates } } - private DateTime _lastPackageUpdateTime; - /// /// Gets all available packages. /// @@ -212,77 +211,21 @@ namespace Emby.Server.Implementations.Updates /// Task{List{PackageInfo}}. public async Task> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken) { - _logger.Info("Opening {0}", PackageCachePath); - try + using (var response = await _httpClient.SendAsync(new HttpRequestOptions { - using (var stream = _fileSystem.OpenRead(PackageCachePath)) + Url = "https://www.mb3admin.com/admin/service/EmbyPackages.json", + CancellationToken = cancellationToken, + Progress = new SimpleProgress(), + CacheLength = GetCacheLength(), + CacheMode = CacheMode.Unconditional + + }, "GET").ConfigureAwait(false)) + { + using (var stream = response.Content) { - var packages = _jsonSerializer.DeserializeFromStream(stream); - - if (DateTime.UtcNow - _lastPackageUpdateTime > GetCacheLength()) - { - UpdateCachedPackages(CancellationToken.None, false); - } - - return FilterPackages(packages); + return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false)); } } - catch (Exception) - { - - } - - _lastPackageUpdateTime = DateTime.MinValue; - await UpdateCachedPackages(cancellationToken, true).ConfigureAwait(false); - using (var stream = _fileSystem.OpenRead(PackageCachePath)) - { - return FilterPackages(_jsonSerializer.DeserializeFromStream(stream)); - } - } - - private string PackageCachePath - { - get { return Path.Combine(_appPaths.CachePath, "serverpackages.json"); } - } - - private readonly SemaphoreSlim _updateSemaphore = new SemaphoreSlim(1, 1); - private async Task UpdateCachedPackages(CancellationToken cancellationToken, bool throwErrors) - { - await _updateSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (DateTime.UtcNow - _lastPackageUpdateTime < GetCacheLength()) - { - return; - } - - var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = "https://www.mb3admin.com/admin/service/EmbyPackages.json", - CancellationToken = cancellationToken, - Progress = new SimpleProgress() - - }).ConfigureAwait(false); - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(PackageCachePath)); - - _fileSystem.CopyFile(tempFile, PackageCachePath, true); - _lastPackageUpdateTime = DateTime.UtcNow; - } - catch (Exception ex) - { - _logger.ErrorException("Error updating package cache", ex); - - if (throwErrors) - { - throw; - } - } - finally - { - _updateSemaphore.Release(); - } } private PackageVersionClass GetSystemUpdateLevel() @@ -304,12 +247,12 @@ namespace Emby.Server.Implementations.Updates var versions = new List(); foreach (var version in package.versions) { - if (string.IsNullOrWhiteSpace(version.sourceUrl)) + if (string.IsNullOrEmpty(version.sourceUrl)) { continue; } - if (string.IsNullOrWhiteSpace(version.runtimes) || version.runtimes.IndexOf(_packageRuntime, StringComparison.OrdinalIgnoreCase) == -1) + if (string.IsNullOrEmpty(version.runtimes) || version.runtimes.IndexOf(_packageRuntime, StringComparison.OrdinalIgnoreCase) == -1) { continue; } @@ -339,7 +282,7 @@ namespace Emby.Server.Implementations.Updates var returnList = new List(); - var filterOnPackageType = !string.IsNullOrWhiteSpace(packageType); + var filterOnPackageType = !string.IsNullOrEmpty(packageType); foreach (var p in packagesList) { @@ -465,7 +408,7 @@ namespace Emby.Server.Implementations.Updates return latestPluginInfo != null && GetPackageVersion(latestPluginInfo) > p.Version ? latestPluginInfo : null; }).Where(i => i != null) - .Where(p => !string.IsNullOrWhiteSpace(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); + .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); } /// @@ -491,7 +434,7 @@ namespace Emby.Server.Implementations.Updates var installationInfo = new InstallationInfo { - Id = Guid.NewGuid().ToString("N"), + Id = Guid.NewGuid(), Name = package.name, AssemblyGuid = package.guid, UpdateClass = package.classification, @@ -575,7 +518,6 @@ namespace Emby.Server.Implementations.Updates finally { // Dispose the progress object and remove the installation from the in-progress list - innerProgress.Dispose(); tuple.Item2.Dispose(); } } @@ -590,16 +532,23 @@ namespace Emby.Server.Implementations.Updates /// Task. private async Task InstallPackageInternal(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken) { + IPlugin plugin = null; + + if (isPlugin) + { + // Set last update time if we were installed before + plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) + ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); + } + + string targetPath = plugin == null ? null : plugin.AssemblyFilePath; + // Do the install - await PerformPackageInstallation(progress, package, cancellationToken).ConfigureAwait(false); + await PerformPackageInstallation(progress, targetPath, package, cancellationToken).ConfigureAwait(false); // Do plugin-specific processing if (isPlugin) { - // Set last update time if we were installed before - var plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) - ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); - if (plugin != null) { OnPluginUpdated(plugin, package); @@ -611,13 +560,17 @@ namespace Emby.Server.Implementations.Updates } } - private async Task PerformPackageInstallation(IProgress progress, PackageVersionInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(IProgress progress, string target, PackageVersionInfo package, CancellationToken cancellationToken) { // Target based on if it is an archive or single assembly // zip archives are assumed to contain directory structures relative to our ProgramDataPath var extension = Path.GetExtension(package.targetFilename); var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".7z", StringComparison.OrdinalIgnoreCase); - var target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename); + + if (target == null) + { + target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename); + } // Download to temporary file so that, if interrupted, it won't destroy the existing installation var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions @@ -632,7 +585,7 @@ namespace Emby.Server.Implementations.Updates // Validate with a checksum var packageChecksum = string.IsNullOrWhiteSpace(package.checksum) ? Guid.Empty : new Guid(package.checksum); - if (packageChecksum != Guid.Empty) // support for legacy uploads for now + if (!packageChecksum.Equals(Guid.Empty)) // support for legacy uploads for now { using (var stream = _fileSystem.OpenRead(tempFile)) { @@ -700,6 +653,15 @@ namespace Emby.Server.Implementations.Updates _fileSystem.DeleteFile(path); + var list = _config.Configuration.UninstalledPlugins.ToList(); + var filename = Path.GetFileName(path); + if (!list.Contains(filename, StringComparer.OrdinalIgnoreCase)) + { + list.Add(filename); + _config.Configuration.UninstalledPlugins = list.ToArray(); + _config.SaveConfiguration(); + } + OnPluginUninstalled(plugin); _applicationHost.NotifyPendingRestart(); @@ -728,7 +690,6 @@ namespace Emby.Server.Implementations.Updates public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index f051e856a8..2543fd3728 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -28,70 +28,70 @@ namespace Emby.Server.Implementations.UserViews { } - protected override List GetItemsWithImages(IHasMetadata item) + protected override List GetItemsWithImages(BaseItem item) { var view = (CollectionFolder)item; + var viewType = view.CollectionType; - var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + string[] includeItemTypes; - var result = view.GetItemList(new InternalItemsQuery + if (string.Equals(viewType, CollectionType.Movies)) + { + includeItemTypes = new string[] { "Movie" }; + } + else if (string.Equals(viewType, CollectionType.TvShows)) + { + includeItemTypes = new string[] { "Series" }; + } + else if (string.Equals(viewType, CollectionType.Music)) + { + includeItemTypes = new string[] { "MusicAlbum" }; + } + else if (string.Equals(viewType, CollectionType.Books)) + { + includeItemTypes = new string[] { "Book", "AudioBook" }; + } + else if (string.Equals(viewType, CollectionType.Games)) + { + includeItemTypes = new string[] { "Game" }; + } + else if (string.Equals(viewType, CollectionType.BoxSets)) + { + includeItemTypes = new string[] { "BoxSet" }; + } + else if (string.Equals(viewType, CollectionType.HomeVideos) || string.Equals(viewType, CollectionType.Photos)) + { + includeItemTypes = new string[] { "Video", "Photo" }; + } + else + { + includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" }; + } + + var recursive = !new[] { CollectionType.Playlists }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + + return view.GetItemList(new InternalItemsQuery { CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" }, - DtoOptions = new DtoOptions(false) - - }); - - var items = result.Select(i => - { - var episode = i as Episode; - if (episode != null) + DtoOptions = new DtoOptions(false), + ImageTypes = new ImageType[] { ImageType.Primary }, + Limit = 8, + OrderBy = new ValueTuple[] { - var series = episode.Series; - if (series != null) - { - return series; - } + new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) + }, + IncludeItemTypes = includeItemTypes - return episode; - } - - var season = i as Season; - if (season != null) - { - var series = season.Series; - if (series != null) - { - return series; - } - - return season; - } - - var audio = i as Audio; - if (audio != null) - { - var album = audio.AlbumEntity; - if (album != null && album.HasImage(ImageType.Primary)) - { - return album; - } - } - - return i; - - }).DistinctBy(i => i.Id); - - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8); + }).ToList(); } - protected override bool Supports(IHasMetadata item) + protected override bool Supports(BaseItem item) { return item is CollectionFolder; } - protected override string CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); @@ -108,55 +108,4 @@ namespace Emby.Server.Implementations.UserViews return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex); } } - - public class ManualCollectionFolderImageProvider : BaseDynamicImageProvider - { - private readonly ILibraryManager _libraryManager; - - public ManualCollectionFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) - { - _libraryManager = libraryManager; - } - - protected override List GetItemsWithImages(IHasMetadata item) - { - var view = (ManualCollectionsFolder)item; - - var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); - - var items = _libraryManager.GetItemList(new InternalItemsQuery - { - Recursive = recursive, - IncludeItemTypes = new[] { typeof(BoxSet).Name }, - Limit = 20, - OrderBy = new [] { new Tuple(ItemSortBy.Random, SortOrder.Ascending) }, - DtoOptions = new DtoOptions(false) - }); - - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8); - } - - protected override bool Supports(IHasMetadata item) - { - return item is ManualCollectionsFolder; - } - - protected override string CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) - { - var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); - - if (imageType == ImageType.Primary) - { - if (itemsWithImages.Count == 0) - { - return null; - } - - return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540); - } - - return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex); - } - } - } diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 23b8c9b9e9..c750332617 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -31,39 +31,12 @@ namespace Emby.Server.Implementations.UserViews _libraryManager = libraryManager; } - protected override List GetItemsWithImages(IHasMetadata item) + protected override List GetItemsWithImages(BaseItem item) { var view = (UserView)item; - if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) - { - var programs = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, - ImageTypes = new[] { ImageType.Primary }, - Limit = 30, - IsMovie = true, - DtoOptions = new DtoOptions(false) - - }); - - return GetFinalItems(programs); - } - - if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) || - string.Equals(view.ViewType, SpecialFolder.TvGenre, StringComparison.OrdinalIgnoreCase)) - { - var userItemsResult = view.GetItemList(new InternalItemsQuery - { - CollapseBoxSetItems = false, - DtoOptions = new DtoOptions(false) - }); - - return userItemsResult.ToList(); - } - var isUsingCollectionStrip = IsUsingCollectionStrip(view); - var recursive = isUsingCollectionStrip && !new[] { CollectionType.Channels, CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase); var result = view.GetItemList(new InternalItemsQuery { @@ -116,13 +89,19 @@ namespace Emby.Server.Implementations.UserViews if (isUsingCollectionStrip) { - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8); + return items + .Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)) + .OrderBy(i => Guid.NewGuid()) + .ToList(); } - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary))); + return items + .Where(i => i.HasImage(ImageType.Primary)) + .OrderBy(i => Guid.NewGuid()) + .ToList(); } - protected override bool Supports(IHasMetadata item) + protected override bool Supports(BaseItem item) { var view = item as UserView; if (view != null) @@ -139,14 +118,13 @@ namespace Emby.Server.Implementations.UserViews { CollectionType.Movies, CollectionType.TvShows, - CollectionType.Playlists, - CollectionType.Photos + CollectionType.Playlists }; return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty); } - protected override string CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { if (itemsWithImages.Count == 0) { diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs index 80a74e8774..abd6810b0b 100644 --- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs @@ -17,7 +17,7 @@ using MediaBrowser.Controller.Dto; namespace Emby.Server.Implementations.Photos { public abstract class BaseFolderImageProvider : BaseDynamicImageProvider - where T : Folder, new () + where T : Folder, new() { protected ILibraryManager _libraryManager; @@ -27,43 +27,33 @@ namespace Emby.Server.Implementations.Photos _libraryManager = libraryManager; } - protected override List GetItemsWithImages(IHasMetadata item) + protected override List GetItemsWithImages(BaseItem item) { return _libraryManager.GetItemList(new InternalItemsQuery { - Parent = item as BaseItem, - GroupByPresentationUniqueKey = false, + Parent = item, DtoOptions = new DtoOptions(true), - ImageTypes = new ImageType[] { ImageType.Primary } + ImageTypes = new ImageType[] { ImageType.Primary }, + OrderBy = new System.ValueTuple[] + { + new System.ValueTuple(ItemSortBy.IsFolder, SortOrder.Ascending), + new System.ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) + }, + Limit = 1 }); } - protected override string CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } - protected override bool Supports(IHasMetadata item) + protected override bool Supports(BaseItem item) { - if (item is PhotoAlbum || item is MusicAlbum) - { - return true; - } - - if (item.GetType() == typeof(Folder)) - { - var folder = item as Folder; - if (folder.IsTopParent) - { - return false; - } - return true; - } - - return false; + return item is T; } - protected override bool HasChangedByDate(IHasMetadata item, ItemImageInfo image) + protected override bool HasChangedByDate(BaseItem item, ItemImageInfo image) { if (item is MusicAlbum) { @@ -80,6 +70,25 @@ namespace Emby.Server.Implementations.Photos : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) { } + + protected override bool Supports(BaseItem item) + { + if (item is PhotoAlbum || item is MusicAlbum) + { + return false; + } + + var folder = item as Folder; + if (folder != null) + { + if (folder.IsTopParent) + { + return false; + } + } + return true; + //return item.SourceType == SourceType.Library; + } } public class MusicAlbumImageProvider : BaseFolderImageProvider diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config deleted file mode 100644 index 6e68810d8f..0000000000 --- a/Emby.Server.Implementations/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/Emby.Server.Implementations/values.txt b/Emby.Server.Implementations/values.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 04cef60bfc..9676a2c2af 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Api { if (string.IsNullOrWhiteSpace(value)) { - return new string[] { }; + return Array.Empty(); } if (removeEmpty) @@ -88,7 +88,6 @@ namespace MediaBrowser.Api public void Dispose() { - GC.SuppressFinalize(this); } } } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 1629d49b49..db6af0c4bd 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Api return Request.Headers[name]; } - private static readonly string[] EmptyStringArray = new string[] { }; + private static readonly string[] EmptyStringArray = Array.Empty(); public static string[] SplitValue(string value, char delim) { if (string.IsNullOrWhiteSpace(value)) @@ -65,7 +65,13 @@ namespace MediaBrowser.Api return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries); } - + + public static Guid[] GetGuids(string value) + { + // Unfortunately filtermenu.js was using |. This can be deprecated after a while. + return (value ?? string.Empty).Split(new[] { ',', '|' }, StringSplitOptions.RemoveEmptyEntries).Select(i => new Guid(i)).ToArray(); + } + /// /// To the optimized result. /// @@ -75,17 +81,17 @@ namespace MediaBrowser.Api protected object ToOptimizedResult(T result) where T : class { - return ResultFactory.GetOptimizedResult(Request, result); + return ResultFactory.GetResult(Request, result); } - protected void AssertCanUpdateUser(IAuthorizationContext authContext, IUserManager userManager, string userId, bool restrictUserPreferences) + protected void AssertCanUpdateUser(IAuthorizationContext authContext, IUserManager userManager, Guid userId, bool restrictUserPreferences) { var auth = authContext.GetAuthorizationInfo(Request); - var authenticatedUser = userManager.GetUserById(auth.UserId); + var authenticatedUser = auth.User; // If they're going to update the record of another user, they must be an administrator - if (!string.Equals(userId, auth.UserId, StringComparison.OrdinalIgnoreCase)) + if (!userId.Equals(auth.UserId)) { if (!authenticatedUser.Policy.IsAdministrator) { @@ -101,25 +107,13 @@ namespace MediaBrowser.Api } } - /// - /// To the optimized serialized result using cache. - /// - /// - /// The result. - /// System.Object. - protected object ToOptimizedSerializedResultUsingCache(T result) - where T : class - { - return ToOptimizedResult(result); - } - /// /// Gets the session. /// /// SessionInfo. - protected async Task GetSession(ISessionContext sessionContext) + protected SessionInfo GetSession(ISessionContext sessionContext) { - var session = await sessionContext.GetSession(Request).ConfigureAwait(false); + var session = sessionContext.GetSession(Request); if (session == null) { @@ -133,38 +127,37 @@ namespace MediaBrowser.Api { var options = new DtoOptions(); - var authInfo = authContext.GetAuthorizationInfo(Request); - - options.DeviceId = authInfo.DeviceId; - var hasFields = request as IHasItemFields; if (hasFields != null) { options.Fields = hasFields.GetItemFields(); } - var client = authInfo.Client ?? string.Empty; - if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1) + if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount) || !options.ContainsField(Model.Querying.ItemFields.ChildCount)) { - var list = options.Fields.ToList(); - list.Add(Model.Querying.ItemFields.RecursiveItemCount); - options.Fields = list.ToArray(list.Count); - } + var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; + if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1) + { + var list = options.Fields.ToList(); + list.Add(Model.Querying.ItemFields.RecursiveItemCount); + options.Fields = list.ToArray(list.Count); + } - if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1) - { - var list = options.Fields.ToList(); - list.Add(Model.Querying.ItemFields.ChildCount); - options.Fields = list.ToArray(list.Count); + if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 || + client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1) + { + var list = options.Fields.ToList(); + list.Add(Model.Querying.ItemFields.ChildCount); + options.Fields = list.ToArray(list.Count); + } } var hasDtoOptions = request as IHasDtoOptions; @@ -289,7 +282,7 @@ namespace MediaBrowser.Api IncludeItemTypes = new[] { typeof(T).Name }, DtoOptions = dtoOptions - }).OfType().FirstOrDefault(); + }).OfType().FirstOrDefault(); if (result == null) { @@ -299,7 +292,7 @@ namespace MediaBrowser.Api IncludeItemTypes = new[] { typeof(T).Name }, DtoOptions = dtoOptions - }).OfType().FirstOrDefault(); + }).OfType().FirstOrDefault(); } if (result == null) @@ -310,10 +303,10 @@ namespace MediaBrowser.Api IncludeItemTypes = new[] { typeof(T).Name }, DtoOptions = dtoOptions - }).OfType().FirstOrDefault(); + }).OfType().FirstOrDefault(); } - return result as T; + return result; } protected string GetPathValue(int index) @@ -331,7 +324,7 @@ namespace MediaBrowser.Api return pathInfo[index]; } - private static List Parse(string pathUri) + private List Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs index 4602f56ed9..4eb69678a3 100644 --- a/MediaBrowser.Api/BrandingService.cs +++ b/MediaBrowser.Api/BrandingService.cs @@ -26,17 +26,15 @@ namespace MediaBrowser.Api public object Get(GetBrandingOptions request) { - var result = _config.GetConfiguration("branding"); - - return ToOptimizedResult(result); + return _config.GetConfiguration("branding"); } public object Get(GetBrandingCss request) { var result = _config.GetConfiguration("branding"); - // When null this throws a 405 error under Mono OSX, so default to empty string - return ResultFactory.GetResult(result.CustomCss ?? string.Empty, "text/css"); + // When null this throws a 405 error under Mono OSX, so default to empty string + return ResultFactory.GetResult(Request, result.CustomCss ?? string.Empty, "text/css"); } } } diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 2e8eb9e07d..0b9d5efa9f 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -11,6 +11,8 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Api.UserLibrary; using MediaBrowser.Model.Services; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Api { @@ -21,8 +23,8 @@ namespace MediaBrowser.Api /// Gets or sets the user id. /// /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -41,6 +43,8 @@ namespace MediaBrowser.Api [ApiMember(Name = "SupportsLatestItems", Description = "Optional. Filter by channels that support getting latest items.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? SupportsLatestItems { get; set; } + public bool? SupportsMediaDeletion { get; set; } + /// /// Gets or sets a value indicating whether this instance is favorite. /// @@ -74,7 +78,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -122,7 +126,7 @@ namespace MediaBrowser.Api /// Gets the order by. /// /// IEnumerable{ItemSortBy}. - public Tuple[] GetOrderBy() + public ValueTuple[] GetOrderBy() { return BaseItemsRequest.GetOrderBy(SortBy, SortOrder); } @@ -136,7 +140,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -178,21 +182,16 @@ namespace MediaBrowser.Api } } - [Route("/Channels/Folder", "GET", Summary = "Gets the users channel folder, along with configured images")] - public class GetChannelFolder : IReturn - { - [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - } - [Authenticated] public class ChannelService : BaseApiService { private readonly IChannelManager _channelManager; + private IUserManager _userManager; - public ChannelService(IChannelManager channelManager) + public ChannelService(IChannelManager channelManager, IUserManager userManager) { _channelManager = channelManager; + _userManager = userManager; } public object Get(GetAllChannelFeatures request) @@ -209,56 +208,132 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - public object Get(GetChannelFolder request) + public object Get(GetChannels request) { - return ToOptimizedResult(_channelManager.GetChannelFolder(request.UserId, CancellationToken.None)); - } - - public async Task Get(GetChannels request) - { - var result = await _channelManager.GetChannels(new ChannelQuery + var result = _channelManager.GetChannels(new ChannelQuery { Limit = request.Limit, StartIndex = request.StartIndex, UserId = request.UserId, SupportsLatestItems = request.SupportsLatestItems, + SupportsMediaDeletion = request.SupportsMediaDeletion, IsFavorite = request.IsFavorite - - }, CancellationToken.None).ConfigureAwait(false); + }); return ToOptimizedResult(result); } public async Task Get(GetChannelItems request) { - var result = await _channelManager.GetChannelItems(new ChannelItemQuery + var user = request.UserId.Equals(Guid.Empty) + ? null + : _userManager.GetUserById(request.UserId); + + var query = new InternalItemsQuery(user) { Limit = request.Limit, StartIndex = request.StartIndex, - UserId = request.UserId, - ChannelId = request.Id, - FolderId = request.FolderId, + ChannelIds = new Guid[] { new Guid(request.Id) }, + ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId), OrderBy = request.GetOrderBy(), - Filters = request.GetFilters().ToArray(), - Fields = request.GetItemFields() + DtoOptions = new Controller.Dto.DtoOptions + { + Fields = request.GetItemFields() + } - }, CancellationToken.None).ConfigureAwait(false); + }; + + foreach (var filter in request.GetFilters()) + { + switch (filter) + { + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + } + } + + var result = await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } public async Task Get(GetLatestChannelItems request) { - var result = await _channelManager.GetLatestChannelItems(new AllChannelMediaQuery + var user = request.UserId.Equals(Guid.Empty) + ? null + : _userManager.GetUserById(request.UserId); + + var query = new InternalItemsQuery(user) { Limit = request.Limit, StartIndex = request.StartIndex, - ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), - UserId = request.UserId, - Filters = request.GetFilters().ToArray(), - Fields = request.GetItemFields() + ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToArray(), + DtoOptions = new Controller.Dto.DtoOptions + { + Fields = request.GetItemFields() + } + }; - }, CancellationToken.None).ConfigureAwait(false); + foreach (var filter in request.GetFilters()) + { + switch (filter) + { + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + } + } + + var result = await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 0023c13d78..9d14558e3b 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -4,14 +4,11 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; -using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; - -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Services; +using System.Threading.Tasks; namespace MediaBrowser.Api { @@ -59,27 +56,13 @@ namespace MediaBrowser.Api } - [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")] - [Authenticated(Roles = "Admin")] - public class GetMetadataPlugins : IReturn - { - - } - - [Route("/System/Configuration/MetadataPlugins/Autoset", "POST")] - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] - public class AutoSetMetadataOptions : IReturnVoid - { - - } - [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")] [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] public class UpdateMediaEncoderPath : IReturnVoid { - [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Path { get; set; } - [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string PathType { get; set; } } @@ -132,10 +115,6 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - public void Post(AutoSetMetadataOptions request) - { - } - /// /// Posts the specified configuraiton. /// @@ -150,24 +129,19 @@ namespace MediaBrowser.Api _configurationManager.ReplaceConfiguration(config); } - public void Post(UpdateNamedConfiguration request) + public async Task Post(UpdateNamedConfiguration request) { var key = GetPathValue(2); var configurationType = _configurationManager.GetConfigurationType(key); - var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, configurationType); + var configuration = await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, configurationType).ConfigureAwait(false); _configurationManager.SaveConfiguration(key, configuration); } public object Get(GetDefaultMetadataOptions request) { - return ToOptimizedSerializedResultUsingCache(new MetadataOptions()); - } - - public object Get(GetMetadataPlugins request) - { - return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins()); + return ToOptimizedResult(new MetadataOptions()); } } } diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index c375e272a7..9cc16b4a1f 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -7,6 +7,8 @@ using MediaBrowser.Model.Session; using System.IO; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; namespace MediaBrowser.Api.Devices { @@ -16,6 +18,22 @@ namespace MediaBrowser.Api.Devices { } + [Route("/Devices/Info", "GET", Summary = "Gets info for a device")] + [Authenticated(Roles = "Admin")] + public class GetDeviceInfo : IReturn + { + [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Devices/Options", "GET", Summary = "Gets options for a device")] + [Authenticated(Roles = "Admin")] + public class GetDeviceOptions : IReturn + { + [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Id { get; set; } + } + [Route("/Devices", "DELETE", Summary = "Deletes a device")] public class DeleteDevice { @@ -35,37 +53,21 @@ namespace MediaBrowser.Api.Devices [Authenticated] public class PostCameraUpload : IRequiresRequestStream, IReturnVoid { - [ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string DeviceId { get; set; } - [ApiMember(Name = "Album", Description = "Album", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Album", Description = "Album", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Album { get; set; } - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Name { get; set; } - [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Id { get; set; } public Stream RequestStream { get; set; } } - [Route("/Devices/Info", "GET", Summary = "Gets device info")] - [Authenticated] - public class GetDeviceInfo : IReturn - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Devices/Capabilities", "GET", Summary = "Gets device capabilities")] - [Authenticated] - public class GetDeviceCapabilities : IReturn - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - [Route("/Devices/Options", "POST", Summary = "Updates device options")] [Authenticated(Roles = "Admin")] public class PostDeviceOptions : DeviceOptions, IReturnVoid @@ -77,29 +79,19 @@ namespace MediaBrowser.Api.Devices public class DeviceService : BaseApiService { private readonly IDeviceManager _deviceManager; + private readonly IAuthenticationRepository _authRepo; + private readonly ISessionManager _sessionManager; - public DeviceService(IDeviceManager deviceManager) + public DeviceService(IDeviceManager deviceManager, IAuthenticationRepository authRepo, ISessionManager sessionManager) { _deviceManager = deviceManager; + _authRepo = authRepo; + _sessionManager = sessionManager; } public void Post(PostDeviceOptions request) { - _deviceManager.UpdateDeviceInfo(request.Id, new DeviceOptions - { - CustomName = request.CustomName, - CameraUploadPath = request.CameraUploadPath - }); - } - - public object Get(GetDeviceInfo request) - { - return ToOptimizedResult(_deviceManager.GetDevice(request.Id)); - } - - public object Get(GetDeviceCapabilities request) - { - return ToOptimizedResult(_deviceManager.GetCapabilities(request.Id)); + _deviceManager.UpdateDeviceOptions(request.Id, request); } public object Get(GetDevices request) @@ -107,6 +99,16 @@ namespace MediaBrowser.Api.Devices return ToOptimizedResult(_deviceManager.GetDevices(request)); } + public object Get(GetDeviceInfo request) + { + return _deviceManager.GetDevice(request.Id); + } + + public object Get(GetDeviceOptions request) + { + return _deviceManager.GetDeviceOptions(request.Id); + } + public object Get(GetCameraUploads request) { return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId)); @@ -114,10 +116,19 @@ namespace MediaBrowser.Api.Devices public void Delete(DeleteDevice request) { - _deviceManager.DeleteDevice(request.Id); + var sessions = _authRepo.Get(new AuthenticationInfoQuery + { + DeviceId = request.Id + + }).Items; + + foreach (var session in sessions) + { + _sessionManager.Logout(session); + } } - public void Post(PostCameraUpload request) + public Task Post(PostCameraUpload request) { var deviceId = Request.QueryString["DeviceId"]; var album = Request.QueryString["Album"]; @@ -126,29 +137,25 @@ namespace MediaBrowser.Api.Devices if (Request.ContentType.IndexOf("multi", StringComparison.OrdinalIgnoreCase) == -1) { - var task = _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo + return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo { MimeType = Request.ContentType, Album = album, Name = name, Id = id }); - - Task.WaitAll(task); } else { var file = Request.Files.Length == 0 ? null : Request.Files[0]; - var task = _deviceManager.AcceptCameraUpload(deviceId, file.InputStream, new LocalFileInfo + return _deviceManager.AcceptCameraUpload(deviceId, file.InputStream, new LocalFileInfo { MimeType = file.ContentType, Album = album, Name = name, Id = id }); - - Task.WaitAll(task); } } } diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 4f8cc52558..6bc2f7e4af 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.Api { var result = _displayPreferencesManager.GetDisplayPreferences(request.Id, request.UserId, request.Client); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 400169ac55..ea3920d38d 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -36,18 +36,6 @@ namespace MediaBrowser.Api /// true if [include directories]; otherwise, false. [ApiMember(Name = "IncludeDirectories", Description = "An optional filter to include or exclude folders from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool IncludeDirectories { get; set; } - - /// - /// Gets or sets a value indicating whether [include hidden]. - /// - /// true if [include hidden]; otherwise, false. - [ApiMember(Name = "IncludeHidden", Description = "An optional filter to include or exclude hidden files and folders. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool IncludeHidden { get; set; } - - public GetDirectoryContents() - { - IncludeHidden = true; - } } [Route("/Environment/ValidatePath", "POST", Summary = "Gets the contents of a given directory in the file system")] @@ -189,18 +177,7 @@ namespace MediaBrowser.Api { var result = new DefaultDirectoryBrowserInfo(); - try - { - var qnap = "/share/CACHEDEV1_DATA"; - if (_fileSystem.DirectoryExists(qnap)) - { - result.Path = qnap; - } - } - catch - { - - } + result.Path = _fileSystem.DefaultDirectory; return ToOptimizedResult(result); } @@ -223,10 +200,10 @@ namespace MediaBrowser.Api if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf(UncSeparator) == 1) { - return ToOptimizedSerializedResultUsingCache(GetNetworkShares(path).OrderBy(i => i.Path).ToList()); + return ToOptimizedResult(GetNetworkShares(path).OrderBy(i => i.Path).ToList()); } - return ToOptimizedSerializedResultUsingCache(GetFileSystemEntries(request).ToList()); + return ToOptimizedResult(GetFileSystemEntries(request).ToList()); } public object Get(GetNetworkShares request) @@ -235,7 +212,7 @@ namespace MediaBrowser.Api var shares = GetNetworkShares(path).OrderBy(i => i.Path).ToList(); - return ToOptimizedSerializedResultUsingCache(shares); + return ToOptimizedResult(shares); } /// @@ -247,7 +224,7 @@ namespace MediaBrowser.Api { var result = GetDrives().ToList(); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -273,7 +250,7 @@ namespace MediaBrowser.Api { var result = _networkManager.GetNetworkDevices().ToList(); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -300,11 +277,6 @@ namespace MediaBrowser.Api { var entries = _fileSystem.GetFileSystemEntries(request.Path).OrderBy(i => i.FullName).Where(i => { - if (!request.IncludeHidden && i.IsHidden) - { - return false; - } - var isDirectory = i.IsDirectory; if (!request.IncludeFiles && !isDirectory) diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 585e9c49bc..59e203b7f7 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ParentId { get; set; } @@ -49,7 +49,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ParentId { get; set; } @@ -76,6 +76,7 @@ namespace MediaBrowser.Api public bool? IsKids { get; set; } public bool? IsNews { get; set; } public bool? IsSeries { get; set; } + public bool? Recursive { get; set; } } [Authenticated] @@ -93,7 +94,7 @@ namespace MediaBrowser.Api public object Get(GetQueryFilters request) { var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId); - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) || @@ -107,7 +108,6 @@ namespace MediaBrowser.Api var genreQuery = new InternalItemsQuery(user) { - AncestorIds = parentItem == null ? new string[] { } : new string[] { parentItem.Id.ToString("N") }, IncludeItemTypes = request.GetIncludeItemTypes(), DtoOptions = new Controller.Dto.DtoOptions { @@ -123,34 +123,44 @@ namespace MediaBrowser.Api IsSeries = request.IsSeries }; + // Non recursive not yet supported for library folders + if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder) + { + genreQuery.AncestorIds = parentItem == null ? Array.Empty() : new Guid[] { parentItem.Id }; + } + else + { + genreQuery.Parent = parentItem; + } + if (string.Equals(request.IncludeItemTypes, "MusicAlbum", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "MusicVideo", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "MusicArtist", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "Audio", StringComparison.OrdinalIgnoreCase)) { - filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameIdPair + filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair { Name = i.Item1.Name, - Id = i.Item1.Id.ToString("N") + Id = i.Item1.Id }).ToArray(); } else if (string.Equals(request.IncludeItemTypes, "Game", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "GameSystem", StringComparison.OrdinalIgnoreCase)) { - filters.Genres = _libraryManager.GetGameGenres(genreQuery).Items.Select(i => new NameIdPair + filters.Genres = _libraryManager.GetGameGenres(genreQuery).Items.Select(i => new NameGuidPair { Name = i.Item1.Name, - Id = i.Item1.Id.ToString("N") + Id = i.Item1.Id }).ToArray(); } else { - filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameIdPair + filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair { Name = i.Item1.Name, - Id = i.Item1.Id.ToString("N") + Id = i.Item1.Id }).ToArray(); } @@ -161,7 +171,7 @@ namespace MediaBrowser.Api public object Get(GetQueryFiltersLegacy request) { var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId); - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) || @@ -172,7 +182,7 @@ namespace MediaBrowser.Api } var item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : user.RootFolder : + user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() : parentItem; var result = ((Folder)item).GetItemList(GetItemsQuery(request, user)); diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index 6c48b732f6..3e4e205067 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -16,14 +16,6 @@ using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { - /// - /// Class GetSimilarGames - /// - [Route("/Games/{Id}/Similar", "GET", Summary = "Finds games similar to a given game.")] - public class GetSimilarGames : BaseGetSimilarItemsFromItem - { - } - /// /// Class GetGameSystemSummaries /// @@ -35,7 +27,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -109,11 +101,9 @@ namespace MediaBrowser.Api .Select(i => GetSummary(i, user)) .ToArray(); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Gets the summary. /// @@ -151,51 +141,5 @@ namespace MediaBrowser.Api return summary; } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSimilarGames request) - { - var result = GetSimilarItemsResult(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - private QueryResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) - { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) ? - (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : - _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Limit = request.Limit, - IncludeItemTypes = new[] - { - typeof(Game).Name - }, - SimilarTo = item, - DtoOptions = dtoOptions - - }); - - var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); - - var result = new QueryResult - { - Items = returnList, - - TotalRecordCount = itemsResult.Count - }; - - return result; - } } } diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs index b61c81972c..d4c01fdb02 100644 --- a/MediaBrowser.Api/Images/ImageRequest.cs +++ b/MediaBrowser.Api/Images/ImageRequest.cs @@ -87,18 +87,14 @@ namespace MediaBrowser.Api.Images /// Gets or sets the type of the image. /// /// The type of the image. - [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET,POST,DELETE")] public ImageType Type { get; set; } /// /// Gets or sets the index. /// /// The index. - [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")] + [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET,POST,DELETE")] public int? Index { get; set; } } } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2b8ac1a667..c3b2e82e7c 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -50,8 +50,8 @@ namespace MediaBrowser.Api.Images /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path")] + public Guid Id { get; set; } } /// @@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Images /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } /// @@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Images /// Gets or sets the new index. /// /// The new index. - [ApiMember(Name = "NewIndex", Description = "The new image index", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "NewIndex", Description = "The new image index", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public int NewIndex { get; set; } } @@ -105,8 +105,8 @@ namespace MediaBrowser.Api.Images [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")] [Route("/Studios/{Name}/Images/{Type}", "GET")] [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")] - [Route("/Years/{Year}/Images/{Type}", "GET")] - [Route("/Years/{Year}/Images/{Type}/{Index}", "GET")] + ////[Route("/Years/{Year}/Images/{Type}", "GET")] + ////[Route("/Years/{Year}/Images/{Type}/{Index}", "GET")] [Route("/Artists/{Name}/Images/{Type}", "HEAD")] [Route("/Artists/{Name}/Images/{Type}/{Index}", "HEAD")] [Route("/Genres/{Name}/Images/{Type}", "HEAD")] @@ -119,8 +119,8 @@ namespace MediaBrowser.Api.Images [Route("/Persons/{Name}/Images/{Type}/{Index}", "HEAD")] [Route("/Studios/{Name}/Images/{Type}", "HEAD")] [Route("/Studios/{Name}/Images/{Type}/{Index}", "HEAD")] - [Route("/Years/{Year}/Images/{Type}", "HEAD")] - [Route("/Years/{Year}/Images/{Type}/{Index}", "HEAD")] + ////[Route("/Years/{Year}/Images/{Type}", "HEAD")] + ////[Route("/Years/{Year}/Images/{Type}/{Index}", "HEAD")] public class GetItemByNameImage : ImageRequest { /// @@ -145,7 +145,7 @@ namespace MediaBrowser.Api.Images /// /// The id. [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -177,7 +177,7 @@ namespace MediaBrowser.Api.Images /// /// The id. [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -265,7 +265,7 @@ namespace MediaBrowser.Api.Images var result = GetItemImageInfos(item); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -373,11 +373,7 @@ namespace MediaBrowser.Api.Images /// System.Object. public object Get(GetItemImage request) { - var item = string.IsNullOrEmpty(request.Id) ? - _libraryManager.RootFolder : - _libraryManager.GetItemById(request.Id); - - return GetImage(request, item, false); + return GetImage(request, request.Id, null, false); } /// @@ -387,11 +383,7 @@ namespace MediaBrowser.Api.Images /// System.Object. public object Head(GetItemImage request) { - var item = string.IsNullOrEmpty(request.Id) ? - _libraryManager.RootFolder : - _libraryManager.GetItemById(request.Id); - - return GetImage(request, item, true); + return GetImage(request, request.Id, null, true); } /// @@ -403,14 +395,14 @@ namespace MediaBrowser.Api.Images { var item = _userManager.GetUserById(request.Id); - return GetImage(request, item, false); + return GetImage(request, Guid.Empty, item, false); } public object Head(GetUserImage request) { var item = _userManager.GetUserById(request.Id); - return GetImage(request, item, true); + return GetImage(request, Guid.Empty, item, true); } public object Get(GetItemByNameImage request) @@ -419,7 +411,7 @@ namespace MediaBrowser.Api.Images var item = GetItemByName(request.Name, type, _libraryManager, new DtoOptions(false)); - return GetImage(request, item, false); + return GetImage(request, item.Id, item, false); } public object Head(GetItemByNameImage request) @@ -428,32 +420,30 @@ namespace MediaBrowser.Api.Images var item = GetItemByName(request.Name, type, _libraryManager, new DtoOptions(false)); - return GetImage(request, item, true); + return GetImage(request, item.Id, item, true); } /// /// Posts the specified request. /// /// The request. - public void Post(PostUserImage request) + public Task Post(PostUserImage request) { var userId = GetPathValue(1); - AssertCanUpdateUser(_authContext, _userManager, userId, true); + AssertCanUpdateUser(_authContext, _userManager, new Guid(userId), true); request.Type = (ImageType)Enum.Parse(typeof(ImageType), GetPathValue(3), true); var item = _userManager.GetUserById(userId); - var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType); - - Task.WaitAll(task); + return PostImage(item, request.RequestStream, request.Type, Request.ContentType); } /// /// Posts the specified request. /// /// The request. - public void Post(PostItemImage request) + public Task Post(PostItemImage request) { var id = GetPathValue(1); @@ -461,9 +451,7 @@ namespace MediaBrowser.Api.Images var item = _libraryManager.GetItemById(id); - var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType); - - Task.WaitAll(task); + return PostImage(item, request.RequestStream, request.Type, Request.ContentType); } /// @@ -510,7 +498,7 @@ namespace MediaBrowser.Api.Images /// Index of the current. /// The new index. /// Task. - private void UpdateItemIndex(IHasMetadata item, ImageType type, int currentIndex, int newIndex) + private void UpdateItemIndex(BaseItem item, ImageType type, int currentIndex, int newIndex) { item.SwapImages(type, currentIndex, newIndex); } @@ -523,7 +511,7 @@ namespace MediaBrowser.Api.Images /// if set to true [is head request]. /// System.Object. /// - public Task GetImage(ImageRequest request, IHasMetadata item, bool isHeadRequest) + public Task GetImage(ImageRequest request, Guid itemId, BaseItem item, bool isHeadRequest) { if (request.PercentPlayed.HasValue) { @@ -549,18 +537,42 @@ namespace MediaBrowser.Api.Images } } + if (item == null) + { + item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N"))); + } + } + var imageInfo = GetImageInfo(request, item); if (imageInfo == null) { - throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type)); + var displayText = item == null ? itemId.ToString() : item.Name; + throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type)); } - var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type) : new List(); + IImageEnhancer[] supportedImageEnhancers; + + if (_imageProcessor.ImageEnhancers.Length > 0) + { + if (item == null) + { + item = _libraryManager.GetItemById(itemId); + } + + supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type) : Array.Empty(); + } + else + { + supportedImageEnhancers = Array.Empty(); + } var cropwhitespace = request.Type == ImageType.Logo || - request.Type == ImageType.Art - || (request.Type == ImageType.Primary && item is LiveTvChannel); + request.Type == ImageType.Art; if (request.CropWhitespace.HasValue) { @@ -583,6 +595,7 @@ namespace MediaBrowser.Api.Images }; return GetImageResult(item, + itemId, request, imageInfo, cropwhitespace, @@ -593,12 +606,13 @@ namespace MediaBrowser.Api.Images isHeadRequest); } - private async Task GetImageResult(IHasMetadata item, + private async Task GetImageResult(BaseItem item, + Guid itemId, ImageRequest request, ItemImageInfo image, bool cropwhitespace, ImageFormat[] supportedFormats, - List enhancers, + IImageEnhancer[] enhancers, TimeSpan? cacheDuration, IDictionary headers, bool isHeadRequest) @@ -611,8 +625,7 @@ namespace MediaBrowser.Api.Images ImageIndex = request.Index ?? 0, Image = image, Item = item, - ItemId = item.Id.ToString("N"), - ItemType = item.GetType().Name, + ItemId = itemId, MaxHeight = request.MaxHeight, MaxWidth = request.MaxWidth, Quality = request.Quality ?? 100, @@ -660,21 +673,15 @@ namespace MediaBrowser.Api.Images private ImageFormat[] GetClientSupportedFormats() { - //Logger.Debug("Request types: {0}", string.Join(",", Request.AcceptTypes ?? new string[] { })); - var supportsWebP = (Request.AcceptTypes ?? new string[] { }).Contains("image/webp", StringComparer.OrdinalIgnoreCase); + //Logger.Debug("Request types: {0}", string.Join(",", Request.AcceptTypes ?? Array.Empty())); + var supportedFormats = (Request.AcceptTypes ?? Array.Empty()).Select(i => i.Split(';')[0]).ToArray(); + var acceptParam = Request.QueryString["accept"]; - var userAgent = Request.UserAgent ?? string.Empty; - - if (!supportsWebP) - { - if (string.Equals(Request.QueryString["accept"], "webp", StringComparison.OrdinalIgnoreCase)) - { - supportsWebP = true; - } - } + var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); if (!supportsWebP) { + var userAgent = Request.UserAgent ?? string.Empty; if (userAgent.IndexOf("crosswalk", StringComparison.OrdinalIgnoreCase) != -1 && userAgent.IndexOf("android", StringComparison.OrdinalIgnoreCase) != -1) { @@ -682,16 +689,39 @@ namespace MediaBrowser.Api.Images } } + var formats = new List(4); + if (supportsWebP) { - // Not displaying properly on iOS - if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) == -1) - { - return new[] { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png }; - } + formats.Add(ImageFormat.Webp); } - return new[] { ImageFormat.Jpg, ImageFormat.Png }; + formats.Add(ImageFormat.Jpg); + formats.Add(ImageFormat.Png); + + if (SupportsFormat(supportedFormats, acceptParam, "gif", true)) + { + formats.Add(ImageFormat.Gif); + } + + return formats.ToArray(); + } + + private bool SupportsFormat(string[] requestAcceptTypes, string acceptParam, string format, bool acceptAll) + { + var mimeType = "image/" + format; + + if (requestAcceptTypes.Contains(mimeType)) + { + return true; + } + + if (acceptAll && requestAcceptTypes.Contains("*/*")) + { + return true; + } + + return string.Equals(Request.QueryString["accept"], format, StringComparison.OrdinalIgnoreCase); } /// @@ -700,7 +730,7 @@ namespace MediaBrowser.Api.Images /// The request. /// The item. /// System.String. - private ItemImageInfo GetImageInfo(ImageRequest request, IHasMetadata item) + private ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item) { var index = request.Index ?? 0; diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index 4782d76dfc..8d75ec10c7 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -73,13 +73,13 @@ namespace MediaBrowser.Api.Images public class BaseDownloadRemoteImage : IReturnVoid { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public ImageType Type { get; set; } - [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string ProviderName { get; set; } - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string ImageUrl { get; set; } } @@ -91,7 +91,7 @@ namespace MediaBrowser.Api.Images /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } } @@ -129,7 +129,7 @@ namespace MediaBrowser.Api.Images var result = GetImageProviders(item); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } private List GetImageProviders(BaseItem item) @@ -188,13 +188,11 @@ namespace MediaBrowser.Api.Images /// Posts the specified request. /// /// The request. - public void Post(DownloadRemoteImage request) + public Task Post(DownloadRemoteImage request) { var item = _libraryManager.GetItemById(request.Id); - var task = DownloadRemoteImage(item, request); - - Task.WaitAll(task); + return DownloadRemoteImage(item, request); } /// @@ -215,12 +213,7 @@ namespace MediaBrowser.Api.Images /// /// The request. /// System.Object. - public object Get(GetRemoteImage request) - { - return GetAsync(request).Result; - } - - public async Task GetAsync(GetRemoteImage request) + public async Task Get(GetRemoteImage request) { var urlHash = request.ImageUrl.GetMD5(); var pointerCachePath = GetFullCachePath(urlHash.ToString()); diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index a454642a42..39a7904866 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + public Guid Id { get; set; } } [Route("/Items/RemoteSearch/Movie", "POST")] @@ -44,9 +44,9 @@ namespace MediaBrowser.Api { } - [Route("/Items/RemoteSearch/AdultVideo", "POST")] + [Route("/Items/RemoteSearch/MusicVideo", "POST")] [Authenticated] - public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery, IReturn> + public class GetMusicVideoRemoteSearchResults : RemoteSearchQuery, IReturn> { } @@ -186,6 +186,13 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + public async Task Post(GetMusicVideoRemoteSearchResults request) + { + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); + + return ToOptimizedResult(result); + } + public async Task Post(GetPersonRemoteSearchResults request) { var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); @@ -212,7 +219,7 @@ namespace MediaBrowser.Api return GetRemoteImage(request); } - public void Post(ApplySearchCriteria request) + public Task Post(ApplySearchCriteria request) { var item = _libraryManager.GetItemById(new Guid(request.Id)); @@ -232,17 +239,15 @@ namespace MediaBrowser.Api //item.ProductionYear = request.ProductionYear; //item.Name = request.Name; - var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem) + return _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem) { MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = ImageRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllMetadata = true, ReplaceAllImages = request.ReplaceAllImages, - SearchResult = request, - ForceEnableInternetMetadata = true + SearchResult = request }, CancellationToken.None); - Task.WaitAll(task); } /// diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index d26fb768a3..ab083c207d 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Api public MetadataRefreshMode MetadataRefreshMode { get; set; } [ApiMember(Name = "ImageRefreshMode", Description = "Specifies the image refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public ImageRefreshMode ImageRefreshMode { get; set; } + public MetadataRefreshMode ImageRefreshMode { get; set; } [ApiMember(Name = "ReplaceAllMetadata", Description = "Determines if metadata should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool ReplaceAllMetadata { get; set; } @@ -73,9 +73,8 @@ namespace MediaBrowser.Api ImageRefreshMode = request.ImageRefreshMode, ReplaceAllImages = request.ReplaceAllImages, ReplaceAllMetadata = request.ReplaceAllMetadata, - ForceSave = request.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || request.ImageRefreshMode == ImageRefreshMode.FullRefresh || request.ReplaceAllImages || request.ReplaceAllMetadata, - IsAutomated = false, - ValidateChildren = request.Recursive + ForceSave = request.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || request.ImageRefreshMode == MetadataRefreshMode.FullRefresh || request.ReplaceAllImages || request.ReplaceAllMetadata, + IsAutomated = false }; } } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index a55741d7dc..22eb7ea099 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -15,6 +15,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; +using MediaBrowser.Model.IO; namespace MediaBrowser.Api { @@ -36,7 +37,7 @@ namespace MediaBrowser.Api public class UpdateItemContentType : IReturnVoid { [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string ItemId { get; set; } + public Guid ItemId { get; set; } [ApiMember(Name = "ContentType", Description = "The content type of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string ContentType { get; set; } @@ -49,13 +50,15 @@ namespace MediaBrowser.Api private readonly IProviderManager _providerManager; private readonly ILocalizationManager _localizationManager; private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; - public ItemUpdateService(ILibraryManager libraryManager, IProviderManager providerManager, ILocalizationManager localizationManager, IServerConfigurationManager config) + public ItemUpdateService(IFileSystem fileSystem, ILibraryManager libraryManager, IProviderManager providerManager, ILocalizationManager localizationManager, IServerConfigurationManager config) { _libraryManager = libraryManager; _providerManager = providerManager; _localizationManager = localizationManager; _config = config; + _fileSystem = fileSystem; } public object Get(GetMetadataEditorInfo request) @@ -199,6 +202,9 @@ namespace MediaBrowser.Api var newLockData = request.LockData ?? false; var isLockedChanged = item.IsLocked != newLockData; + var series = item as Series; + var displayOrderChanged = series != null && !string.Equals(series.DisplayOrder ?? string.Empty, request.DisplayOrder ?? string.Empty, StringComparison.OrdinalIgnoreCase); + // Do this first so that metadata savers can pull the updates from the database. if (request.People != null) { @@ -221,6 +227,17 @@ namespace MediaBrowser.Api child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } } + + if (displayOrderChanged) + { + _providerManager.QueueRefresh(series.Id, new MetadataRefreshOptions(_fileSystem) + { + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + ReplaceAllMetadata = true + + }, RefreshPriority.High); + } } private DateTime NormalizeDateTime(DateTime val) @@ -238,7 +255,6 @@ namespace MediaBrowser.Api item.CriticRating = request.CriticRating; item.CommunityRating = request.CommunityRating; - item.HomePageUrl = request.HomePageUrl; item.IndexNumber = request.IndexNumber; item.ParentIndexNumber = request.ParentIndexNumber; item.Overview = request.Overview; @@ -247,12 +263,9 @@ namespace MediaBrowser.Api var episode = item as Episode; if (episode != null) { - episode.DvdSeasonNumber = request.DvdSeasonNumber; - episode.DvdEpisodeNumber = request.DvdEpisodeNumber; episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber; episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber; episode.AirsBeforeSeasonNumber = request.AirsBeforeSeasonNumber; - episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber; } item.Tags = request.Tags; diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index a036a00a6e..cc8c1251fe 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -30,6 +30,8 @@ using MediaBrowser.Model.Services; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; namespace MediaBrowser.Api.Library { @@ -50,7 +52,7 @@ namespace MediaBrowser.Api.Library /// [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")] [Authenticated] - public class GetCriticReviews : IReturn> + public class GetCriticReviews : IReturn> { /// /// Gets or sets the id. @@ -86,7 +88,7 @@ namespace MediaBrowser.Api.Library /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -111,7 +113,7 @@ namespace MediaBrowser.Api.Library /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -136,7 +138,7 @@ namespace MediaBrowser.Api.Library /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -167,7 +169,7 @@ namespace MediaBrowser.Api.Library [Authenticated] public class DeleteItems : IReturnVoid { - [ApiMember(Name = "Ids", Description = "Ids", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + [ApiMember(Name = "Ids", Description = "Ids", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] public string Ids { get; set; } } @@ -176,7 +178,7 @@ namespace MediaBrowser.Api.Library public class GetItemCounts : IReturn { [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsFavorite { get; set; } @@ -191,7 +193,7 @@ namespace MediaBrowser.Api.Library /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -201,21 +203,6 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } - [Route("/Items/YearIndex", "GET", Summary = "Gets a year index based on an item query.")] - [Authenticated] - public class GetYearIndex : IReturn> - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - } - /// /// Class GetPhyscialPaths /// @@ -238,7 +225,7 @@ namespace MediaBrowser.Api.Library [Authenticated] public class PostUpdatedSeries : IReturnVoid { - [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] public string TvdbId { get; set; } } @@ -247,12 +234,28 @@ namespace MediaBrowser.Api.Library [Authenticated] public class PostUpdatedMovies : IReturnVoid { - [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] public string TmdbId { get; set; } - [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] public string ImdbId { get; set; } } + public class MediaUpdateInfo + { + public string Path { get; set; } + + // Created, Modified, Deleted + public string UpdateType { get; set; } + } + + [Route("/Library/Media/Updated", "POST", Summary = "Reports that new movies have been added by an external source")] + [Authenticated] + public class PostUpdatedMedia : IReturnVoid + { + [ApiMember(Name = "Updates", Description = "A list of updated media paths", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] + public List Updates { get; set; } + } + [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")] [Authenticated(Roles = "download")] public class GetDownload @@ -265,12 +268,49 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } + [Route("/Games/{Id}/Similar", "GET", Summary = "Finds games similar to a given game.")] + [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")] + [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] + [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")] + [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")] + [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] [Authenticated] public class GetSimilarItems : BaseGetSimilarItemsFromItem { } + [Route("/Libraries/AvailableOptions", "GET")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class GetLibraryOptionsInfo : IReturn + { + public string LibraryContentType { get; set; } + public bool IsNewLibrary { get; set; } + } + + public class LibraryOptionInfo + { + public string Name { get; set; } + public bool DefaultEnabled { get; set; } + } + + public class LibraryOptionsResult + { + public LibraryOptionInfo[] MetadataSavers { get; set; } + public LibraryOptionInfo[] MetadataReaders { get; set; } + public LibraryOptionInfo[] SubtitleFetchers { get; set; } + public LibraryTypeOptions[] TypeOptions { get; set; } + } + + public class LibraryTypeOptions + { + public string Type { get; set; } + public LibraryOptionInfo[] MetadataFetchers { get; set; } + public LibraryOptionInfo[] ImageFetchers { get; set; } + public ImageType[] SupportedImageTypes { get; set; } + public ImageOption[] DefaultImageOptions { get; set; } + } + /// /// Class LibraryService /// @@ -294,11 +334,12 @@ namespace MediaBrowser.Api.Library private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; + private readonly IProviderManager _providerManager; /// /// Initializes a new instance of the class. /// - public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, + public LibraryService(IProviderManager providerManager, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, IServerConfigurationManager config) { _itemRepo = itemRepo; @@ -314,63 +355,287 @@ namespace MediaBrowser.Api.Library _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _config = config; + _providerManager = providerManager; + } + + private string[] GetRepresentativeItemTypes(string contentType) + { + switch (contentType) + { + case CollectionType.BoxSets: + return new string[] { "BoxSet" }; + case CollectionType.Playlists: + return new string[] { "Playlist" }; + case CollectionType.Movies: + return new string[] { "Movie" }; + case CollectionType.TvShows: + return new string[] { "Series", "Season", "Episode" }; + case CollectionType.Books: + return new string[] { "Book" }; + case CollectionType.Games: + return new string[] { "Game", "GameSystem" }; + case CollectionType.Music: + return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" }; + case CollectionType.HomeVideos: + case CollectionType.Photos: + return new string[] { "Video", "Photo" }; + case CollectionType.MusicVideos: + return new string[] { "MusicVideo" }; + default: + return new string[] { "Series", "Season", "Episode", "Movie" }; + } + } + + private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary) + { + if (isNewLibrary) + { + return false; + } + + var metadataOptions = _config.Configuration.MetadataOptions + .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + .ToArray(); + + if (metadataOptions.Length == 0) + { + return true; + } + + return metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase)); + } + + private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary) + { + if (isNewLibrary) + { + if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return true; + } + else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + + var metadataOptions = _config.Configuration.MetadataOptions + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + if (metadataOptions.Length == 0) + { + return true; + } + + return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); + } + + private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) + { + if (isNewLibrary) + { + if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return true; + } + else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + else if (string.Equals(name, "FanArt", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return true; + } + else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + + var metadataOptions = _config.Configuration.MetadataOptions + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + if (metadataOptions.Length == 0) + { + return true; + } + + return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); + } + + public object Get(GetLibraryOptionsInfo request) + { + var result = new LibraryOptionsResult(); + + var types = GetRepresentativeItemTypes(request.LibraryContentType); + var isNewLibrary = request.IsNewLibrary; + var typesList = types.ToList(); + + var plugins = _providerManager.GetAllMetadataPlugins() + .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase)) + .OrderBy(i => typesList.IndexOf(i.ItemType)) + .ToList(); + + result.MetadataSavers = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver)) + .Select(i => new LibraryOptionInfo + { + Name = i.Name, + DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary) + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + result.MetadataReaders = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider)) + .Select(i => new LibraryOptionInfo + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + result.SubtitleFetchers = plugins + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher)) + .Select(i => new LibraryOptionInfo + { + Name = i.Name, + DefaultEnabled = true + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + var typeOptions = new List(); + + foreach (var type in types) + { + ImageOption[] defaultImageOptions = null; + TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions); + + typeOptions.Add(new LibraryTypeOptions + { + Type = type, + + MetadataFetchers = plugins + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher)) + .Select(i => new LibraryOptionInfo + { + Name = i.Name, + DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary) + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(), + + ImageFetchers = plugins + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher)) + .Select(i => new LibraryOptionInfo + { + Name = i.Name, + DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary) + }) + .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(), + + SupportedImageTypes = plugins + .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) + .SelectMany(i => i.SupportedImageTypes ?? Array.Empty()) + .Distinct() + .ToArray(), + + DefaultImageOptions = defaultImageOptions ?? Array.Empty() + }); + } + + result.TypeOptions = typeOptions.ToArray(); + + return result; } public object Get(GetSimilarItems request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? - (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : + (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - if (item is Game) - { - return new GamesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _authContext) - { - Request = Request, - - }.Get(new GetSimilarGames - { - Fields = request.Fields, - Id = request.Id, - Limit = request.Limit, - UserId = request.UserId, - ImageTypeLimit = request.ImageTypeLimit - }); - } - if (item is MusicAlbum) - { - return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _authContext) - { - Request = Request, - - }.Get(new GetSimilarAlbums - { - Fields = request.Fields, - Id = request.Id, - Limit = request.Limit, - UserId = request.UserId, - ExcludeArtistIds = request.ExcludeArtistIds, - ImageTypeLimit = request.ImageTypeLimit - }); - } - if (item is MusicArtist) - { - return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _authContext) - { - Request = Request, - - }.Get(new GetSimilarArtists - { - Fields = request.Fields, - Id = request.Id, - Limit = request.Limit, - UserId = request.UserId, - ImageTypeLimit = request.ImageTypeLimit - }); - } - var program = item as IHasProgramAttributes; if (item is Movie || (program != null && program.IsMovie) || item is Trailer) @@ -379,33 +644,70 @@ namespace MediaBrowser.Api.Library { Request = Request, - }.Get(new GetSimilarMovies - { - Fields = request.Fields, - Id = request.Id, - Limit = request.Limit, - UserId = request.UserId, - ImageTypeLimit = request.ImageTypeLimit - }); + }.GetSimilarItemsResult(request); } - if (item is Series || (program != null && program.IsSeries)) + if (program != null && program.IsSeries) { - return new TvShowsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _tvManager, _authContext) - { - Request = Request, - - }.Get(new GetSimilarShows - { - Fields = request.Fields, - Id = request.Id, - Limit = request.Limit, - UserId = request.UserId, - ImageTypeLimit = request.ImageTypeLimit - }); + return GetSimilarItemsResult(request, new[] { typeof(Series).Name }); } - return new QueryResult(); + if (item is Episode || (item is IItemByName && !(item is MusicArtist))) + { + return new QueryResult(); + } + + return GetSimilarItemsResult(request, new[] { item.GetType().Name }); + } + + private QueryResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, string[] includeItemTypes) + { + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; + + var item = string.IsNullOrEmpty(request.Id) ? + (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : + _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + + var dtoOptions = GetDtoOptions(_authContext, request); + + var query = new InternalItemsQuery(user) + { + Limit = request.Limit, + IncludeItemTypes = includeItemTypes, + SimilarTo = item, + DtoOptions = dtoOptions, + EnableTotalRecordCount = false + }; + + // ExcludeArtistIds + if (!string.IsNullOrEmpty(request.ExcludeArtistIds)) + { + query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds); + } + + List itemsResult; + + if (item is MusicArtist) + { + query.IncludeItemTypes = Array.Empty(); + + itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); + } + else + { + itemsResult = _libraryManager.GetItemList(query); + } + + var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); + + var result = new QueryResult + { + Items = returnList, + + TotalRecordCount = itemsResult.Count + }; + + return result; } public object Get(GetMediaFolders request) @@ -428,7 +730,7 @@ namespace MediaBrowser.Api.Library Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray() }; - return ToOptimizedResult(result); + return result; } public void Post(PostUpdatedSeries request) @@ -443,17 +745,21 @@ namespace MediaBrowser.Api.Library }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); - if (series.Length > 0) + foreach (var item in series) { - foreach (var item in series) + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + } + + public void Post(PostUpdatedMedia request) + { + if (request.Updates != null) + { + foreach (var item in request.Updates) { _libraryMonitor.ReportFileSystemChanged(item.Path); } } - else - { - Task.Run(() => _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None)); - } } public void Post(PostUpdatedMovies request) @@ -481,16 +787,9 @@ namespace MediaBrowser.Api.Library movies = new List(); } - if (movies.Count > 0) + foreach (var item in movies) { - foreach (var item in movies) - { - _libraryMonitor.ReportFileSystemChanged(item.Path); - } - } - else - { - Task.Run(() => _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None)); + _libraryMonitor.ReportFileSystemChanged(item.Path); } } @@ -499,7 +798,7 @@ namespace MediaBrowser.Api.Library var item = _libraryManager.GetItemById(request.Id); var auth = _authContext.GetAuthorizationInfo(Request); - var user = _userManager.GetUserById(auth.UserId); + var user = auth.User; if (user != null) { @@ -561,15 +860,6 @@ namespace MediaBrowser.Api.Library public Task Get(GetFile request) { var item = _libraryManager.GetItemById(request.Id); - var locationType = item.LocationType; - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) - { - throw new ArgumentException("This command cannot be used for remote or virtual items."); - } - if (_fileSystem.DirectoryExists(item.Path)) - { - throw new ArgumentException("This command cannot be used for directories."); - } return ResultFactory.GetStaticFileResult(Request, item.Path); } @@ -585,7 +875,7 @@ namespace MediaBrowser.Api.Library .SelectMany(c => c.PhysicalLocations) .ToList(); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -597,7 +887,7 @@ namespace MediaBrowser.Api.Library { var result = GetAncestors(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -611,7 +901,7 @@ namespace MediaBrowser.Api.Library var baseItemDtos = new List(); - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var dtoOptions = GetDtoOptions(_authContext, request); @@ -636,7 +926,7 @@ namespace MediaBrowser.Api.Library { if (item.GetParent() is AggregateFolder) { - return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)); + return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)); } return item; @@ -649,9 +939,7 @@ namespace MediaBrowser.Api.Library /// System.Object. public object Get(GetCriticReviews request) { - var result = GetCriticReviews(request); - - return ToOptimizedSerializedResultUsingCache(result); + return new QueryResult(); } /// @@ -661,7 +949,7 @@ namespace MediaBrowser.Api.Library /// System.Object. public object Get(GetItemCounts request) { - var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId); + var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); var counts = new ItemCounts { @@ -677,7 +965,7 @@ namespace MediaBrowser.Api.Library BookCount = GetCount(typeof(Book), user, request) }; - return ToOptimizedSerializedResultUsingCache(counts); + return ToOptimizedResult(counts); } private int GetCount(Type type, User user, GetItemCounts request) @@ -724,14 +1012,14 @@ namespace MediaBrowser.Api.Library public void Delete(DeleteItems request) { var ids = string.IsNullOrWhiteSpace(request.Ids) - ? new string[] { } + ? Array.Empty() : request.Ids.Split(','); - var tasks = ids.Select(i => + foreach (var i in ids) { var item = _libraryManager.GetItemById(i); var auth = _authContext.GetAuthorizationInfo(Request); - var user = _userManager.GetUserById(auth.UserId); + var user = auth.User; if (!item.CanDelete(user)) { @@ -740,17 +1028,15 @@ namespace MediaBrowser.Api.Library throw new SecurityException("Unauthorized access"); } - return Task.FromResult(true); + continue; } - return item.Delete(new DeleteOptions + _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = true - }); - }).ToArray(ids.Length); - - Task.WaitAll(tasks); + }, true); + } } /// @@ -765,36 +1051,6 @@ namespace MediaBrowser.Api.Library }); } - /// - /// Gets the critic reviews async. - /// - /// The request. - /// Task{ItemReviewsResult}. - private QueryResult GetCriticReviews(GetCriticReviews request) - { - var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id)); - - var reviewsArray = reviews.ToArray(reviews.Count); - - var result = new QueryResult - { - TotalRecordCount = reviewsArray.Length - }; - - if (request.StartIndex.HasValue) - { - reviewsArray = reviewsArray.Skip(request.StartIndex.Value).ToArray(); - } - if (request.Limit.HasValue) - { - reviewsArray = reviewsArray.Take(request.Limit.Value).ToArray(); - } - - result.Items = reviewsArray; - - return result; - } - public object Get(GetThemeMedia request) { var themeSongs = GetThemeSongs(new GetThemeSongs @@ -813,7 +1069,7 @@ namespace MediaBrowser.Api.Library }); - return ToOptimizedSerializedResultUsingCache(new AllThemeMediaResult + return ToOptimizedResult(new AllThemeMediaResult { ThemeSongsResult = themeSongs, ThemeVideosResult = themeVideos, @@ -831,16 +1087,16 @@ namespace MediaBrowser.Api.Library { var result = GetThemeSongs(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } private ThemeMediaResult GetThemeSongs(GetThemeSongs request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) - ? (!string.IsNullOrWhiteSpace(request.UserId) - ? user.RootFolder + ? (!request.UserId.Equals(Guid.Empty) + ? _libraryManager.GetUserRootFolder() : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); @@ -849,16 +1105,33 @@ namespace MediaBrowser.Api.Library throw new ResourceNotFoundException("Item not found."); } - while (item.ThemeSongIds.Length == 0 && request.InheritFromParent && item.GetParent() != null) + BaseItem[] themeItems = Array.Empty(); + + while (true) { - item = item.GetParent(); + themeItems = item.GetThemeSongs().ToArray(); + + if (themeItems.Length > 0) + { + break; + } + + if (!request.InheritFromParent) + { + break; + } + + var parent = item.GetParent(); + if (parent == null) + { + break; + } + item = parent; } var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = item.ThemeSongIds.Select(_libraryManager.GetItemById) - .Where(i => i != null) - .OrderBy(i => i.SortName) + var dtos = themeItems .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); var items = dtos.ToArray(); @@ -867,7 +1140,7 @@ namespace MediaBrowser.Api.Library { Items = items, TotalRecordCount = items.Length, - OwnerId = _dtoService.GetDtoId(item) + OwnerId = item.Id }; } @@ -880,16 +1153,16 @@ namespace MediaBrowser.Api.Library { var result = GetThemeVideos(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public ThemeMediaResult GetThemeVideos(GetThemeVideos request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) - ? (!string.IsNullOrWhiteSpace(request.UserId) - ? user.RootFolder + ? (!request.UserId.Equals(Guid.Empty) + ? _libraryManager.GetUserRootFolder() : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); @@ -898,16 +1171,33 @@ namespace MediaBrowser.Api.Library throw new ResourceNotFoundException("Item not found."); } - while (item.ThemeVideoIds.Length == 0 && request.InheritFromParent && item.GetParent() != null) + BaseItem[] themeItems = Array.Empty(); + + while (true) { - item = item.GetParent(); + themeItems = item.GetThemeVideos().ToArray(); + + if (themeItems.Length > 0) + { + break; + } + + if (!request.InheritFromParent) + { + break; + } + + var parent = item.GetParent(); + if (parent == null) + { + break; + } + item = parent; } var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = item.ThemeVideoIds.Select(_libraryManager.GetItemById) - .Where(i => i != null) - .OrderBy(i => i.SortName) + var dtos = themeItems .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); var items = dtos.ToArray(); @@ -916,43 +1206,8 @@ namespace MediaBrowser.Api.Library { Items = items, TotalRecordCount = items.Length, - OwnerId = _dtoService.GetDtoId(item) + OwnerId = item.Id }; } - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public object Get(GetYearIndex request) - { - var includeTypes = string.IsNullOrWhiteSpace(request.IncludeItemTypes) - ? new string[] { } - : request.IncludeItemTypes.Split(','); - - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; - - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = includeTypes, - Recursive = true, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - }; - - var items = _libraryManager.GetItemList(query); - - var lookup = items - .ToLookup(i => i.ProductionYear ?? -1) - .OrderBy(i => i.Key) - .Select(i => new ItemIndex - { - ItemCount = i.Count(), - Name = i.Key == -1 ? string.Empty : i.Key.ToString(_usCulture) - }) - .ToList(); - - return ToOptimizedSerializedResultUsingCache(lookup); - } } } diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index ae488f066c..bba89acec6 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -210,7 +210,7 @@ namespace MediaBrowser.Api.Library { var result = _libraryManager.GetVirtualFolders(true); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public void Post(UpdateLibraryOptions request) @@ -224,7 +224,7 @@ namespace MediaBrowser.Api.Library /// Posts the specified request. /// /// The request. - public void Post(AddVirtualFolder request) + public Task Post(AddVirtualFolder request) { var libraryOptions = request.LibraryOptions ?? new LibraryOptions(); @@ -233,7 +233,7 @@ namespace MediaBrowser.Api.Library libraryOptions.PathInfos = request.Paths.Select(i => new MediaPathInfo { Path = i }).ToArray(); } - _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, libraryOptions, request.RefreshLibrary); + return _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, libraryOptions, request.RefreshLibrary); } /// @@ -264,27 +264,27 @@ namespace MediaBrowser.Api.Library if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && _fileSystem.DirectoryExists(newPath)) { - throw new ArgumentException("There is already a media collection with the name " + newPath + "."); + throw new ArgumentException("Media library already exists at " + newPath + "."); } _libraryMonitor.Stop(); try { - // Only make a two-phase move when changing capitalization + // Changing capitalization. Handle windows case insensitivity if (string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase)) { - //Create an unique name - var temporaryName = Guid.NewGuid().ToString(); - var temporaryPath = Path.Combine(rootFolderPath, temporaryName); - _fileSystem.MoveDirectory(currentPath, temporaryPath); - currentPath = temporaryPath; + var tempPath = Path.Combine(rootFolderPath, Guid.NewGuid().ToString("N")); + _fileSystem.MoveDirectory(currentPath, tempPath); + currentPath = tempPath; } _fileSystem.MoveDirectory(currentPath, newPath); } finally { + CollectionFolder.OnCollectionFolderChange(); + Task.Run(() => { // No need to start if scanning the library because it will handle it @@ -309,9 +309,9 @@ namespace MediaBrowser.Api.Library /// Deletes the specified request. /// /// The request. - public void Delete(RemoveVirtualFolder request) + public Task Delete(RemoveVirtualFolder request) { - _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary); + return _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary); } /// diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index fee52ea5ee..510c5f135b 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -17,12 +17,15 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Api.UserLibrary; using MediaBrowser.Model.IO; - +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Cryptography; +using System.Text; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Api.LiveTv { @@ -37,13 +40,13 @@ namespace MediaBrowser.Api.LiveTv [Route("/LiveTv/Channels", "GET", Summary = "Gets available live tv channels.")] [Authenticated] - public class GetChannels : IReturn>, IHasDtoOptions + public class GetChannels : IReturn>, IHasDtoOptions { [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public ChannelType? Type { get; set; } [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -122,7 +125,7 @@ namespace MediaBrowser.Api.LiveTv if (string.IsNullOrEmpty(val)) { - return new string[] { }; + return Array.Empty(); } return val.Split(','); @@ -136,7 +139,7 @@ namespace MediaBrowser.Api.LiveTv [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")] [Authenticated] - public class GetChannel : IReturn + public class GetChannel : IReturn { /// /// Gets or sets the id. @@ -146,7 +149,7 @@ namespace MediaBrowser.Api.LiveTv public string Id { get; set; } [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } [Route("/LiveTv/Recordings", "GET", Summary = "Gets live tv recordings")] @@ -157,10 +160,7 @@ namespace MediaBrowser.Api.LiveTv public string ChannelId { get; set; } [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - - [ApiMember(Name = "GroupId", Description = "Optional filter by recording group.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string GroupId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? StartIndex { get; set; } @@ -274,6 +274,14 @@ namespace MediaBrowser.Api.LiveTv public string UserId { get; set; } } + [Route("/LiveTv/Recordings/Folders", "GET", Summary = "Gets recording folders")] + [Authenticated] + public class GetRecordingFolders : IReturn + { + [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } + } + [Route("/LiveTv/Recordings/{Id}", "GET", Summary = "Gets a live tv recording")] [Authenticated] public class GetRecording : IReturn @@ -282,7 +290,7 @@ namespace MediaBrowser.Api.LiveTv public string Id { get; set; } [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } [Route("/LiveTv/Tuners/{Id}/Reset", "POST", Summary = "Resets a tv tuner")] @@ -332,13 +340,14 @@ namespace MediaBrowser.Api.LiveTv public string ChannelIds { get; set; } [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string UserId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string MinStartDate { get; set; } [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasAired { get; set; } + public bool? IsAiring { get; set; } [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string MaxStartDate { get; set; } @@ -397,7 +406,7 @@ namespace MediaBrowser.Api.LiveTv public bool? EnableUserData { get; set; } public string SeriesTimerId { get; set; } - public string LibrarySeriesId { get; set; } + public Guid LibrarySeriesId { get; set; } /// /// Fields to return within the items, in addition to basic information @@ -424,7 +433,7 @@ namespace MediaBrowser.Api.LiveTv } [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string UserId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } @@ -481,7 +490,7 @@ namespace MediaBrowser.Api.LiveTv public string Id { get; set; } [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } @@ -489,15 +498,15 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class DeleteRecording : IReturnVoid { - [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public Guid Id { get; set; } } [Route("/LiveTv/Timers/{Id}", "DELETE", Summary = "Cancels a live tv timer")] [Authenticated] public class CancelTimer : IReturnVoid { - [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } @@ -536,7 +545,7 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class CancelSeriesTimer : IReturnVoid { - [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } @@ -566,14 +575,6 @@ namespace MediaBrowser.Api.LiveTv { } - [Route("/LiveTv/Folder", "GET", Summary = "Gets the users live tv folder, along with configured images")] - [Authenticated] - public class GetLiveTvFolder : IReturn - { - [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - } - [Route("/LiveTv/TunerHosts", "POST", Summary = "Adds a tuner host")] [Authenticated] public class AddTunerHost : TunerHostInfo, IReturn @@ -600,6 +601,7 @@ namespace MediaBrowser.Api.LiveTv { public bool ValidateLogin { get; set; } public bool ValidateListings { get; set; } + public string Pw { get; set; } } [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")] @@ -637,7 +639,7 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class GetChannelMappingOptions { - [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")] public string ProviderId { get; set; } } @@ -645,7 +647,7 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class SetChannelMapping { - [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")] public string ProviderId { get; set; } public string TunerChannelId { get; set; } public string ProviderChannelId { get; set; } @@ -659,14 +661,6 @@ namespace MediaBrowser.Api.LiveTv public string ProviderName { get; set; } } - [Route("/LiveTv/Registration", "GET")] - [Authenticated] - public class GetLiveTvRegistrationInfo : IReturn - { - [ApiMember(Name = "Feature", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Feature { get; set; } - } - [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")] public class GetLiveStreamFile { @@ -706,8 +700,11 @@ namespace MediaBrowser.Api.LiveTv private readonly IAuthorizationContext _authContext; private readonly ISessionContext _sessionContext; private readonly IEnvironmentInfo _environment; + private ICryptoProvider _cryptographyProvider; + private IStreamHelper _streamHelper; + private IMediaSourceManager _mediaSourceManager; - public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext, IEnvironmentInfo environment) + public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext, IEnvironmentInfo environment) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -719,6 +716,9 @@ namespace MediaBrowser.Api.LiveTv _authContext = authContext; _sessionContext = sessionContext; _environment = environment; + _cryptographyProvider = crypto; + _streamHelper = streamHelper; + _mediaSourceManager = mediaSourceManager; } public object Get(GetTunerHostTypes request) @@ -727,6 +727,22 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(list); } + public object Get(GetRecordingFolders request) + { + var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); + var folders = _liveTvManager.GetRecordingFolders(user); + + var returnArray = _dtoService.GetBaseItemDtos(folders.ToArray(), new DtoOptions(), user); + + var result = new QueryResult + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + + return ToOptimizedResult(result); + } + public object Get(GetLiveRecordingFile request) { var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id); @@ -740,7 +756,7 @@ namespace MediaBrowser.Api.LiveTv outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); - return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment) + return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment) { AllowEndOfFile = false }; @@ -754,12 +770,15 @@ namespace MediaBrowser.Api.LiveTv public async Task Get(GetLiveStreamFile request) { - var directStreamProvider = (await _liveTvManager.GetEmbyTvLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider; + var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(request.Id, CancellationToken.None).ConfigureAwait(false); + + var directStreamProvider = liveStreamInfo; + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); - return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment) + return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment) { AllowEndOfFile = false }; @@ -770,13 +789,6 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(new ListingsProviderInfo()); } - public async Task Get(GetLiveTvRegistrationInfo request) - { - var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - public async Task Post(SetChannelMapping request) { return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelId, request.ProviderChannelId).ConfigureAwait(false); @@ -828,12 +840,12 @@ namespace MediaBrowser.Api.LiveTv }).ConfigureAwait(false); - return ResultFactory.GetResult(response, "application/json"); + return ResultFactory.GetResult(Request, response, "application/json"); } private void AssertUserCanManageLiveTv() { - var user = _sessionContext.GetUser(Request).Result; + var user = _sessionContext.GetUser(Request); if (user == null) { @@ -848,10 +860,26 @@ namespace MediaBrowser.Api.LiveTv public async Task Post(AddListingProvider request) { + if (request.Pw != null) + { + request.Password = GetHashedString(request.Pw); + } + + request.Pw = null; + var result = await _liveTvManager.SaveListingProvider(request, request.ValidateLogin, request.ValidateListings).ConfigureAwait(false); return ToOptimizedResult(result); } + /// + /// Gets the hashed string. + /// + private string GetHashedString(string str) + { + // legacy + return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty).ToLower(); + } + public void Delete(DeleteListingProvider request) { _liveTvManager.DeleteListingsProvider(request.Id); @@ -859,8 +887,6 @@ namespace MediaBrowser.Api.LiveTv public async Task Post(AddTunerHost request) { - request.EnableNewHdhrChannelIds = true; - var result = await _liveTvManager.SaveTunerHost(request).ConfigureAwait(false); return ToOptimizedResult(result); } @@ -888,14 +914,14 @@ namespace MediaBrowser.Api.LiveTv { var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(info); + return ToOptimizedResult(info); } - public async Task Get(GetLiveTvInfo request) + public object Get(GetLiveTvInfo request) { - var info = await _liveTvManager.GetLiveTvInfo(CancellationToken.None).ConfigureAwait(false); + var info = _liveTvManager.GetLiveTvInfo(CancellationToken.None); - return ToOptimizedSerializedResultUsingCache(info); + return ToOptimizedResult(info); } public object Get(GetChannels request) @@ -923,7 +949,7 @@ namespace MediaBrowser.Api.LiveTv }, options, CancellationToken.None); - var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId); + var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); RemoveFields(options); @@ -937,7 +963,7 @@ namespace MediaBrowser.Api.LiveTv TotalRecordCount = channelResult.TotalRecordCount }; - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } private void RemoveFields(DtoOptions options) @@ -955,27 +981,24 @@ namespace MediaBrowser.Api.LiveTv { var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); + var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); var dtoOptions = GetDtoOptions(_authContext, request); var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - return ToOptimizedSerializedResultUsingCache(result); - } - - public object Get(GetLiveTvFolder request) - { - return ToOptimizedResult(_liveTvManager.GetLiveTvFolder(request.UserId, CancellationToken.None)); + return ToOptimizedResult(result); } public async Task Get(GetPrograms request) { - var query = new ProgramQuery + var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); + + var query = new InternalItemsQuery(user) { - ChannelIds = ApiEntryPoint.Split(request.ChannelIds, ',', true), - UserId = request.UserId, + ChannelIds = ApiEntryPoint.Split(request.ChannelIds, ',', true).Select(i => new Guid(i)).ToArray(), HasAired = request.HasAired, + IsAiring = request.IsAiring, EnableTotalRecordCount = request.EnableTotalRecordCount }; @@ -1009,9 +1032,9 @@ namespace MediaBrowser.Api.LiveTv query.IsSports = request.IsSports; query.SeriesTimerId = request.SeriesTimerId; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - query.GenreIds = (request.GenreIds ?? String.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + query.GenreIds = GetGuids(request.GenreIds); - if (!string.IsNullOrWhiteSpace(request.LibrarySeriesId)) + if (!request.LibrarySeriesId.Equals(Guid.Empty)) { query.IsSeries = true; @@ -1029,9 +1052,10 @@ namespace MediaBrowser.Api.LiveTv public object Get(GetRecommendedPrograms request) { - var query = new RecommendedProgramQuery + var user = _userManager.GetUserById(request.UserId); + + var query = new InternalItemsQuery(user) { - UserId = request.UserId, IsAiring = request.IsAiring, Limit = request.Limit, HasAired = request.HasAired, @@ -1043,7 +1067,7 @@ namespace MediaBrowser.Api.LiveTv EnableTotalRecordCount = request.EnableTotalRecordCount }; - query.GenreIds = (request.GenreIds ?? String.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + query.GenreIds = GetGuids(request.GenreIds); var result = _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(_authContext, request), CancellationToken.None); @@ -1055,16 +1079,14 @@ namespace MediaBrowser.Api.LiveTv return Get(request); } - public async Task Get(GetRecordings request) + public object Get(GetRecordings request) { var options = GetDtoOptions(_authContext, request); - options.DeviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; - var result = await _liveTvManager.GetRecordings(new RecordingQuery + var result = _liveTvManager.GetRecordings(new RecordingQuery { ChannelId = request.ChannelId, UserId = request.UserId, - GroupId = request.GroupId, StartIndex = request.StartIndex, Limit = request.Limit, Status = request.Status, @@ -1076,53 +1098,39 @@ namespace MediaBrowser.Api.LiveTv IsSeries = request.IsSeries, IsKids = request.IsKids, IsSports = request.IsSports, - IsLibraryItem = request.IsLibraryItem + IsLibraryItem = request.IsLibraryItem, + Fields = request.GetItemFields(), + ImageTypeLimit = request.ImageTypeLimit, + EnableImages = request.EnableImages - }, options, CancellationToken.None).ConfigureAwait(false); + }, options); return ToOptimizedResult(result); } public object Get(GetRecordingSeries request) { - var options = GetDtoOptions(_authContext, request); - options.DeviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; - - var result = _liveTvManager.GetRecordingSeries(new RecordingQuery - { - ChannelId = request.ChannelId, - UserId = request.UserId, - GroupId = request.GroupId, - StartIndex = request.StartIndex, - Limit = request.Limit, - Status = request.Status, - SeriesTimerId = request.SeriesTimerId, - IsInProgress = request.IsInProgress, - EnableTotalRecordCount = request.EnableTotalRecordCount - - }, options, CancellationToken.None); - - return ToOptimizedResult(result); + return ToOptimizedResult(new QueryResult()); } - public async Task Get(GetRecording request) + public object Get(GetRecording request) { var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); + var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); var dtoOptions = GetDtoOptions(_authContext, request); var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public async Task Get(GetTimer request) { var result = await _liveTvManager.GetTimer(request.Id, CancellationToken.None).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public async Task Get(GetTimers request) @@ -1136,34 +1144,31 @@ namespace MediaBrowser.Api.LiveTv }, CancellationToken.None).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public void Delete(DeleteRecording request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.DeleteRecording(request.Id); - - Task.WaitAll(task); + _libraryManager.DeleteItem(_libraryManager.GetItemById(request.Id), new DeleteOptions + { + DeleteFileLocation = false + }); } - public void Delete(CancelTimer request) + public Task Delete(CancelTimer request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.CancelTimer(request.Id); - - Task.WaitAll(task); + return _liveTvManager.CancelTimer(request.Id); } - public void Post(UpdateTimer request) + public Task Post(UpdateTimer request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.UpdateTimer(request, CancellationToken.None); - - Task.WaitAll(task); + return _liveTvManager.UpdateTimer(request, CancellationToken.None); } public async Task Get(GetSeriesTimers request) @@ -1175,32 +1180,28 @@ namespace MediaBrowser.Api.LiveTv }, CancellationToken.None).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public async Task Get(GetSeriesTimer request) { var result = await _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } - public void Delete(CancelSeriesTimer request) + public Task Delete(CancelSeriesTimer request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.CancelSeriesTimer(request.Id); - - Task.WaitAll(task); + return _liveTvManager.CancelSeriesTimer(request.Id); } - public void Post(UpdateSeriesTimer request) + public Task Post(UpdateSeriesTimer request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.UpdateSeriesTimer(request, CancellationToken.None); - - Task.WaitAll(task); + return _liveTvManager.UpdateSeriesTimer(request, CancellationToken.None); } public async Task Get(GetDefaultTimer request) @@ -1209,61 +1210,47 @@ namespace MediaBrowser.Api.LiveTv { var result = await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } else { var result = await _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } } public async Task Get(GetProgram request) { - var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId); + var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); var result = await _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).ConfigureAwait(false); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } - public void Post(CreateSeriesTimer request) + public Task Post(CreateSeriesTimer request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.CreateSeriesTimer(request, CancellationToken.None); - - Task.WaitAll(task); + return _liveTvManager.CreateSeriesTimer(request, CancellationToken.None); } - public void Post(CreateTimer request) + public Task Post(CreateTimer request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.CreateTimer(request, CancellationToken.None); - - Task.WaitAll(task); + return _liveTvManager.CreateTimer(request, CancellationToken.None); } - public async Task Get(GetRecordingGroups request) + public object Get(GetRecordingGroups request) { - var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery - { - UserId = request.UserId - - }, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(new QueryResult()); } - public async Task Get(GetRecordingGroup request) + public object Get(GetRecordingGroup request) { - var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery(), CancellationToken.None).ConfigureAwait(false); - - var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase)); - - return ToOptimizedSerializedResultUsingCache(group); + throw new FileNotFoundException(); } public object Get(GetGuideInfo request) @@ -1271,13 +1258,11 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(_liveTvManager.GetGuideInfo()); } - public void Post(ResetTuner request) + public Task Post(ResetTuner request) { AssertUserCanManageLiveTv(); - var task = _liveTvManager.ResetTuner(request.Id, CancellationToken.None); - - Task.WaitAll(task); + return _liveTvManager.ResetTuner(request.Id, CancellationToken.None); } } } \ No newline at end of file diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index 74293ccd90..0a6ccd4c5c 100644 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using MediaBrowser.Controller.IO; namespace MediaBrowser.Api.LiveTv { @@ -20,28 +21,30 @@ namespace MediaBrowser.Api.LiveTv const int StreamCopyToBufferSize = 81920; - private long _bytesWritten = 0; public long StartPosition { get; set; } public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; private readonly IEnvironmentInfo _environment; + private IStreamHelper _streamHelper; - public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) + public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _logger = logger; _environment = environment; + _streamHelper = streamHelper; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _logger = logger; _environment = environment; + _streamHelper = streamHelper; } public IDictionary Headers @@ -75,7 +78,7 @@ namespace MediaBrowser.Api.LiveTv var eofCount = 0; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + var allowAsyncFileRead = true; using (var inputStream = GetInputStream(allowAsyncFileRead)) { @@ -89,14 +92,7 @@ namespace MediaBrowser.Api.LiveTv while (eofCount < emptyReadLimit) { int bytesRead; - if (allowAsyncFileRead) - { - bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - } - else - { - bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - } + bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); @@ -113,49 +109,5 @@ namespace MediaBrowser.Api.LiveTv } } } - - private async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) - { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = source.Read(array, 0, array.Length)) != 0) - { - var bytesToWrite = bytesRead; - - if (bytesToWrite > 0) - { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - - _bytesWritten += bytesRead; - totalBytesRead += bytesRead; - } - } - - return totalBytesRead; - } - - private async Task CopyToInternalAsync(Stream source, Stream destination, CancellationToken cancellationToken) - { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - var bytesToWrite = bytesRead; - - if (bytesToWrite > 0) - { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - - _bytesWritten += bytesRead; - totalBytesRead += bytesRead; - } - } - - return totalBytesRead; - } } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 650306ea60..e4c9a0c351 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -1,136 +1,17 @@ - - - - - Debug - AnyCPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582} - Library - Properties - MediaBrowser.Api - MediaBrowser.Api - 512 - ..\ - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AnyCPU - - - none - true - bin\Release\ - TRACE - prompt - 4 - - - Always - + + - - Properties\SharedVersion.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - {9142EEFA-7570-41E1-BFCC-468BB571AF2F} - MediaBrowser.Common - - - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} - MediaBrowser.Controller - - - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} - MediaBrowser.Model - + - + - - + netcoreapp2.1 + false - - \ No newline at end of file + + diff --git a/MediaBrowser.Api/MediaBrowser.Api.nuget.targets b/MediaBrowser.Api/MediaBrowser.Api.nuget.targets deleted file mode 100644 index e69ce0e64f..0000000000 --- a/MediaBrowser.Api/MediaBrowser.Api.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs index c63712f4cc..9adefdf755 100644 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -3,8 +3,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Collections; using System; -using System.Collections.Generic; -using System.Threading.Tasks; using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Movies @@ -38,7 +36,7 @@ namespace MediaBrowser.Api.Movies [Route("/Collections/{Id}/Items", "DELETE", Summary = "Removes items from a collection")] public class RemoveFromCollection : IReturnVoid { - [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] public string Ids { get; set; } [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] @@ -59,44 +57,40 @@ namespace MediaBrowser.Api.Movies _authContext = authContext; } - public async Task Post(CreateCollection request) + public object Post(CreateCollection request) { var userId = _authContext.GetAuthorizationInfo(Request).UserId; var parentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); - var item = await _collectionManager.CreateCollection(new CollectionCreationOptions + var item = _collectionManager.CreateCollection(new CollectionCreationOptions { IsLocked = request.IsLocked, Name = request.Name, ParentId = parentId, ItemIdList = SplitValue(request.Ids, ','), - UserIds = new string[] { userId } + UserIds = new [] { userId } - }).ConfigureAwait(false); + }); var dtoOptions = GetDtoOptions(_authContext, request); var dto = _dtoService.GetBaseItemDto(item, dtoOptions); - return ToOptimizedResult(new CollectionCreationResult + return new CollectionCreationResult { Id = dto.Id - }); + }; } public void Post(AddToCollection request) { - var task = _collectionManager.AddToCollection(new Guid(request.Id), SplitValue(request.Ids, ',')); - - Task.WaitAll(task); + _collectionManager.AddToCollection(new Guid(request.Id), SplitValue(request.Ids, ',')); } public void Delete(RemoveFromCollection request) { - var task = _collectionManager.RemoveFromCollection(new Guid(request.Id), SplitValue(request.Ids, ',')); - - Task.WaitAll(task); + _collectionManager.RemoveFromCollection(new Guid(request.Id), SplitValue(request.Ids, ',')); } } } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 254c93b33a..7112806011 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -19,22 +19,6 @@ using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Movies { - /// - /// Class GetSimilarMovies - /// - [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")] - public class GetSimilarMovies : BaseGetSimilarItemsFromItem - { - } - - /// - /// Class GetSimilarTrailers - /// - [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] - public class GetSimilarTrailers : BaseGetSimilarItemsFromItem - { - } - [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")] public class GetMovieRecommendations : IReturn, IHasDtoOptions { @@ -49,7 +33,7 @@ namespace MediaBrowser.Api.Movies /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Specify this to localize the search to a specific item or folder. Omit to use the root. @@ -108,25 +92,6 @@ namespace MediaBrowser.Api.Movies _authContext = authContext; } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSimilarMovies request) - { - var result = GetSimilarItemsResult(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - public object Get(GetSimilarTrailers request) - { - var result = GetSimilarItemsResult(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - public object Get(GetMovieRecommendations request) { var user = _userManager.GetUserById(request.UserId); @@ -138,12 +103,12 @@ namespace MediaBrowser.Api.Movies return ToOptimizedResult(result); } - private QueryResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) + public QueryResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? - (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : + (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); var itemTypes = new List { typeof(Movie).Name }; @@ -182,7 +147,7 @@ namespace MediaBrowser.Api.Movies { var categories = new List(); - var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? (Guid?)null : new Guid(parentId); + var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId); var query = new InternalItemsQuery(user) { @@ -193,7 +158,7 @@ namespace MediaBrowser.Api.Movies //typeof(LiveTvProgram).Name }, // IsMovie = true - OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new Tuple(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), Limit = 7, ParentId = parentIdGuid, Recursive = true, @@ -214,10 +179,10 @@ namespace MediaBrowser.Api.Movies { IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, - OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), Limit = 10, IsFavoriteOrLiked = true, - ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(recentlyPlayedMovies.Count), + ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(recentlyPlayedMovies.Count), EnableGroupByMetadataKey = true, ParentId = parentIdGuid, Recursive = true, @@ -316,7 +281,7 @@ namespace MediaBrowser.Api.Movies yield return new RecommendationDto { BaselineItemName = name, - CategoryId = name.GetMD5().ToString("N"), + CategoryId = name.GetMD5(), RecommendationType = type, Items = returnItems }; @@ -356,7 +321,7 @@ namespace MediaBrowser.Api.Movies yield return new RecommendationDto { BaselineItemName = name, - CategoryId = name.GetMD5().ToString("N"), + CategoryId = name.GetMD5(), RecommendationType = type, Items = returnItems }; @@ -393,7 +358,7 @@ namespace MediaBrowser.Api.Movies yield return new RecommendationDto { BaselineItemName = item.Name, - CategoryId = item.Id.ToString("N"), + CategoryId = item.Id, RecommendationType = type, Items = returnItems }; @@ -405,7 +370,7 @@ namespace MediaBrowser.Api.Movies { var people = _libraryManager.GetPeople(new InternalPeopleQuery { - ExcludePersonTypes = new List + ExcludePersonTypes = new [] { PersonType.Director }, diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index d7986fa501..538840f6c4 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Api.Music request, new[] { typeof(MusicArtist) }, SimilarItemsHelper.GetSimiliarityScore); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -85,7 +85,7 @@ namespace MediaBrowser.Api.Music request, new[] { typeof(MusicAlbum) }, GetAlbumSimilarityScore); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 8a18298f1e..8435b1ac1c 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -29,13 +29,6 @@ namespace MediaBrowser.Api.Music { } - [Route("/Artists/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")] - public class GetInstantMixFromArtist : BaseGetSimilarItems - { - [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - [Route("/MusicGenres/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")] public class GetInstantMixFromMusicGenre : BaseGetSimilarItems { @@ -170,18 +163,6 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public object Get(GetInstantMixFromArtist request) - { - var user = _userManager.GetUserById(request.UserId); - var artist = _libraryManager.GetArtist(request.Name, new DtoOptions(false)); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromArtist(artist, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - private object GetResult(List items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) { var list = items; @@ -200,7 +181,7 @@ namespace MediaBrowser.Api.Music result.Items = returnList; - return ToOptimizedResult(result); + return result; } } diff --git a/MediaBrowser.Api/NewsService.cs b/MediaBrowser.Api/NewsService.cs index 542103799b..cbc77bfc9a 100644 --- a/MediaBrowser.Api/NewsService.cs +++ b/MediaBrowser.Api/NewsService.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Api }); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } } } diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 79dda87028..366d1318bf 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Api [ApiMember(Name = "PackageType", Description = "Optional package type filter (System/UserInstalled)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string PackageType { get; set; } - [ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET", AllowMultiple = true)] + [ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string TargetSystems { get; set; } [ApiMember(Name = "IsPremium", Description = "Optional. Filter by premium status", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] @@ -146,24 +146,24 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public object Get(GetPackageVersionUpdates request) + public async Task Get(GetPackageVersionUpdates request) { PackageVersionInfo[] result = null; if (string.Equals(request.PackageType, "UserInstalled", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase)) { - result = _installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToArray(); + result = (await _installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).ConfigureAwait(false)).ToArray(); } else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase)) { - var updateCheckResult = _appHost - .CheckForApplicationUpdate(CancellationToken.None, new SimpleProgress()).Result; + var updateCheckResult = await _appHost + .CheckForApplicationUpdate(CancellationToken.None, new SimpleProgress()).ConfigureAwait(false); if (updateCheckResult.IsUpdateAvailable) { - result = new PackageVersionInfo[] {updateCheckResult.Package}; + result = new PackageVersionInfo[] { updateCheckResult.Package }; } } @@ -224,11 +224,11 @@ namespace MediaBrowser.Api /// /// The request. /// - public void Post(InstallPackage request) + public async Task Post(InstallPackage request) { var package = string.IsNullOrEmpty(request.Version) ? - _installationManager.GetLatestCompatibleVersion(request.Name, request.AssemblyGuid, _appHost.ApplicationVersion, request.UpdateClass).Result : - _installationManager.GetPackage(request.Name, request.AssemblyGuid, request.UpdateClass, Version.Parse(request.Version)).Result; + await _installationManager.GetLatestCompatibleVersion(request.Name, request.AssemblyGuid, _appHost.ApplicationVersion, request.UpdateClass).ConfigureAwait(false) : + await _installationManager.GetPackage(request.Name, request.AssemblyGuid, request.UpdateClass, Version.Parse(request.Version)).ConfigureAwait(false); if (package == null) { @@ -244,7 +244,7 @@ namespace MediaBrowser.Api /// The request. public void Delete(CancelPackageInstallation request) { - var info = _installationManager.CurrentInstallations.FirstOrDefault(i => string.Equals(i.Item1.Id, request.Id)); + var info = _installationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id.Equals(request.Id)); if (info != null) { diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 2266780217..471e3bb575 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -9,6 +9,7 @@ using MediaBrowser.Model.Querying; using System.Threading.Tasks; using MediaBrowser.Model.Services; using MediaBrowser.Model.Extensions; +using System; namespace MediaBrowser.Api { @@ -21,10 +22,10 @@ namespace MediaBrowser.Api [ApiMember(Name = "Ids", Description = "Item Ids to add to the playlist", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] public string Ids { get; set; } - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } - [ApiMember(Name = "MediaType", Description = "The playlist media type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "MediaType", Description = "The playlist media type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string MediaType { get; set; } } @@ -42,7 +43,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string UserId { get; set; } + public Guid UserId { get; set; } } [Route("/Playlists/{Id}/Items/{ItemId}/Move/{NewIndex}", "POST", Summary = "Moves a playlist item")] @@ -76,14 +77,14 @@ namespace MediaBrowser.Api public class GetPlaylistItems : IReturn>, IHasDtoOptions { [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + public Guid Id { get; set; } /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -139,9 +140,7 @@ namespace MediaBrowser.Api public void Post(MoveItem request) { - var task = _playlistManager.MoveItem(request.Id, request.ItemId, request.NewIndex); - - Task.WaitAll(task); + _playlistManager.MoveItem(request.Id, request.ItemId, request.NewIndex); } public async Task Post(CreatePlaylist request) @@ -149,7 +148,7 @@ namespace MediaBrowser.Api var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest { Name = request.Name, - ItemIdList = SplitValue(request.Ids, ','), + ItemIdList = GetGuids(request.Ids), UserId = request.UserId, MediaType = request.MediaType @@ -160,22 +159,18 @@ namespace MediaBrowser.Api public void Post(AddToPlaylist request) { - var task = _playlistManager.AddToPlaylist(request.Id, request.Ids.Split(','), request.UserId); - - Task.WaitAll(task); + _playlistManager.AddToPlaylist(request.Id, GetGuids(request.Ids), request.UserId); } public void Delete(RemoveFromPlaylist request) { - var task = _playlistManager.RemoveFromPlaylist(request.Id, request.EntryIds.Split(',')); - - Task.WaitAll(task); + _playlistManager.RemoveFromPlaylist(request.Id, request.EntryIds.Split(',')); } public object Get(GetPlaylistItems request) { var playlist = (Playlist)_libraryManager.GetItemById(request.Id); - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var items = playlist.GetManageableItems().ToArray(); diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index 1eea894312..1ee096a2e7 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Registration; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; @@ -15,6 +14,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Common.Plugins; namespace MediaBrowser.Api { @@ -103,9 +103,6 @@ namespace MediaBrowser.Api { [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } - - [ApiMember(Name = "Mb2Equivalent", Description = "Optional. The equivalent feature name in MB2", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Mb2Equivalent { get; set; } } [Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature", IsHidden = true)] @@ -116,6 +113,14 @@ namespace MediaBrowser.Api public string Name { get; set; } } + public class RegistrationInfo + { + public string Name { get; set; } + public DateTime ExpirationDate { get; set; } + public bool IsTrial { get; set; } + public bool IsRegistered { get; set; } + } + [Route("/Appstore/Register", "POST", Summary = "Registers an appstore sale", IsHidden = true)] [Authenticated] public class RegisterAppstoreSale @@ -168,7 +173,7 @@ namespace MediaBrowser.Api /// System.Object. public async Task Get(GetRegistrationStatus request) { - var result = await _securityManager.GetRegistrationStatus(request.Name, request.Mb2Equivalent).ConfigureAwait(false); + var result = await _securityManager.GetRegistrationStatus(request.Name).ConfigureAwait(false); return ToOptimizedResult(result); } @@ -235,7 +240,7 @@ namespace MediaBrowser.Api } } - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -246,7 +251,7 @@ namespace MediaBrowser.Api public object Get(GetPluginConfiguration request) { var guid = new Guid(request.Id); - var plugin = _appHost.Plugins.First(p => p.Id == guid); + var plugin = _appHost.Plugins.First(p => p.Id == guid) as IHasPluginConfiguration; return ToOptimizedResult(plugin.Configuration); } @@ -256,15 +261,15 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public object Get(GetPluginSecurityInfo request) + public async Task Get(GetPluginSecurityInfo request) { var result = new PluginSecurityInfo { - IsMBSupporter = _securityManager.IsMBSupporter, + IsMBSupporter = await _securityManager.IsSupporter().ConfigureAwait(false), SupporterKey = _securityManager.SupporterKey }; - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -272,37 +277,38 @@ namespace MediaBrowser.Api /// /// /// - public void Post(RegisterAppstoreSale request) + public Task Post(RegisterAppstoreSale request) { - var task = _securityManager.RegisterAppStoreSale(request.Parameters); - - Task.WaitAll(task); + return _securityManager.RegisterAppStoreSale(request.Parameters); } /// /// Posts the specified request. /// /// The request. - public void Post(UpdatePluginSecurityInfo request) + public Task Post(UpdatePluginSecurityInfo request) { - var info = request; - - _securityManager.SupporterKey = info.SupporterKey; + return _securityManager.UpdateSupporterKey(request.SupporterKey); } /// /// Posts the specified request. /// /// The request. - public void Post(UpdatePluginConfiguration request) + public async Task Post(UpdatePluginConfiguration request) { // We need to parse this manually because we told service stack not to with IRequiresRequestStream // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs var id = new Guid(GetPathValue(1)); - var plugin = _appHost.Plugins.First(p => p.Id == id); + var plugin = _appHost.Plugins.First(p => p.Id == id) as IHasPluginConfiguration; - var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, plugin.ConfigurationType) as BasePluginConfiguration; + if (plugin == null) + { + throw new FileNotFoundException(); + } + + var configuration = (await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, plugin.ConfigurationType).ConfigureAwait(false)) as BasePluginConfiguration; plugin.UpdateConfiguration(configuration); } diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index abe3e5407c..959bdcd550 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -207,7 +207,7 @@ namespace MediaBrowser.Api.ScheduledTasks } } - TaskManager.Execute(task); + TaskManager.Execute(task, new TaskOptions()); } /// diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs index 2d30625a98..ff57b4dd15 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -33,8 +33,8 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Initializes a new instance of the class. /// - public ScheduledTasksWebSocketListener(ILogger logger, ITaskManager taskManager, ITimerFactory timerFactory) - : base(logger, timerFactory) + public ScheduledTasksWebSocketListener(ILogger logger, ITaskManager taskManager) + : base(logger) { TaskManager = taskManager; @@ -72,14 +72,6 @@ namespace MediaBrowser.Api.ScheduledTasks .Where(i => !i.IsHidden)); } - protected override bool SendOnTimer - { - get - { - return false; - } - } - protected override void Dispose(bool dispose) { TaskManager.TaskExecuting -= TaskManager_TaskExecuting; diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 50033eee82..2e363136b3 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -11,6 +11,7 @@ using MediaBrowser.Model.Search; using System.Threading.Tasks; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Services; +using System; namespace MediaBrowser.Api { @@ -39,7 +40,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Supply a user id to search within a user's library or omit to search all.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Search characters used to find items @@ -134,11 +135,11 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public async Task Get(GetSearchHints request) + public object Get(GetSearchHints request) { - var result = await GetSearchHintsAsync(request).ConfigureAwait(false); + var result = GetSearchHintsAsync(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -146,9 +147,9 @@ namespace MediaBrowser.Api /// /// The request. /// Task{IEnumerable{SearchHintResult}}. - private async Task GetSearchHintsAsync(GetSearchHints request) + private SearchHintResult GetSearchHintsAsync(GetSearchHints request) { - var result = await _searchEngine.GetSearchHints(new SearchQuery + var result = _searchEngine.GetSearchHints(new SearchQuery { Limit = request.Limit, SearchTerm = request.SearchTerm, @@ -170,7 +171,7 @@ namespace MediaBrowser.Api IsSeries = request.IsSeries, IsSports = request.IsSports - }).ConfigureAwait(false); + }); return new SearchHintResult { @@ -194,7 +195,7 @@ namespace MediaBrowser.Api Name = item.Name, IndexNumber = item.IndexNumber, ParentIndexNumber = item.ParentIndexNumber, - ItemId = _dtoService.GetDtoId(item), + Id = item.Id, Type = item.GetClientTypeName(), MediaType = item.MediaType, MatchedTerm = hintInfo.MatchedTerm, @@ -204,6 +205,14 @@ namespace MediaBrowser.Api EndDate = item.EndDate }; + // legacy + result.ItemId = result.Id; + + if (item.IsFolder) + { + result.IsFolder = true; + } + var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); if (primaryImageTag != null) @@ -221,7 +230,7 @@ namespace MediaBrowser.Api result.StartDate = program.StartDate; } - var hasSeries = item as IHasSeries; + var hasSeries = item as IHasSeriesName; if (hasSeries != null) { result.Series = hasSeries.SeriesName; @@ -256,7 +265,7 @@ namespace MediaBrowser.Api if (album != null) { result.Album = album.Name; - result.AlbumId = album.Id.ToString("N"); + result.AlbumId = album.Id; } else { @@ -264,7 +273,7 @@ namespace MediaBrowser.Api } } - if (!string.IsNullOrWhiteSpace(item.ChannelId)) + if (!item.ChannelId.Equals(Guid.Empty)) { var channel = _libraryManager.GetItemById(item.ChannelId); result.ChannelName = channel == null ? null : channel.Name; diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index 65c69fc7de..ca2c381cf6 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Api.Session /// /// Class SessionInfoWebSocketListener /// - class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> { /// /// Gets the name. @@ -33,8 +33,8 @@ namespace MediaBrowser.Api.Session /// /// Initializes a new instance of the class. /// - public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager, ITimerFactory timerFactory) - : base(logger, timerFactory) + public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) + : base(logger) { _sessionManager = sessionManager; @@ -87,17 +87,9 @@ namespace MediaBrowser.Api.Session /// /// The state. /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) + protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) { - return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); - } - - protected override bool SendOnTimer - { - get - { - return false; - } + return Task.FromResult(_sessionManager.Sessions); } protected override void Dispose(bool dispose) diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 35c29cd15b..1056b3c8bd 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; using MediaBrowser.Controller; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Api.Session { @@ -19,13 +20,15 @@ namespace MediaBrowser.Api.Session /// [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] [Authenticated] - public class GetSessions : IReturn + public class GetSessions : IReturn { [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ControllableByUserId { get; set; } + public Guid ControllableByUserId { get; set; } [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceId { get; set; } + + public int? ActiveWithinSeconds { get; set; } } /// @@ -189,7 +192,7 @@ namespace MediaBrowser.Api.Session /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Id { get; set; } [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] @@ -198,9 +201,6 @@ namespace MediaBrowser.Api.Session [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string SupportedCommands { get; set; } - [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MessageCallbackUrl { get; set; } - [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool SupportsMediaControl { get; set; } @@ -210,8 +210,6 @@ namespace MediaBrowser.Api.Session [ApiMember(Name = "SupportsPersistentIdentifier", Description = "Determines whether the device supports a unique identifier.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool SupportsPersistentIdentifier { get; set; } - public bool SupportsContentUploading { get; set; } - public PostCapabilities() { SupportsPersistentIdentifier = true; @@ -226,7 +224,7 @@ namespace MediaBrowser.Api.Session /// Gets or sets the id. /// /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Id { get; set; } } @@ -242,11 +240,17 @@ namespace MediaBrowser.Api.Session { } + [Route("/Auth/Providers", "GET")] + [Authenticated(Roles = "Admin")] + public class GetAuthProviders : IReturn + { + } + [Route("/Auth/Keys/{Key}", "DELETE")] [Authenticated(Roles = "Admin")] public class RevokeKey { - [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Key { get; set; } } @@ -286,6 +290,11 @@ namespace MediaBrowser.Api.Session _appHost = appHost; } + public object Get(GetAuthProviders request) + { + return _userManager.GetAuthenticationProviders(); + } + public void Delete(RevokeKey request) { _sessionManager.RevokeToken(request.Key); @@ -297,14 +306,13 @@ namespace MediaBrowser.Api.Session _authRepo.Create(new AuthenticationInfo { AppName = request.App, - IsActive = true, AccessToken = Guid.NewGuid().ToString("N"), DateCreated = DateTime.UtcNow, DeviceId = _appHost.SystemId, DeviceName = _appHost.FriendlyName, AppVersion = _appHost.ApplicationVersion.ToString() - }, CancellationToken.None); + }); } public void Post(ReportSessionEnded request) @@ -318,11 +326,10 @@ namespace MediaBrowser.Api.Session { var result = _authRepo.Get(new AuthenticationInfoQuery { - IsActive = true, HasUser = false }); - return ToOptimizedResult(result); + return result; } /// @@ -332,27 +339,33 @@ namespace MediaBrowser.Api.Session /// System.Object. public object Get(GetSessions request) { - var result = _sessionManager.Sessions.Where(i => i.IsActive); + var result = _sessionManager.Sessions; if (!string.IsNullOrEmpty(request.DeviceId)) { result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); } - if (!string.IsNullOrWhiteSpace(request.ControllableByUserId)) + if (!request.ControllableByUserId.Equals(Guid.Empty)) { - result = result.Where(i => i.SupportsMediaControl); + result = result.Where(i => i.SupportsRemoteControl); var user = _userManager.GetUserById(request.ControllableByUserId); if (!user.Policy.EnableRemoteControlOfOtherUsers) { - result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId)); + result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(request.ControllableByUserId)); } if (!user.Policy.EnableSharedDeviceControl) { - result = result.Where(i => i.UserId.HasValue); + result = result.Where(i => !i.UserId.Equals(Guid.Empty)); + } + + if (request.ActiveWithinSeconds.HasValue && request.ActiveWithinSeconds.Value > 0) + { + var minActiveDate = DateTime.UtcNow.AddSeconds(0 - request.ActiveWithinSeconds.Value); + result = result.Where(i => i.LastActivityDate >= minActiveDate); } result = result.Where(i => @@ -361,7 +374,7 @@ namespace MediaBrowser.Api.Session if (!string.IsNullOrWhiteSpace(deviceId)) { - if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), deviceId)) + if (!_deviceManager.CanAccessDevice(user, deviceId)) { return false; } @@ -371,21 +384,19 @@ namespace MediaBrowser.Api.Session }); } - return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToArray()); + return ToOptimizedResult(result.ToArray()); } - public void Post(SendPlaystateCommand request) + public Task Post(SendPlaystateCommand request) { - var task = _sessionManager.SendPlaystateCommand(GetSession(_sessionContext).Result.Id, request.Id, request, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendPlaystateCommand(GetSession(_sessionContext).Id, request.Id, request, CancellationToken.None); } /// /// Posts the specified request. /// /// The request. - public void Post(DisplayContent request) + public Task Post(DisplayContent request) { var command = new BrowseRequest { @@ -394,16 +405,14 @@ namespace MediaBrowser.Api.Session ItemType = request.ItemType }; - var task = _sessionManager.SendBrowseCommand(GetSession(_sessionContext).Result.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendBrowseCommand(GetSession(_sessionContext).Id, request.Id, command, CancellationToken.None); } /// /// Posts the specified request. /// /// The request. - public void Post(SendSystemCommand request) + public Task Post(SendSystemCommand request) { GeneralCommandType commandType; var name = request.Command; @@ -413,24 +422,22 @@ namespace MediaBrowser.Api.Session name = commandType.ToString(); } - var currentSession = GetSession(_sessionContext).Result; + var currentSession = GetSession(_sessionContext); var command = new GeneralCommand { Name = name, - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + ControllingUserId = currentSession.UserId }; - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); } /// /// Posts the specified request. /// /// The request. - public void Post(SendMessageCommand request) + public Task Post(SendMessageCommand request) { var command = new MessageCommand { @@ -439,63 +446,55 @@ namespace MediaBrowser.Api.Session Text = request.Text }; - var task = _sessionManager.SendMessageCommand(GetSession(_sessionContext).Result.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendMessageCommand(GetSession(_sessionContext).Id, request.Id, command, CancellationToken.None); } /// /// Posts the specified request. /// /// The request. - public void Post(Play request) + public Task Post(Play request) { - var task = _sessionManager.SendPlayCommand(GetSession(_sessionContext).Result.Id, request.Id, request, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendPlayCommand(GetSession(_sessionContext).Id, request.Id, request, CancellationToken.None); } - public void Post(SendGeneralCommand request) + public Task Post(SendGeneralCommand request) { - var currentSession = GetSession(_sessionContext).Result; + var currentSession = GetSession(_sessionContext); var command = new GeneralCommand { Name = request.Command, - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + ControllingUserId = currentSession.UserId }; - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); } - public void Post(SendFullGeneralCommand request) + public Task Post(SendFullGeneralCommand request) { - var currentSession = GetSession(_sessionContext).Result; + var currentSession = GetSession(_sessionContext); - request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; + request.ControllingUserId = currentSession.UserId; - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); - - Task.WaitAll(task); + return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); } public void Post(AddUserToSession request) { - _sessionManager.AddAdditionalUser(request.Id, request.UserId); + _sessionManager.AddAdditionalUser(request.Id, new Guid(request.UserId)); } public void Delete(RemoveUserFromSession request) { - _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); + _sessionManager.RemoveAdditionalUser(request.Id, new Guid(request.UserId)); } public void Post(PostCapabilities request) { if (string.IsNullOrWhiteSpace(request.Id)) { - request.Id = GetSession(_sessionContext).Result.Id; + request.Id = GetSession(_sessionContext).Id; } _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities { @@ -505,12 +504,8 @@ namespace MediaBrowser.Api.Session SupportsMediaControl = request.SupportsMediaControl, - MessageCallbackUrl = request.MessageCallbackUrl, - SupportsSync = request.SupportsSync, - SupportsContentUploading = request.SupportsContentUploading, - SupportsPersistentIdentifier = request.SupportsPersistentIdentifier }); } @@ -519,7 +514,7 @@ namespace MediaBrowser.Api.Session { if (string.IsNullOrWhiteSpace(request.Id)) { - request.Id = GetSession(_sessionContext).Result.Id; + request.Id = GetSession(_sessionContext).Id; } _sessionManager.ReportCapabilities(request.Id, request); } diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 0b5eaa4760..be9c1a4c58 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// The maximum number of items to return @@ -73,10 +73,10 @@ namespace MediaBrowser.Api { internal static QueryResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func, List, BaseItem, int> getSimilarityScore) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? - (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : + (!request.UserId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() : libraryManager.RootFolder) : libraryManager.GetItemById(request.Id); var query = new InternalItemsQuery(user) @@ -89,7 +89,7 @@ namespace MediaBrowser.Api // ExcludeArtistIds if (!string.IsNullOrEmpty(request.ExcludeArtistIds)) { - query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|'); + query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds); } var inputItems = libraryManager.GetItemList(query); diff --git a/MediaBrowser.Api/Social/SharingService.cs b/MediaBrowser.Api/Social/SharingService.cs deleted file mode 100644 index 4f10667b72..0000000000 --- a/MediaBrowser.Api/Social/SharingService.cs +++ /dev/null @@ -1,178 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Social; -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Social -{ - [Route("/Social/Shares/{Id}", "GET", Summary = "Gets a share")] - [Authenticated] - public class GetSocialShareInfo : IReturn - { - [ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Social/Shares/Public/{Id}", "GET", Summary = "Gets a share")] - public class GetPublicSocialShareInfo : IReturn - { - [ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Social/Shares/Public/{Id}/Image", "GET", Summary = "Gets a share")] - public class GetShareImage - { - [ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Social/Shares", "POST", Summary = "Creates a share")] - [Authenticated] - public class CreateShare : IReturn - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - - [ApiMember(Name = "UserId", Description = "The user id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string UserId { get; set; } - } - - [Route("/Social/Shares/{Id}", "DELETE", Summary = "Deletes a share")] - [Authenticated] - public class DeleteShare : IReturnVoid - { - [ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Social/Shares/Public/{Id}/Item", "GET", Summary = "Gets a share")] - public class GetSharedLibraryItem - { - [ApiMember(Name = "Id", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - public class SharingService : BaseApiService - { - private readonly ISharingManager _sharingManager; - private readonly ILibraryManager _libraryManager; - private readonly IDlnaManager _dlnaManager; - private readonly IDtoService _dtoService; - private readonly IHttpResultFactory _resultFactory; - - public SharingService(ISharingManager sharingManager, IDlnaManager dlnaManager, ILibraryManager libraryManager, IDtoService dtoService, IHttpResultFactory resultFactory) - { - _sharingManager = sharingManager; - _dlnaManager = dlnaManager; - _libraryManager = libraryManager; - _dtoService = dtoService; - _resultFactory = resultFactory; - } - - public object Get(GetSocialShareInfo request) - { - var info = _sharingManager.GetShareInfo(request.Id); - - return ToOptimizedResult(info); - } - - public object Get(GetSharedLibraryItem request) - { - var info = _sharingManager.GetShareInfo(request.Id); - - if (info.ExpirationDate <= DateTime.UtcNow) - { - throw new ResourceNotFoundException(); - } - - var item = _libraryManager.GetItemById(info.ItemId); - - var dto = _dtoService.GetBaseItemDto(item, new DtoOptions()); - - return ToOptimizedResult(dto); - } - - public object Get(GetPublicSocialShareInfo request) - { - var info = _sharingManager.GetShareInfo(request.Id); - - if (info.ExpirationDate <= DateTime.UtcNow) - { - throw new ResourceNotFoundException(); - } - - return ToOptimizedResult(info); - } - - public async Task Post(CreateShare request) - { - var info = await _sharingManager.CreateShare(request.ItemId, request.UserId).ConfigureAwait(false); - - return ToOptimizedResult(info); - } - - public void Delete(DeleteShare request) - { - _sharingManager.DeleteShare(request.Id); - } - - public async Task Get(GetShareImage request) - { - var share = _sharingManager.GetShareInfo(request.Id); - - if (share == null) - { - throw new ResourceNotFoundException(); - } - if (share.ExpirationDate <= DateTime.UtcNow) - { - throw new ResourceNotFoundException(); - } - - var item = _libraryManager.GetItemById(share.ItemId); - - var image = item.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - if (image.IsLocalFile) - { - return await _resultFactory.GetStaticFileResult(Request, image.Path).ConfigureAwait(false); - } - - try - { - // Don't fail the request over this - var updatedImage = await _libraryManager.ConvertImageToLocal(item, image, 0).ConfigureAwait(false); - return await _resultFactory.GetStaticFileResult(Request, updatedImage.Path).ConfigureAwait(false); - } - catch - { - - } - } - - // Grab a dlna icon if nothing else is available - using (var response = _dlnaManager.GetIcon("logo240.jpg")) - { - using (var ms = new MemoryStream()) - { - response.Stream.CopyTo(ms); - - ms.Position = 0; - var bytes = ms.ToArray(); - return ResultFactory.GetResult(bytes, "image/" + response.Format.ToString().ToLower()); - } - } - - } - } -} diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index c6345c17f4..61d9a90d6c 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; using System; using System.Linq; using System.Threading.Tasks; @@ -14,32 +13,34 @@ using System.Threading; namespace MediaBrowser.Api { - [Route("/Startup/Complete", "POST", Summary = "Reports that the startup wizard has been completed")] + [Route("/Startup/Complete", "POST", Summary = "Reports that the startup wizard has been completed", IsHidden = true)] public class ReportStartupWizardComplete : IReturnVoid { } - [Route("/Startup/Info", "GET", Summary = "Gets initial server info")] - public class GetStartupInfo : IReturn - { - } - - [Route("/Startup/Configuration", "GET", Summary = "Gets initial server configuration")] + [Route("/Startup/Configuration", "GET", Summary = "Gets initial server configuration", IsHidden = true)] public class GetStartupConfiguration : IReturn { } - [Route("/Startup/Configuration", "POST", Summary = "Updates initial server configuration")] + [Route("/Startup/Configuration", "POST", Summary = "Updates initial server configuration", IsHidden = true)] public class UpdateStartupConfiguration : StartupConfiguration, IReturnVoid { } - [Route("/Startup/User", "GET", Summary = "Gets initial user info")] + [Route("/Startup/RemoteAccess", "POST", Summary = "Updates initial server configuration", IsHidden = true)] + public class UpdateRemoteAccessConfiguration : IReturnVoid + { + public bool EnableRemoteAccess { get; set; } + public bool EnableAutomaticPortMapping { get; set; } + } + + [Route("/Startup/User", "GET", Summary = "Gets initial user info", IsHidden = true)] public class GetStartupUser : IReturn { } - [Route("/Startup/User", "POST", Summary = "Updates initial user info")] + [Route("/Startup/User", "POST", Summary = "Updates initial user info", IsHidden = true)] public class UpdateStartupUser : StartupUser, IReturn { } @@ -67,7 +68,6 @@ namespace MediaBrowser.Api public void Post(ReportStartupWizardComplete request) { _config.Configuration.IsStartupWizardCompleted = true; - _config.Configuration.AutoRunWebApp = true; _config.SetOptimalValues(); _config.SaveConfiguration(); @@ -101,14 +101,6 @@ namespace MediaBrowser.Api } } - public object Get(GetStartupInfo request) - { - return new StartupInfo - { - HasMediaEncoder = !string.IsNullOrWhiteSpace(_mediaEncoder.EncoderPath) - }; - } - public object Get(GetStartupConfiguration request) { var result = new StartupConfiguration @@ -129,6 +121,13 @@ namespace MediaBrowser.Api _config.SaveConfiguration(); } + public void Post(UpdateRemoteAccessConfiguration request) + { + _config.Configuration.EnableRemoteAccess = request.EnableRemoteAccess; + _config.Configuration.EnableUPnP = request.EnableAutomaticPortMapping; + _config.SaveConfiguration(); + } + public object Get(GetStartupUser request) { var user = _userManager.Users.First(); @@ -152,11 +151,11 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(user.ConnectUserName) && string.IsNullOrWhiteSpace(request.ConnectUserName)) { - await _connectManager.RemoveConnect(user.Id.ToString("N")).ConfigureAwait(false); + await _connectManager.RemoveConnect(user).ConfigureAwait(false); } else if (!string.Equals(user.ConnectUserName, request.ConnectUserName, StringComparison.OrdinalIgnoreCase)) { - result.UserLinkResult = await _connectManager.LinkUser(user.Id.ToString("N"), request.ConnectUserName).ConfigureAwait(false); + result.UserLinkResult = await _connectManager.LinkUser(user, request.ConnectUserName).ConfigureAwait(false); } return result; @@ -170,11 +169,6 @@ namespace MediaBrowser.Api public string PreferredMetadataLanguage { get; set; } } - public class StartupInfo - { - public bool HasMediaEncoder { get; set; } - } - public class StartupUser { public string Name { get; set; } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 4d4b4cb273..c8b0a32e95 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Api.Subtitles /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + public Guid Id { get; set; } [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] public int Index { get; set; } @@ -40,7 +40,7 @@ namespace MediaBrowser.Api.Subtitles public class SearchRemoteSubtitles : IReturn { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + public Guid Id { get; set; } [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Language { get; set; } @@ -48,20 +48,12 @@ namespace MediaBrowser.Api.Subtitles public bool? IsPerfectMatch { get; set; } } - [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")] - [Authenticated] - public class GetSubtitleProviders : IReturn - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] [Authenticated] public class DownloadRemoteSubtitles : IReturnVoid { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } + public Guid Id { get; set; } [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SubtitleId { get; set; } @@ -84,7 +76,7 @@ namespace MediaBrowser.Api.Subtitles /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + public Guid Id { get; set; } [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string MediaSourceId { get; set; } @@ -123,7 +115,7 @@ namespace MediaBrowser.Api.Subtitles [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] public int Index { get; set; } - [ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")] public int SegmentLength { get; set; } } @@ -195,7 +187,7 @@ namespace MediaBrowser.Api.Subtitles builder.AppendLine("#EXT-X-ENDLIST"); - return ResultFactory.GetResult(builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); + return ResultFactory.GetResult(Request, builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); } public async Task Get(GetSubtitle request) @@ -206,10 +198,11 @@ namespace MediaBrowser.Api.Subtitles } if (string.IsNullOrEmpty(request.Format)) { - var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); + var item = (Video)_libraryManager.GetItemById(request.Id); + var idString = request.Id.ToString("N"); var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null) - .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id)); + .First(i => string.Equals(i.Id, request.MediaSourceId ?? idString)); var subtitleStream = mediaSource.MediaStreams .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); @@ -227,22 +220,24 @@ namespace MediaBrowser.Api.Subtitles text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000"); - return ResultFactory.GetResult(text, MimeTypes.GetMimeType("file." + request.Format)); + return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format)); } } } - return ResultFactory.GetResult(await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format)); + return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format)); } private Task GetSubtitles(GetSubtitle request) { - return _subtitleEncoder.GetSubtitles(request.Id, + var item = _libraryManager.GetItemById(request.Id); + + return _subtitleEncoder.GetSubtitles(item, request.MediaSourceId, request.Index, request.Format, request.StartPositionTicks, - request.EndPositionTicks, + request.EndPositionTicks ?? 0, request.CopyTimestamps, CancellationToken.None); } @@ -251,30 +246,20 @@ namespace MediaBrowser.Api.Subtitles { var video = (Video)_libraryManager.GetItemById(request.Id); - var response = await _subtitleManager.SearchSubtitles(video, request.Language, request.IsPerfectMatch, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(response); + return await _subtitleManager.SearchSubtitles(video, request.Language, request.IsPerfectMatch, CancellationToken.None).ConfigureAwait(false); } - public void Delete(DeleteSubtitle request) + public Task Delete(DeleteSubtitle request) { - var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index); - - Task.WaitAll(task); - } - - public object Get(GetSubtitleProviders request) - { - var result = _subtitleManager.GetProviders(request.Id); - - return ToOptimizedResult(result); + var item = _libraryManager.GetItemById(request.Id); + return _subtitleManager.DeleteSubtitles(item, request.Index); } public async Task Get(GetRemoteSubtitles request) { var result = await _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).ConfigureAwait(false); - return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format)); + return ResultFactory.GetResult(Request, result.Stream, MimeTypes.GetMimeType("file." + result.Format)); } public void Post(DownloadRemoteSubtitles request) diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 3b918d8a2f..07053c5542 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -14,11 +14,11 @@ using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { [Route("/Users/{UserId}/Suggestions", "GET", Summary = "Gets items based on a query.")] - public class GetSuggestedItems : IReturn> + public class GetSuggestedItems : IReturn> { public string MediaType { get; set; } public string Type { get; set; } - public string UserId { get; set; } + public Guid UserId { get; set; } public bool EnableTotalRecordCount { get; set; } public int? StartIndex { get; set; } public int? Limit { get; set; } @@ -51,14 +51,12 @@ namespace MediaBrowser.Api public object Get(GetSuggestedItems request) { - var result = GetResultItems(request); - - return ToOptimizedResult(result); + return GetResultItems(request); } private QueryResult GetResultItems(GetSuggestedItems request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var dtoOptions = GetDtoOptions(_authContext, request); var result = GetItems(request, user, dtoOptions); @@ -81,7 +79,7 @@ namespace MediaBrowser.Api { return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { - OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), MediaTypes = request.GetMediaTypes(), IncludeItemTypes = request.GetIncludeItemTypes(), IsVirtualItem = false, diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs index e3a18a9335..d55c57ffaa 100644 --- a/MediaBrowser.Api/System/ActivityLogService.cs +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -24,8 +24,10 @@ namespace MediaBrowser.Api.System [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } - [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string MinDate { get; set; } + + public bool? HasUserId { get; set; } } [Authenticated(Roles = "Admin")] @@ -44,7 +46,7 @@ namespace MediaBrowser.Api.System (DateTime?)null : DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - var result = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit); + var result = _activityManager.GetActivityLogEntries(minDate, request.HasUserId, request.StartIndex, request.Limit); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs index f9cac7389d..6991244c6c 100644 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs @@ -4,7 +4,6 @@ using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Threading.Tasks; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Threading; using System.Threading; namespace MediaBrowser.Api.System @@ -28,7 +27,7 @@ namespace MediaBrowser.Api.System /// private readonly IActivityManager _activityManager; - public ActivityLogWebSocketListener(ILogger logger, ITimerFactory timerFactory, IActivityManager activityManager) : base(logger, timerFactory) + public ActivityLogWebSocketListener(ILogger logger, IActivityManager activityManager) : base(logger) { _activityManager = activityManager; _activityManager.EntryCreated += _activityManager_EntryCreated; @@ -48,14 +47,7 @@ namespace MediaBrowser.Api.System { return Task.FromResult(new List()); } - - protected override bool SendOnTimer - { - get - { - return false; - } - } + protected override void Dispose(bool dispose) { diff --git a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs deleted file mode 100644 index 63847f2b52..0000000000 --- a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.System; -using System.Threading.Tasks; -using MediaBrowser.Model.Threading; -using System.Threading; - -namespace MediaBrowser.Api.System -{ - /// - /// Class SystemInfoWebSocketListener - /// - public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "SystemInfo"; } - } - - /// - /// The _kernel - /// - private readonly IServerApplicationHost _appHost; - - /// - /// Initializes a new instance of the class. - /// - public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost, ITimerFactory timerFactory) - : base(logger, timerFactory) - { - _appHost = appHost; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) - { - return _appHost.GetSystemInfo(cancellationToken); - } - } -} diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index c0bbf70ead..d2880f735a 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.Api.System } [Route("/System/Ping", "POST")] + [Route("/System/Ping", "GET")] public class PingSystem : IReturnVoid { @@ -79,6 +80,13 @@ namespace MediaBrowser.Api.System public string Name { get; set; } } + [Route("/System/WakeOnLanInfo", "GET", Summary = "Gets wake on lan information")] + [Authenticated] + public class GetWakeOnLanInfo : IReturn + { + + } + /// /// Class SystemInfoService /// @@ -116,6 +124,13 @@ namespace MediaBrowser.Api.System return _appHost.Name; } + public object Get(GetWakeOnLanInfo request) + { + var result = _appHost.GetWakeOnLanInfo(); + + return ToOptimizedResult(result); + } + public object Get(GetServerLogs request) { IEnumerable files; diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index fd81a9a3e6..6cbb4062dc 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -88,7 +88,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Skips over a given number of items within the results. Use for paging. @@ -131,11 +131,6 @@ namespace MediaBrowser.Api public bool? EnableUserData { get; set; } } - [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")] - public class GetSimilarShows : BaseGetSimilarItemsFromItem - { - } - [Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")] public class GetEpisodes : IReturn>, IHasItemFields, IHasDtoOptions { @@ -144,7 +139,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Fields to return within the items, in addition to basic information @@ -212,7 +207,7 @@ namespace MediaBrowser.Api /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Fields to return within the items, in addition to basic information @@ -288,66 +283,20 @@ namespace MediaBrowser.Api _authContext = authContext; } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSimilarShows request) - { - var result = GetSimilarItemsResult(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - private QueryResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) - { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) ? - (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : - _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Limit = request.Limit, - IncludeItemTypes = new[] - { - typeof(Series).Name - }, - SimilarTo = item, - DtoOptions = dtoOptions - - }); - - var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); - - var result = new QueryResult - { - Items = returnList, - - TotalRecordCount = itemsResult.Count - }; - - return result; - } - public object Get(GetUpcomingEpisodes request) { var user = _userManager.GetUserById(request.UserId); var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1); - var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); + var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId); var options = GetDtoOptions(_authContext, request); var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new Tuple(i, SortOrder.Ascending)).ToArray(), + OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(), MinPremiereDate = minPremiereDate, StartIndex = request.StartIndex, Limit = request.Limit, @@ -365,7 +314,7 @@ namespace MediaBrowser.Api Items = returnItems }; - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -391,7 +340,7 @@ namespace MediaBrowser.Api var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user); - return ToOptimizedSerializedResultUsingCache(new QueryResult + return ToOptimizedResult(new QueryResult { TotalRecordCount = result.TotalRecordCount, Items = returnItems diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 4018759d99..cd3c80463b 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -56,9 +56,7 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetArtist request) { - var result = GetItem(request); - - return ToOptimizedResult(result); + return GetItem(request); } /// @@ -71,8 +69,8 @@ namespace MediaBrowser.Api.UserLibrary var dtoOptions = GetDtoOptions(AuthorizationContext, request); var item = GetArtist(request.Name, LibraryManager, dtoOptions); - - if (!string.IsNullOrWhiteSpace(request.UserId)) + + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -94,9 +92,7 @@ namespace MediaBrowser.Api.UserLibrary //request.IncludeItemTypes = "Audio,MusicVideo"; } - var result = GetResultSlim(request); - - return ToOptimizedResult(result); + return GetResultSlim(request); } /// diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index ed0c4069b3..fe8b446a1f 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -56,10 +56,10 @@ namespace MediaBrowser.Api.UserLibrary { BaseItem parentItem; - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); - parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId); + parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId); } else { @@ -73,18 +73,12 @@ namespace MediaBrowser.Api.UserLibrary { var parent = GetParentItem(request); - var collectionFolder = parent as ICollectionFolder; + var collectionFolder = parent as IHasCollectionType; if (collectionFolder != null) { return collectionFolder.CollectionType; } - var view = parent as UserView; - if (view != null) - { - return view.ViewType; - } - return null; } @@ -95,10 +89,10 @@ namespace MediaBrowser.Api.UserLibrary User user = null; BaseItem parentItem; - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { user = UserManager.GetUserById(request.UserId); - parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId); + parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId); } else { @@ -123,25 +117,27 @@ namespace MediaBrowser.Api.UserLibrary Tags = request.GetTags(), OfficialRatings = request.GetOfficialRatings(), Genres = request.GetGenres(), - GenreIds = request.GetGenreIds(), - StudioIds = request.GetStudioIds(), + GenreIds = GetGuids(request.GenreIds), + StudioIds = GetGuids(request.StudioIds), Person = request.Person, - PersonIds = request.GetPersonIds(), + PersonIds = GetGuids(request.PersonIds), PersonTypes = request.GetPersonTypes(), Years = request.GetYears(), MinCommunityRating = request.MinCommunityRating, - DtoOptions = dtoOptions + DtoOptions = dtoOptions, + SearchTerm = request.SearchTerm, + EnableTotalRecordCount = request.EnableTotalRecordCount }; if (!string.IsNullOrWhiteSpace(request.ParentId)) { if (parentItem is Folder) { - query.AncestorIds = new[] { request.ParentId }; + query.AncestorIds = new[] { new Guid(request.ParentId) }; } else { - query.ItemIds = new[] { request.ParentId }; + query.ItemIds = new[] { new Guid(request.ParentId) }; } } @@ -158,7 +154,7 @@ namespace MediaBrowser.Api.UserLibrary { return null; } - }).Where(i => i != null).Select(i => i.Id.ToString("N")).ToArray(); + }).Where(i => i != null).Select(i => i.Id).ToArray(); } foreach (var filter in request.GetFilters()) @@ -197,10 +193,9 @@ namespace MediaBrowser.Api.UserLibrary var result = GetItems(request, query); - var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions); var dtos = result.Items.Select(i => { - var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, syncProgess, user); + var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user); if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes)) { @@ -247,10 +242,10 @@ namespace MediaBrowser.Api.UserLibrary User user = null; BaseItem parentItem; - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { user = UserManager.GetUserById(request.UserId); - parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId); + parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId); } else { @@ -277,7 +272,7 @@ namespace MediaBrowser.Api.UserLibrary { var folder = (Folder)parentItem; - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { items = request.Recursive ? folder.GetRecursiveChildren(user, query).ToList() : @@ -297,9 +292,7 @@ namespace MediaBrowser.Api.UserLibrary var extractedItems = GetAllItems(request, items); - var filteredItems = FilterItems(request, extractedItems, user); - - filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy()); + var filteredItems = LibraryManager.Sort(extractedItems, user, request.GetOrderBy()); var ibnItemsArray = filteredItems.ToList(); @@ -326,133 +319,13 @@ namespace MediaBrowser.Api.UserLibrary var tuples = ibnItems.Select(i => new Tuple>(i, new List())); - var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions); - var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, syncProgess, user)); + var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user)); result.Items = dtos.Where(i => i != null).ToArray(); return result; } - /// - /// Filters the items. - /// - /// The request. - /// The items. - /// The user. - /// IEnumerable{`0}. - private IEnumerable FilterItems(GetItemsByName request, IEnumerable items, User user) - { - if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater)) - { - items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); - } - if (!string.IsNullOrEmpty(request.NameStartsWith)) - { - items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); - } - - if (!string.IsNullOrEmpty(request.NameLessThan)) - { - items = items.Where(i => string.Compare(request.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1); - } - - var imageTypes = request.GetImageTypes(); - if (imageTypes.Length > 0) - { - items = items.Where(item => imageTypes.Any(item.HasImage)); - } - - var filters = request.GetFilters(); - - if (filters.Contains(ItemFilter.Dislikes)) - { - items = items.Where(i => - { - var userdata = UserDataRepository.GetUserData(user, i); - - return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value; - }); - } - - if (filters.Contains(ItemFilter.Likes)) - { - items = items.Where(i => - { - var userdata = UserDataRepository.GetUserData(user, i); - - return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value; - }); - } - - if (filters.Contains(ItemFilter.IsFavoriteOrLikes)) - { - items = items.Where(i => - { - var userdata = UserDataRepository.GetUserData(user, i); - - var likes = userdata.Likes ?? false; - var favorite = userdata.IsFavorite; - - return likes || favorite; - }); - } - - if (filters.Contains(ItemFilter.IsFavorite)) - { - items = items.Where(i => - { - var userdata = UserDataRepository.GetUserData(user, i); - - return userdata != null && userdata.IsFavorite; - }); - } - - // Avoid implicitly captured closure - var currentRequest = request; - return items.Where(i => ApplyAdditionalFilters(currentRequest, i, user, false)); - } - - private bool ApplyAdditionalFilters(BaseItemsRequest request, BaseItem i, User user, bool isPreFiltered) - { - if (!isPreFiltered) - { - // Apply tag filter - var tags = request.GetTags(); - if (tags.Length > 0) - { - if (!tags.Any(v => i.Tags.Contains(v, StringComparer.OrdinalIgnoreCase))) - { - return false; - } - } - - // Apply official rating filter - var officialRatings = request.GetOfficialRatings(); - if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty)) - { - return false; - } - - // Apply genre filter - var genres = request.GetGenres(); - if (genres.Length > 0 && !genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) - { - return false; - } - - // Apply year filter - var years = request.GetYears(); - if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value))) - { - return false; - } - } - - return true; - } - - /// /// Filters the items. /// diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 88d080db58..11c12c718f 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -58,6 +58,8 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsHD { get; set; } + public bool? Is4K { get; set; } + [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string LocationTypes { get; set; } @@ -103,9 +105,7 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasTvdbId { get; set; } - [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsInBoxSet { get; set; } - + [ApiMember(Name = "ExcludeItemIds", Description = "Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string ExcludeItemIds { get; set; } public bool EnableTotalRecordCount { get; set; } @@ -131,6 +131,8 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool Recursive { get; set; } + public string SearchTerm { get; set; } + /// /// Gets or sets the sort order. /// @@ -277,6 +279,10 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string ArtistIds { get; set; } + public string AlbumArtistIds { get; set; } + + public string ContributingArtistIds { get; set; } + [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Albums { get; set; } @@ -300,8 +306,8 @@ namespace MediaBrowser.Api.UserLibrary /// Gets or sets the user id. /// /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } /// /// Gets or sets the min offical rating. @@ -321,6 +327,12 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? CollapseBoxSetItems { get; set; } + + public int? MinWidth { get; set; } + public int? MinHeight { get; set; } + public int? MaxWidth { get; set; } + public int? MaxHeight { get; set; } + /// /// Gets or sets the video formats. /// @@ -369,11 +381,6 @@ namespace MediaBrowser.Api.UserLibrary return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } - public string[] GetExcludeItemIds() - { - return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - public string[] GetExcludeItemTypes() { return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); @@ -389,36 +396,11 @@ namespace MediaBrowser.Api.UserLibrary return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } - public string[] GetArtistIds() - { - return (ArtistIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetStudioIds() - { - return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetGenreIds() - { - return (GenreIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - public string[] GetPersonTypes() { return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } - public string[] GetPersonIds() - { - return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetItemIds() - { - return (Ids ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - public VideoType[] GetVideoTypes() { var val = VideoTypes; @@ -467,18 +449,18 @@ namespace MediaBrowser.Api.UserLibrary /// Gets the order by. /// /// IEnumerable{ItemSortBy}. - public Tuple[] GetOrderBy() + public ValueTuple[] GetOrderBy() { return GetOrderBy(SortBy, SortOrder); } - public static Tuple[] GetOrderBy(string sortBy, string requestedSortOrder) + public static ValueTuple[] GetOrderBy(string sortBy, string requestedSortOrder) { var val = sortBy; if (string.IsNullOrEmpty(val)) { - return new Tuple[] { }; + return Array.Empty>(); } var vals = val.Split(','); @@ -489,7 +471,7 @@ namespace MediaBrowser.Api.UserLibrary var sortOrders = requestedSortOrder.Split(','); - var result = new Tuple[vals.Length]; + var result = new ValueTuple[vals.Length]; for (var i = 0; i < vals.Length; i++) { @@ -498,7 +480,7 @@ namespace MediaBrowser.Api.UserLibrary var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending; - result[i] = new Tuple(vals[i], sortOrder); + result[i] = new ValueTuple(vals[i], sortOrder); } return result; diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index 0b2ca4daf9..476e881b97 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } [Authenticated] @@ -46,7 +46,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetItem(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -60,7 +60,7 @@ namespace MediaBrowser.Api.UserLibrary var item = GetGameGenre(request.Name, LibraryManager, dtoOptions); - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -79,7 +79,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetResultSlim(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } protected override QueryResult> GetItems(GetItemsByName request, InternalItemsQuery query) diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index d913f52d96..0bb7d7865a 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -56,7 +56,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetItem(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -70,7 +70,7 @@ namespace MediaBrowser.Api.UserLibrary var item = GetGenre(request.Name, LibraryManager, dtoOptions); - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -89,7 +89,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetResultSlim(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } protected override QueryResult> GetItems(GetItemsByName request, InternalItemsQuery query) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 1e531ba664..aa17e85f34 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -88,25 +88,25 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); + var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId); var options = GetDtoOptions(_authContext, request); - var ancestorIds = new List(); + var ancestorIds = new List(); var excludeFolderIds = user.Configuration.LatestItemsExcludes; - if (!parentIdGuid.HasValue && excludeFolderIds.Length > 0) + if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) { - ancestorIds = user.RootFolder.GetChildren(user, true) + ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N"))) - .Select(i => i.Id.ToString("N")) + .Select(i => i.Id) .ToList(); } var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) { - OrderBy = new[] { ItemSortBy.DatePlayed }.Select(i => new Tuple(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { ItemSortBy.DatePlayed }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), IsResumable = true, StartIndex = request.StartIndex, Limit = request.Limit, @@ -119,7 +119,8 @@ namespace MediaBrowser.Api.UserLibrary EnableTotalRecordCount = request.EnableTotalRecordCount, AncestorIds = ancestorIds.ToArray(), IncludeItemTypes = request.GetIncludeItemTypes(), - ExcludeItemTypes = request.GetExcludeItemTypes() + ExcludeItemTypes = request.GetExcludeItemTypes(), + SearchTerm = request.SearchTerm }); var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, options, user); @@ -130,7 +131,7 @@ namespace MediaBrowser.Api.UserLibrary Items = returnItems }; - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -147,7 +148,7 @@ namespace MediaBrowser.Api.UserLibrary var result = GetItems(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -156,7 +157,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. private QueryResult GetItems(GetItems request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var dtoOptions = GetDtoOptions(_authContext, request); @@ -191,26 +192,23 @@ namespace MediaBrowser.Api.UserLibrary /// private QueryResult GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) { + if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)) + { + request.ParentId = null; + } + else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) + { + request.ParentId = null; + } + var item = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId); - if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)) - { - if (item == null || user != null) - { - item = _libraryManager.RootFolder.Children.OfType().FirstOrDefault(i => string.Equals(i.GetType().Name, "PlaylistsFolder", StringComparison.OrdinalIgnoreCase)); - } - } - else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) - { - item = user == null ? _libraryManager.RootFolder : user.RootFolder; - } - if (item == null) { item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : user.RootFolder : + user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.ParentId); } @@ -222,6 +220,15 @@ namespace MediaBrowser.Api.UserLibrary folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); } + var hasCollectionType = folder as IHasCollectionType; + var isPlaylistQuery = (hasCollectionType != null && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)); + + if (isPlaylistQuery) + { + request.Recursive = true; + request.IncludeItemTypes = "Playlist"; + } + if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) { return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); @@ -266,8 +273,10 @@ namespace MediaBrowser.Api.UserLibrary HasImdbId = request.HasImdbId, IsPlaceHolder = request.IsPlaceHolder, IsLocked = request.IsLocked, - IsInBoxSet = request.IsInBoxSet, - IsHD = request.IsHD, + MinWidth = request.MinWidth, + MinHeight = request.MinHeight, + MaxWidth = request.MaxWidth, + MaxHeight = request.MaxHeight, Is3D = request.Is3D, HasTvdbId = request.HasTvdbId, HasTmdbId = request.HasTmdbId, @@ -279,33 +288,37 @@ namespace MediaBrowser.Api.UserLibrary HasThemeSong = request.HasThemeSong, HasThemeVideo = request.HasThemeVideo, HasTrailer = request.HasTrailer, + IsHD = request.IsHD, + Is4K = request.Is4K, Tags = request.GetTags(), OfficialRatings = request.GetOfficialRatings(), Genres = request.GetGenres(), - ArtistIds = request.GetArtistIds(), - GenreIds = request.GetGenreIds(), - StudioIds = request.GetStudioIds(), + ArtistIds = GetGuids(request.ArtistIds), + AlbumArtistIds = GetGuids(request.AlbumArtistIds), + ContributingArtistIds = GetGuids(request.ContributingArtistIds), + GenreIds = GetGuids(request.GenreIds), + StudioIds = GetGuids(request.StudioIds), Person = request.Person, - PersonIds = request.GetPersonIds(), + PersonIds = GetGuids(request.PersonIds), PersonTypes = request.GetPersonTypes(), Years = request.GetYears(), ImageTypes = request.GetImageTypes(), VideoTypes = request.GetVideoTypes(), AdjacentTo = request.AdjacentTo, - ItemIds = request.GetItemIds(), + ItemIds = GetGuids(request.Ids), MinPlayers = request.MinPlayers, MaxPlayers = request.MaxPlayers, MinCommunityRating = request.MinCommunityRating, MinCriticRating = request.MinCriticRating, - ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId), + ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId), ParentIndexNumber = request.ParentIndexNumber, - AiredDuringSeason = request.AiredDuringSeason, EnableTotalRecordCount = request.EnableTotalRecordCount, - ExcludeItemIds = request.GetExcludeItemIds(), - DtoOptions = dtoOptions + ExcludeItemIds = GetGuids(request.ExcludeItemIds), + DtoOptions = dtoOptions, + SearchTerm = request.SearchTerm }; - if (!string.IsNullOrWhiteSpace(request.Ids)) + if (!string.IsNullOrWhiteSpace(request.Ids) || !string.IsNullOrWhiteSpace(request.SearchTerm)) { query.CollapseBoxSetItems = false; } @@ -416,18 +429,18 @@ namespace MediaBrowser.Api.UserLibrary { return null; } - }).Where(i => i != null).Select(i => i.Id.ToString("N")).ToArray(); + }).Where(i => i != null).Select(i => i.Id).ToArray(); } // ExcludeArtistIds if (!string.IsNullOrWhiteSpace(request.ExcludeArtistIds)) { - query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|'); + query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds); } if (!string.IsNullOrWhiteSpace(request.AlbumIds)) { - query.AlbumIds = request.AlbumIds.Split('|'); + query.AlbumIds = GetGuids(request.AlbumIds); } // Albums @@ -441,7 +454,7 @@ namespace MediaBrowser.Api.UserLibrary Name = i, Limit = 1 - }).Select(albumId => albumId.ToString("N")); + }).Select(albumId => albumId); }).ToArray(); } @@ -459,7 +472,7 @@ namespace MediaBrowser.Api.UserLibrary { return null; } - }).Where(i => i != null).Select(i => i.Id.ToString("N")).ToArray(); + }).Where(i => i != null).Select(i => i.Id).ToArray(); } // Apply default sorting if none requested @@ -468,10 +481,10 @@ namespace MediaBrowser.Api.UserLibrary // Albums by artist if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase)) { - query.OrderBy = new Tuple[] + query.OrderBy = new [] { - new Tuple(ItemSortBy.ProductionYear, SortOrder.Descending), - new Tuple(ItemSortBy.SortName, SortOrder.Ascending) + new ValueTuple(ItemSortBy.ProductionYear, SortOrder.Descending), + new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) }; } } diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index 36dc773d4d..d4f1b3fa8d 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } [Authenticated] @@ -47,7 +47,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetItem(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -61,7 +61,7 @@ namespace MediaBrowser.Api.UserLibrary var item = GetMusicGenre(request.Name, LibraryManager, dtoOptions); - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -80,7 +80,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetResultSlim(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } protected override QueryResult> GetItems(GetItemsByName request, InternalItemsQuery query) diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 9417447d84..d2c9ef33aa 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -7,6 +7,8 @@ using MediaBrowser.Model.Dto; using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.Services; +using System; +using MediaBrowser.Model.Querying; namespace MediaBrowser.Api.UserLibrary { @@ -36,7 +38,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -54,7 +56,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetItem(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -67,8 +69,8 @@ namespace MediaBrowser.Api.UserLibrary var dtoOptions = GetDtoOptions(AuthorizationContext, request); var item = GetPerson(request.Name, LibraryManager, dtoOptions); - - if (!string.IsNullOrWhiteSpace(request.UserId)) + + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -85,9 +87,7 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetPersons request) { - var result = GetResult(request); - - return ToOptimizedSerializedResultUsingCache(result); + return GetResultSlim(request); } /// @@ -98,48 +98,22 @@ namespace MediaBrowser.Api.UserLibrary /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. protected override IEnumerable GetAllItems(GetItemsByName request, IList items) { - var inputPersonTypes = ((GetPersons)request).PersonTypes; - var personTypes = string.IsNullOrEmpty(inputPersonTypes) ? new string[] { } : inputPersonTypes.Split(','); - - // Either get all people, or all people filtered by a specific person type - var allPeople = GetAllPeople(items, personTypes); - - return allPeople - .Select(i => i.Name) - .DistinctNames() - - .Select(name => - { - try - { - return LibraryManager.GetPerson(name); - } - catch - { - return null; - // Already logged at lower levels - } - } - - ).Where(i => i != null); + throw new NotImplementedException(); } - /// - /// Gets all people. - /// - /// The items list. - /// The person types. - /// IEnumerable{PersonInfo}. - private IEnumerable GetAllPeople(IList itemsList, string[] personTypes) + protected override QueryResult> GetItems(GetItemsByName request, InternalItemsQuery query) { - var allIds = itemsList.Select(i => i.Id).ToArray(); - - var allPeople = LibraryManager.GetPeople(new InternalPeopleQuery + var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery { - PersonTypes = personTypes + PersonTypes = query.PersonTypes, + NameContains = query.NameContains ?? query.SearchTerm }); - return allPeople.Where(i => allIds.Contains(i.ItemId)).OrderBy(p => p.SortOrder ?? int.MaxValue).ThenBy(p => p.Type); + return new QueryResult> + { + TotalRecordCount = items.Count, + Items = items.Take(query.Limit ?? int.MaxValue).Select(i => new Tuple(i, new ItemCounts())).ToArray() + }; } public PersonsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepository, IDtoService dtoService, IAuthorizationContext authorizationContext) : base(userManager, libraryManager, userDataRepository, itemRepository, dtoService, authorizationContext) diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index f10cccbb13..4a3204f71f 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -56,7 +56,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetItem(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -70,7 +70,7 @@ namespace MediaBrowser.Api.UserLibrary var item = GetStudio(request.Name, LibraryManager, dtoOptions); - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -89,7 +89,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetResultSlim(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } protected override QueryResult> GetItems(GetItemsByName request, InternalItemsQuery query) @@ -105,10 +105,7 @@ namespace MediaBrowser.Api.UserLibrary /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. protected override IEnumerable GetAllItems(GetItemsByName request, IList items) { - return items - .SelectMany(i => i.Studios) - .DistinctNames() - .Select(name => LibraryManager.GetStudio(name)); + throw new NotImplementedException(); } public StudiosService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepository, IDtoService dtoService, IAuthorizationContext authorizationContext) : base(userManager, libraryManager, userDataRepository, itemRepository, dtoService, authorizationContext) diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 1bbc740c01..30df0ad23c 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -50,7 +50,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -64,7 +64,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the item id. @@ -85,14 +85,14 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -106,14 +106,14 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -127,14 +127,14 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -148,14 +148,14 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } + public Guid Id { get; set; } /// /// Gets or sets a value indicating whether this is likes. @@ -176,7 +176,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -197,7 +197,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -215,13 +215,13 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } [ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int Limit { get; set; } [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { get; set; } + public Guid ParentId { get; set; } [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Fields { get; set; } @@ -291,7 +291,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetAsync(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public object Get(GetLatestMedia request) @@ -344,43 +344,15 @@ namespace MediaBrowser.Api.UserLibrary var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? - user.RootFolder : + _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); - var series = item as Series; + var dtoOptions = GetDtoOptions(_authContext, request); - // Get them from the child tree - if (series != null) - { - var dtoOptions = GetDtoOptions(_authContext, request); + var dtos = item.GetDisplayExtras() + .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); - // Avoid implicitly captured closure - var currentUser = user; - - var dtos = series - .GetEpisodes(user, dtoOptions) - .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, currentUser)); - - return dtos.ToArray(); - } - - var movie = item as IHasSpecialFeatures; - - // Get them from the db - if (movie != null) - { - var dtoOptions = GetDtoOptions(_authContext, request); - - var dtos = movie.SpecialFeatureIds - .Select(_libraryManager.GetItemById) - .OrderBy(i => i.SortName) - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); - - return dtos.ToArray(); - } - - return new BaseItemDto[] { }; + return dtos.ToArray(); } /// @@ -392,28 +364,15 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); - - List trailerIds = null; - - var hasTrailers = item as IHasTrailers; - if (hasTrailers != null) - { - trailerIds = hasTrailers.GetTrailerIds(); - } - else - { - trailerIds = new List(); - } + var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = trailerIds - .Select(_libraryManager.GetItemById) + var dtos = item.GetExtras(new[] { ExtraType.Trailer }) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) .ToArray(); - return ToOptimizedSerializedResultUsingCache(dtos); + return ToOptimizedResult(dtos); } /// @@ -425,7 +384,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); + var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); @@ -433,7 +392,7 @@ namespace MediaBrowser.Api.UserLibrary var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } private async Task RefreshItemOnDemandIfNeeded(BaseItem item) @@ -448,7 +407,7 @@ namespace MediaBrowser.Api.UserLibrary var options = new MetadataRefreshOptions(_fileSystem) { MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = ImageRefreshMode.FullRefresh, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, ForceSave = performFullRefresh }; @@ -466,13 +425,13 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var item = user.RootFolder; + var item = _libraryManager.GetUserRootFolder(); var dtoOptions = GetDtoOptions(_authContext, request); var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -484,7 +443,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id); + var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false); @@ -498,7 +457,7 @@ namespace MediaBrowser.Api.UserLibrary TotalRecordCount = dtos.Length }; - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -507,7 +466,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. public object Post(MarkFavoriteItem request) { - var dto = MarkFavorite(request.UserId, request.Id, true); + var dto = MarkFavorite(request.UserId, request.Id, true); return ToOptimizedResult(dto); } @@ -529,11 +488,11 @@ namespace MediaBrowser.Api.UserLibrary /// The user id. /// The item id. /// if set to true [is favorite]. - private UserItemDataDto MarkFavorite(string userId, string itemId, bool isFavorite) + private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite) { var user = _userManager.GetUserById(userId); - var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId); + var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); // Get the user data for this item var data = _userDataRepository.GetUserData(user, item); @@ -541,7 +500,7 @@ namespace MediaBrowser.Api.UserLibrary // Set favorite status data.IsFavorite = isFavorite; - _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); return _userDataRepository.GetUserDataDto(item, user); } @@ -563,7 +522,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. public object Post(UpdateUserItemRating request) { - var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes); + var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes); return ToOptimizedResult(dto); } @@ -574,18 +533,18 @@ namespace MediaBrowser.Api.UserLibrary /// The user id. /// The item id. /// if set to true [likes]. - private UserItemDataDto UpdateUserItemRating(string userId, string itemId, bool? likes) + private UserItemDataDto UpdateUserItemRating(Guid userId, Guid itemId, bool? likes) { var user = _userManager.GetUserById(userId); - var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId); + var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); // Get the user data for this item var data = _userDataRepository.GetUserData(user, item); data.Likes = likes; - _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); return _userDataRepository.GetUserDataDto(item, user); } diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 096157e47f..5e9270e0bf 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -24,10 +24,11 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } - [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? IncludeExternalContent { get; set; } + public bool IncludeHidden { get; set; } public string PresetViews { get; set; } } @@ -40,7 +41,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } public class UserViewsService : BaseApiService @@ -49,16 +50,18 @@ namespace MediaBrowser.Api.UserLibrary private readonly IUserViewManager _userViewManager; private readonly IDtoService _dtoService; private readonly IAuthorizationContext _authContext; + private readonly ILibraryManager _libraryManager; - public UserViewsService(IUserManager userManager, IUserViewManager userViewManager, IDtoService dtoService, IAuthorizationContext authContext) + public UserViewsService(IUserManager userManager, IUserViewManager userViewManager, IDtoService dtoService, IAuthorizationContext authContext, ILibraryManager libraryManager) { _userManager = userManager; _userViewManager = userViewManager; _dtoService = dtoService; _authContext = authContext; + _libraryManager = libraryManager; } - public async Task Get(GetUserViews request) + public object Get(GetUserViews request) { var query = new UserViewQuery { @@ -69,6 +72,7 @@ namespace MediaBrowser.Api.UserLibrary { query.IncludeExternalContent = request.IncludeExternalContent.Value; } + query.IncludeHidden = request.IncludeHidden; if (!string.IsNullOrWhiteSpace(request.PresetViews)) { @@ -78,18 +82,16 @@ namespace MediaBrowser.Api.UserLibrary var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1) { - query.PresetViews = new[] { CollectionType.Music, CollectionType.Movies, CollectionType.TvShows }; + query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows }; } - //query.PresetViews = new[] { CollectionType.Music, CollectionType.Movies, CollectionType.TvShows }; - var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false); + var folders = _userViewManager.GetUserViews(query); var dtoOptions = GetDtoOptions(_authContext, request); var fields = dtoOptions.Fields.ToList(); fields.Add(ItemFields.PrimaryImageAspectRatio); fields.Add(ItemFields.DisplayPreferencesId); - fields.Remove(ItemFields.SyncInfo); fields.Remove(ItemFields.BasicSyncInfo); dtoOptions.Fields = fields.ToArray(fields.Count); @@ -111,7 +113,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var list = user.RootFolder + var list = _libraryManager.GetUserRootFolder() .GetChildren(user, true) .OfType() .Where(UserView.IsEligibleForGrouping) diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index db622a9b34..30ac88e00f 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -7,6 +7,7 @@ using MediaBrowser.Model.Dto; using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.Services; +using System; namespace MediaBrowser.Api.UserLibrary { @@ -36,7 +37,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// The user id. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } } /// @@ -54,7 +55,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetItem(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// @@ -68,7 +69,7 @@ namespace MediaBrowser.Api.UserLibrary var dtoOptions = GetDtoOptions(AuthorizationContext, request); - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { var user = UserManager.GetUserById(request.UserId); @@ -87,7 +88,7 @@ namespace MediaBrowser.Api.UserLibrary { var result = GetResult(request); - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } /// diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 66d6a024eb..29f3070a59 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Controller.Authentication; namespace MediaBrowser.Api { @@ -51,22 +52,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// - /// Class GetUser - /// - [Route("/Users/{Id}/Offline", "GET", Summary = "Gets an offline user record by Id")] - [Authenticated] - public class GetOfflineUser : IReturn - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -81,7 +67,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -95,7 +81,7 @@ namespace MediaBrowser.Api /// /// The id. [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } + public Guid Id { get; set; } [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string Pw { get; set; } @@ -130,9 +116,6 @@ namespace MediaBrowser.Api [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string Pw { get; set; } - - [ApiMember(Name = "PasswordMd5", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string PasswordMd5 { get; set; } } /// @@ -146,7 +129,7 @@ namespace MediaBrowser.Api /// Gets or sets the id. /// /// The id. - public string Id { get; set; } + public Guid Id { get; set; } /// /// Gets or sets the password. @@ -156,12 +139,6 @@ namespace MediaBrowser.Api public string CurrentPw { get; set; } - /// - /// Gets or sets the new password. - /// - /// The new password. - public string NewPassword { get; set; } - public string NewPw { get; set; } /// @@ -182,7 +159,7 @@ namespace MediaBrowser.Api /// Gets or sets the id. /// /// The id. - public string Id { get; set; } + public Guid Id { get; set; } /// /// Gets or sets the new password. @@ -216,7 +193,7 @@ namespace MediaBrowser.Api public class UpdateUserPolicy : UserPolicy, IReturnVoid { [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -227,7 +204,7 @@ namespace MediaBrowser.Api public class UpdateUserConfiguration : UserConfiguration, IReturnVoid { [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } + public Guid Id { get; set; } } /// @@ -296,7 +273,7 @@ namespace MediaBrowser.Api IsHidden = false, IsDisabled = false - }, true); + }, true, true); } /// @@ -306,10 +283,10 @@ namespace MediaBrowser.Api /// System.Object. public object Get(GetUsers request) { - return Get(request, false); + return Get(request, false, false); } - private object Get(GetUsers request, bool filterByDevice) + private object Get(GetUsers request, bool filterByDevice, bool filterByNetwork) { var users = _userManager.Users; @@ -334,7 +311,15 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(deviceId)) { - users = users.Where(i => _deviceManager.CanAccessDevice(i.Id.ToString("N"), deviceId)); + users = users.Where(i => _deviceManager.CanAccessDevice(i, deviceId)); + } + } + + if (filterByNetwork) + { + if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) + { + users = users.Where(i => i.Policy.EnableRemoteAccess); } } @@ -365,32 +350,16 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - public object Get(GetOfflineUser request) - { - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - var result = _userManager.GetOfflineUserDto(user); - - return ToOptimizedResult(result); - } - /// /// Deletes the specified request. /// /// The request. - public void Delete(DeleteUser request) + public Task Delete(DeleteUser request) { - var task = DeleteAsync(request); - - Task.WaitAll(task); + return DeleteAsync(request); } - public async Task DeleteAsync(DeleteUser request) + public Task DeleteAsync(DeleteUser request) { var user = _userManager.GetUserById(request.Id); @@ -399,9 +368,9 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), null); + _sessionMananger.RevokeUserTokens(user.Id, null); - await _userManager.DeleteUser(user).ConfigureAwait(false); + return _userManager.DeleteUser(user); } /// @@ -437,7 +406,6 @@ namespace MediaBrowser.Api DeviceName = auth.Device, Password = request.Pw, PasswordSha1 = request.Password, - PasswordMd5 = request.PasswordMd5, RemoteEndPoint = Request.RemoteIp, Username = request.Username @@ -450,10 +418,9 @@ namespace MediaBrowser.Api /// Posts the specified request. /// /// The request. - public void Post(UpdateUserPassword request) + public Task Post(UpdateUserPassword request) { - var task = PostAsync(request); - Task.WaitAll(task); + return PostAsync(request); } public async Task PostAsync(UpdateUserPassword request) @@ -469,22 +436,22 @@ namespace MediaBrowser.Api if (request.ResetPassword) { - _userManager.ResetPassword(user); + await _userManager.ResetPassword(user).ConfigureAwait(false); } else { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, null, Request.RemoteIp, false).ConfigureAwait(false); + var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, Request.RemoteIp, false).ConfigureAwait(false); if (success == null) { throw new ArgumentException("Invalid user or password entered."); } - _userManager.ChangePassword(user, request.NewPw, request.NewPassword); + await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false); var currentToken = _authContext.GetAuthorizationInfo(Request).Token; - _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken); + _sessionMananger.RevokeUserTokens(user.Id, currentToken); } } @@ -513,11 +480,11 @@ namespace MediaBrowser.Api /// Posts the specified request. /// /// The request. - public void Post(UpdateUser request) + public async Task Post(UpdateUser request) { var id = GetPathValue(1); - AssertCanUpdateUser(_authContext, _userManager, id, false); + AssertCanUpdateUser(_authContext, _userManager, new Guid(id), false); var dtoUser = request; @@ -526,15 +493,14 @@ namespace MediaBrowser.Api if (string.Equals(user.Name, dtoUser.Name, StringComparison.Ordinal)) { _userManager.UpdateUser(user); + _userManager.UpdateConfiguration(user, dtoUser.Configuration); } else { - var task = _userManager.RenameUser(user, dtoUser.Name); + await _userManager.RenameUser(user, dtoUser.Name).ConfigureAwait(false); - Task.WaitAll(task); + _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration); } - - _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration); } /// @@ -542,11 +508,11 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public object Post(CreateUserByName request) + public async Task Post(CreateUserByName request) { var dtoUser = request; - var newUser = _userManager.CreateUser(dtoUser.Name).Result; + var newUser = await _userManager.CreateUser(dtoUser.Name).ConfigureAwait(false); var result = _userManager.GetUserDto(newUser, Request.RemoteIp); @@ -558,16 +524,20 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public object Post(ForgotPassword request) + public async Task Post(ForgotPassword request) { var isLocal = Request.IsLocal || _networkManager.IsInLocalNetwork(Request.RemoteIp); - return _userManager.StartForgotPasswordProcess(request.EnteredUsername, isLocal); + var result = await _userManager.StartForgotPasswordProcess(request.EnteredUsername, isLocal).ConfigureAwait(false); + + return result; } - public object Post(ForgotPasswordPin request) + public async Task Post(ForgotPasswordPin request) { - return _userManager.RedeemPasswordResetPin(request.Pin); + var result = await _userManager.RedeemPasswordResetPin(request.Pin).ConfigureAwait(false); + + return result; } public void Post(UpdateUserConfiguration request) @@ -606,7 +576,7 @@ namespace MediaBrowser.Api } var currentToken = _authContext.GetAuthorizationInfo(Request).Token; - _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken); + _sessionMananger.RevokeUserTokens(user.Id, currentToken); } _userManager.UpdateUserPolicy(request.Id, request); diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index 3f4bb46f45..40d2e066c5 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Api public class GetAdditionalParts : IReturn> { [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Gets or sets the id. @@ -76,11 +76,11 @@ namespace MediaBrowser.Api /// System.Object. public object Get(GetAdditionalParts request) { - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) - ? (!string.IsNullOrWhiteSpace(request.UserId) - ? user.RootFolder + ? (!request.UserId.Equals(Guid.Empty) + ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); @@ -105,7 +105,7 @@ namespace MediaBrowser.Api TotalRecordCount = items.Length }; - return ToOptimizedSerializedResultUsingCache(result); + return ToOptimizedResult(result); } public void Delete(DeleteAlternateSources request) @@ -115,12 +115,12 @@ namespace MediaBrowser.Api foreach (var link in video.GetLinkedAlternateVersions()) { link.SetPrimaryVersionId(null); - link.LinkedAlternateVersions = Video.EmptyLinkedChildArray; + link.LinkedAlternateVersions = Array.Empty(); link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } - video.LinkedAlternateVersions = Video.EmptyLinkedChildArray; + video.LinkedAlternateVersions = Array.Empty(); video.SetPrimaryVersionId(null); video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } @@ -140,11 +140,6 @@ namespace MediaBrowser.Api var videosWithVersions = items.Where(i => i.MediaSourceCount > 1) .ToList(); - if (videosWithVersions.Count > 1) - { - throw new ArgumentException("Videos with sub-versions cannot be merged."); - } - var primaryVersion = videosWithVersions.FirstOrDefault(); if (primaryVersion == null) @@ -185,10 +180,23 @@ namespace MediaBrowser.Api Path = item.Path, ItemId = item.Id }); + + foreach (var linkedItem in item.LinkedAlternateVersions) + { + if (!list.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase))) + { + list.Add(linkedItem); + } + } + + if (item.LinkedAlternateVersions.Length > 0) + { + item.LinkedAlternateVersions = Array.Empty(); + item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); + } } primaryVersion.LinkedAlternateVersions = list.ToArray(); - primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } } diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs deleted file mode 100644 index 310e2aa638..0000000000 --- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace MediaBrowser.Common.Configuration -{ - public class ConfigurationUpdateEventArgs : EventArgs - { - /// - /// Gets or sets the key. - /// - /// The key. - public string Key { get; set; } - /// - /// Gets or sets the new configuration. - /// - /// The new configuration. - public object NewConfiguration { get; set; } - } -} diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs deleted file mode 100644 index d2446ce46d..0000000000 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ /dev/null @@ -1,82 +0,0 @@ - -namespace MediaBrowser.Common.Configuration -{ - /// - /// Interface IApplicationPaths - /// - public interface IApplicationPaths - { - /// - /// Gets the path to the program data folder - /// - /// The program data path. - string ProgramDataPath { get; } - - /// - /// Gets the path to the program system folder - /// - /// The program data path. - string ProgramSystemPath { get; } - - /// - /// Gets the folder path to the data directory - /// - /// The data directory. - string DataPath { get; } - - /// - /// Gets the image cache path. - /// - /// The image cache path. - string ImageCachePath { get; } - - /// - /// Gets the path to the plugin directory - /// - /// The plugins path. - string PluginsPath { get; } - - /// - /// Gets the path to the plugin configurations directory - /// - /// The plugin configurations path. - string PluginConfigurationsPath { get; } - - /// - /// Gets the path to where temporary update files will be stored - /// - /// The plugin configurations path. - string TempUpdatePath { get; } - - /// - /// Gets the path to the log directory - /// - /// The log directory path. - string LogDirectoryPath { get; } - - /// - /// Gets the path to the application configuration root directory - /// - /// The configuration directory path. - string ConfigurationDirectoryPath { get; } - - /// - /// Gets the path to the system configuration file - /// - /// The system configuration file path. - string SystemConfigurationFilePath { get; } - - /// - /// Gets the folder path to the cache directory - /// - /// The cache directory. - string CachePath { get; } - - /// - /// Gets the folder path to the temp directory within the cache folder - /// - /// The temp directory. - string TempDirectory { get; } - } - -} diff --git a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs deleted file mode 100644 index 6ed6385360..0000000000 --- a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Common.Configuration -{ - public interface IConfigurationFactory - { - IEnumerable GetConfigurations(); - } - - public class ConfigurationStore - { - public string Key { get; set; } - - public Type ConfigurationType { get; set; } - } - - public interface IValidatingConfiguration - { - void Validate(object oldConfig, object newConfig); - } -} diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs deleted file mode 100644 index d826a3ee78..0000000000 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ /dev/null @@ -1,82 +0,0 @@ -using MediaBrowser.Model.Configuration; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Common.Configuration -{ - public interface IConfigurationManager - { - /// - /// Occurs when [configuration updating]. - /// - event EventHandler NamedConfigurationUpdating; - - /// - /// Occurs when [configuration updated]. - /// - event EventHandler ConfigurationUpdated; - - /// - /// Occurs when [named configuration updated]. - /// - event EventHandler NamedConfigurationUpdated; - - /// - /// Gets or sets the application paths. - /// - /// The application paths. - IApplicationPaths CommonApplicationPaths { get; } - - /// - /// Gets the configuration. - /// - /// The configuration. - BaseApplicationConfiguration CommonConfiguration { get; } - - /// - /// Saves the configuration. - /// - void SaveConfiguration(); - - /// - /// Replaces the configuration. - /// - /// The new configuration. - void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration); - - /// - /// Gets the configuration. - /// - /// The key. - /// System.Object. - object GetConfiguration(string key); - - /// - /// Gets the type of the configuration. - /// - /// The key. - /// Type. - Type GetConfigurationType(string key); - - /// - /// Saves the configuration. - /// - /// The key. - /// The configuration. - void SaveConfiguration(string key, object configuration); - - /// - /// Adds the parts. - /// - /// The factories. - void AddParts(IEnumerable factories); - } - - public static class ConfigurationManagerExtensions - { - public static T GetConfiguration(this IConfigurationManager manager, string key) - { - return (T)manager.GetConfiguration(key); - } - } -} diff --git a/MediaBrowser.Common/Events/EventHelper.cs b/MediaBrowser.Common/Events/EventHelper.cs deleted file mode 100644 index 2bb52f0ae5..0000000000 --- a/MediaBrowser.Common/Events/EventHelper.cs +++ /dev/null @@ -1,108 +0,0 @@ -using MediaBrowser.Model.Logging; -using System; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Events -{ - /// - /// Class EventHelper - /// - public static class EventHelper - { - /// - /// Fires the event. - /// - /// The handler. - /// The sender. - /// The instance containing the event data. - /// The logger. - public static void QueueEventIfNotNull(EventHandler handler, object sender, EventArgs args, ILogger logger) - { - if (handler != null) - { - Task.Run(() => - { - try - { - handler(sender, args); - } - catch (Exception ex) - { - logger.ErrorException("Error in event handler", ex); - } - }); - } - } - - /// - /// Queues the event. - /// - /// - /// The handler. - /// The sender. - /// The args. - /// The logger. - public static void QueueEventIfNotNull(EventHandler handler, object sender, T args, ILogger logger) - { - if (handler != null) - { - Task.Run(() => - { - try - { - handler(sender, args); - } - catch (Exception ex) - { - logger.ErrorException("Error in event handler", ex); - } - }); - } - } - - /// - /// Fires the event. - /// - /// The handler. - /// The sender. - /// The instance containing the event data. - /// The logger. - public static void FireEventIfNotNull(EventHandler handler, object sender, EventArgs args, ILogger logger) - { - if (handler != null) - { - try - { - handler(sender, args); - } - catch (Exception ex) - { - logger.ErrorException("Error in event handler", ex); - } - } - } - - /// - /// Fires the event. - /// - /// - /// The handler. - /// The sender. - /// The args. - /// The logger. - public static void FireEventIfNotNull(EventHandler handler, object sender, T args, ILogger logger) - { - if (handler != null) - { - try - { - handler(sender, args); - } - catch (Exception ex) - { - logger.ErrorException("Error in event handler", ex); - } - } - } - } -} diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs deleted file mode 100644 index d7f4424faa..0000000000 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Globalization; -using System.Text.RegularExpressions; -using MediaBrowser.Model.Cryptography; - -namespace MediaBrowser.Common.Extensions -{ - /// - /// Class BaseExtensions - /// - public static class BaseExtensions - { - public static ICryptoProvider CryptographyProvider { get; set; } - - /// - /// Strips the HTML. - /// - /// The HTML string. - /// System.String. - public static string StripHtml(this string htmlString) - { - // http://stackoverflow.com/questions/1349023/how-can-i-strip-html-from-text-in-net - const string pattern = @"<(.|\n)*?>"; - - return Regex.Replace(htmlString, pattern, string.Empty).Trim(); - } - - /// - /// Gets the M d5. - /// - /// The STR. - /// Guid. - public static Guid GetMD5(this string str) - { - return CryptographyProvider.GetMD5(str); - } - - /// - /// Gets the MB id. - /// - /// The STR. - /// The type. - /// Guid. - /// type - [Obsolete("Use LibraryManager.GetNewItemId")] - public static Guid GetMBId(this string str, Type type) - { - if (type == null) - { - throw new ArgumentNullException("type"); - } - - var key = type.FullName + str.ToLower(); - - return key.GetMD5(); - } - } -} diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs deleted file mode 100644 index 89e20b1b41..0000000000 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; - -namespace MediaBrowser.Common.Extensions -{ - /// - /// Class ResourceNotFoundException - /// - public class ResourceNotFoundException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public ResourceNotFoundException() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// The message. - public ResourceNotFoundException(string message) - : base(message) - { - - } - } - - public class RemoteServiceUnavailableException : Exception - { - public RemoteServiceUnavailableException() - { - - } - - public RemoteServiceUnavailableException(string message) - : base(message) - { - - } - } - - public class RateLimitExceededException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public RateLimitExceededException() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// The message. - public RateLimitExceededException(string message) - : base(message) - { - - } - } -} diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs deleted file mode 100644 index dc0c9ac9bd..0000000000 --- a/MediaBrowser.Common/IApplicationHost.cs +++ /dev/null @@ -1,165 +0,0 @@ -using MediaBrowser.Common.Plugins; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Updates; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common -{ - /// - /// An interface to be implemented by the applications hosting a kernel - /// - public interface IApplicationHost - { - /// - /// Gets the display name of the operating system. - /// - /// The display name of the operating system. - string OperatingSystemDisplayName { get; } - - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the device identifier. - /// - /// The device identifier. - string SystemId { get; } - - /// - /// Occurs when [application updated]. - /// - event EventHandler> ApplicationUpdated; - - /// - /// Gets or sets a value indicating whether this instance has pending kernel reload. - /// - /// true if this instance has pending kernel reload; otherwise, false. - bool HasPendingRestart { get; } - - bool IsShuttingDown { get; } - - /// - /// Gets a value indicating whether this instance can self restart. - /// - /// true if this instance can self restart; otherwise, false. - bool CanSelfRestart { get; } - - /// - /// Occurs when [has pending restart changed]. - /// - event EventHandler HasPendingRestartChanged; - - /// - /// Notifies the pending restart. - /// - void NotifyPendingRestart(); - - /// - /// Restarts this instance. - /// - void Restart(); - - /// - /// Gets the application version. - /// - /// The application version. - Version ApplicationVersion { get; } - - /// - /// Gets or sets a value indicating whether this instance can self update. - /// - /// true if this instance can self update; otherwise, false. - bool CanSelfUpdate { get; } - - /// - /// Gets a value indicating whether this instance is first run. - /// - /// true if this instance is first run; otherwise, false. - bool IsFirstRun { get; } - - /// - /// Gets the failed assemblies. - /// - /// The failed assemblies. - List FailedAssemblies { get; } - - /// - /// Gets all concrete types. - /// - /// All concrete types. - Type[] AllConcreteTypes { get; } - - /// - /// Gets the exports. - /// - /// - /// if set to true [manage liftime]. - /// IEnumerable{``0}. - IEnumerable GetExports(bool manageLiftime = true); - - /// - /// Checks for update. - /// - /// Task{CheckForUpdateResult}. - Task CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress progress); - - /// - /// Updates the application. - /// - /// Task. - Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress progress); - - /// - /// Resolves this instance. - /// - /// - /// ``0. - T Resolve(); - - /// - /// Resolves this instance. - /// - /// - /// ``0. - T TryResolve(); - - /// - /// Shuts down. - /// - Task Shutdown(); - - /// - /// Gets the plugins. - /// - /// The plugins. - IPlugin[] Plugins { get; } - - /// - /// Removes the plugin. - /// - /// The plugin. - void RemovePlugin(IPlugin plugin); - - /// - /// Inits this instance. - /// - /// The progress. - /// Task. - Task Init(IProgress progress); - - /// - /// Creates the instance. - /// - /// The type. - /// System.Object. - object CreateInstance(Type type); - - PackageVersionClass SystemUpdateLevel { get; } - } -} diff --git a/MediaBrowser.Common/IDependencyContainer.cs b/MediaBrowser.Common/IDependencyContainer.cs deleted file mode 100644 index 423c1740a3..0000000000 --- a/MediaBrowser.Common/IDependencyContainer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MediaBrowser.Common -{ - public interface IDependencyContainer - { - void RegisterSingleInstance(T obj, bool manageLifetime = true) - where T : class; - - void RegisterSingleInstance(Func func) - where T : class; - - void Register(Type typeInterface, Type typeImplementation); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj deleted file mode 100644 index d561b634ec..0000000000 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ /dev/null @@ -1,90 +0,0 @@ - - - - - Debug - AnyCPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F} - Library - Properties - MediaBrowser.Common - MediaBrowser.Common - 512 - ..\ - 10.0.0 - 2.0 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - 11.0 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AnyCPU - - - none - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\SharedVersion.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} - MediaBrowser.Model - - - - - - - - - - - - \ No newline at end of file diff --git a/MediaBrowser.Common/MediaBrowser.Common.nuget.targets b/MediaBrowser.Common/MediaBrowser.Common.nuget.targets deleted file mode 100644 index e69ce0e64f..0000000000 --- a/MediaBrowser.Common/MediaBrowser.Common.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs deleted file mode 100644 index 8f0b155f34..0000000000 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Text; - -namespace MediaBrowser.Common.Net -{ - /// - /// Class HttpRequestOptions - /// - public class HttpRequestOptions - { - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - - public CompressionMethod? DecompressionMethod { get; set; } - - /// - /// Gets or sets the accept header. - /// - /// The accept header. - public string AcceptHeader - { - get { return GetHeaderValue("Accept"); } - set - { - RequestHeaders["Accept"] = value; - } - } - /// - /// Gets or sets the cancellation token. - /// - /// The cancellation token. - public CancellationToken CancellationToken { get; set; } - - /// - /// Gets or sets the resource pool. - /// - /// The resource pool. - public SemaphoreSlim ResourcePool { get; set; } - - /// - /// Gets or sets the user agent. - /// - /// The user agent. - public string UserAgent - { - get { return GetHeaderValue("User-Agent"); } - set - { - RequestHeaders["User-Agent"] = value; - } - } - - /// - /// Gets or sets the referrer. - /// - /// The referrer. - public string Referer { get; set; } - - /// - /// Gets or sets the host. - /// - /// The host. - public string Host { get; set; } - - /// - /// Gets or sets the progress. - /// - /// The progress. - public IProgress Progress { get; set; } - - /// - /// Gets or sets a value indicating whether [enable HTTP compression]. - /// - /// true if [enable HTTP compression]; otherwise, false. - public bool EnableHttpCompression { get; set; } - - public Dictionary RequestHeaders { get; private set; } - - public string RequestContentType { get; set; } - - public string RequestContent { get; set; } - public byte[] RequestContentBytes { get; set; } - - public bool BufferContent { get; set; } - - public bool LogRequest { get; set; } - public bool LogRequestAsDebug { get; set; } - public bool LogErrors { get; set; } - public bool LogResponse { get; set; } - public bool LogResponseHeaders { get; set; } - - public bool LogErrorResponseBody { get; set; } - public bool EnableKeepAlive { get; set; } - - public CacheMode CacheMode { get; set; } - public TimeSpan CacheLength { get; set; } - - public int TimeoutMs { get; set; } - public bool EnableDefaultUserAgent { get; set; } - - public bool AppendCharsetToMimeType { get; set; } - - private string GetHeaderValue(string name) - { - string value; - - RequestHeaders.TryGetValue(name, out value); - - return value; - } - - /// - /// Initializes a new instance of the class. - /// - public HttpRequestOptions() - { - EnableHttpCompression = true; - - RequestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - LogRequest = true; - LogErrors = true; - CacheMode = CacheMode.None; - - TimeoutMs = 20000; - } - - public void SetPostData(IDictionary values) - { - var strings = values.Keys.Select(key => string.Format("{0}={1}", key, values[key])); - var postContent = string.Join("&", strings.ToArray()); - - RequestContent = postContent; - RequestContentType = "application/x-www-form-urlencoded"; - } - } - - public enum CacheMode - { - None = 0, - Unconditional = 1 - } - - public enum CompressionMethod - { - Deflate, - Gzip - } -} diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs deleted file mode 100644 index 0d7fb69cb6..0000000000 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; - -namespace MediaBrowser.Common.Net -{ - /// - /// Class HttpResponseInfo - /// - public class HttpResponseInfo : IDisposable - { - /// - /// Gets or sets the type of the content. - /// - /// The type of the content. - public string ContentType { get; set; } - - /// - /// Gets or sets the response URL. - /// - /// The response URL. - public string ResponseUrl { get; set; } - - /// - /// Gets or sets the content. - /// - /// The content. - public Stream Content { get; set; } - - /// - /// Gets or sets the status code. - /// - /// The status code. - public HttpStatusCode StatusCode { get; set; } - - /// - /// Gets or sets the temp file path. - /// - /// The temp file path. - public string TempFilePath { get; set; } - - /// - /// Gets or sets the length of the content. - /// - /// The length of the content. - public long? ContentLength { get; set; } - - /// - /// Gets or sets the headers. - /// - /// The headers. - public Dictionary Headers { get; set; } - - private readonly IDisposable _disposable; - - public HttpResponseInfo(IDisposable disposable) - { - _disposable = disposable; - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - public HttpResponseInfo() - { - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - public void Dispose() - { - if (_disposable != null) - { - _disposable.Dispose(); - } - GC.SuppressFinalize(this); - } - } -} diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs deleted file mode 100644 index cf55119653..0000000000 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net -{ - /// - /// Interface IHttpClient - /// - public interface IHttpClient - { - /// - /// Gets the response. - /// - /// The options. - /// Task{HttpResponseInfo}. - Task GetResponse(HttpRequestOptions options); - - /// - /// Gets the specified options. - /// - /// The options. - /// Task{Stream}. - Task Get(HttpRequestOptions options); - - /// - /// Sends the asynchronous. - /// - /// The options. - /// The HTTP method. - /// Task{HttpResponseInfo}. - Task SendAsync(HttpRequestOptions options, string httpMethod); - - /// - /// Posts the specified options. - /// - /// The options. - /// Task{HttpResponseInfo}. - Task Post(HttpRequestOptions options); - - /// - /// Downloads the contents of a given url into a temporary location - /// - /// The options. - /// Task{System.String}. - /// progress - /// - Task GetTempFile(HttpRequestOptions options); - - /// - /// Gets the temporary file response. - /// - /// The options. - /// Task{HttpResponseInfo}. - Task GetTempFileResponse(HttpRequestOptions options); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs deleted file mode 100644 index 6ddc2e7997..0000000000 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using System.Collections.Generic; -using System; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net -{ - public interface INetworkManager - { - event EventHandler NetworkChanged; - - /// - /// Gets a random port number that is currently available - /// - /// System.Int32. - int GetRandomUnusedTcpPort(); - - int GetRandomUnusedUdpPort(); - - /// - /// Returns MAC Address from first Network Card in Computer - /// - /// [string] MAC Address - string GetMacAddress(); - - /// - /// Determines whether [is in private address space] [the specified endpoint]. - /// - /// The endpoint. - /// true if [is in private address space] [the specified endpoint]; otherwise, false. - bool IsInPrivateAddressSpace(string endpoint); - - /// - /// Gets the network shares. - /// - /// The path. - /// IEnumerable{NetworkShare}. - IEnumerable GetNetworkShares(string path); - - /// - /// Gets available devices within the domain - /// - /// PC's in the Domain - IEnumerable GetNetworkDevices(); - - /// - /// Determines whether [is in local network] [the specified endpoint]. - /// - /// The endpoint. - /// true if [is in local network] [the specified endpoint]; otherwise, false. - bool IsInLocalNetwork(string endpoint); - - List GetLocalIpAddresses(); - - IpAddressInfo ParseIpAddress(string ipAddress); - - bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo); - - Task GetHostAddressesAsync(string host); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs deleted file mode 100644 index 73be04ac88..0000000000 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ /dev/null @@ -1,294 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Serialization; -using System; -using System.IO; - -namespace MediaBrowser.Common.Plugins -{ - /// - /// Provides a common base class for all plugins - /// - /// The type of the T configuration type. - public abstract class BasePlugin : IPlugin, IPluginAssembly - where TConfigurationType : BasePluginConfiguration - { - /// - /// Gets the application paths. - /// - /// The application paths. - protected IApplicationPaths ApplicationPaths { get; private set; } - - /// - /// Gets the XML serializer. - /// - /// The XML serializer. - protected IXmlSerializer XmlSerializer { get; private set; } - - /// - /// Gets the name of the plugin - /// - /// The name. - public abstract string Name { get; } - - /// - /// Gets a value indicating whether this instance is first run. - /// - /// true if this instance is first run; otherwise, false. - public bool IsFirstRun { get; private set; } - - /// - /// Gets the description. - /// - /// The description. - public virtual string Description - { - get { return string.Empty; } - } - - /// - /// Gets the type of configuration this plugin uses - /// - /// The type of the configuration. - public Type ConfigurationType - { - get { return typeof(TConfigurationType); } - } - - public void SetAttributes(string assemblyFilePath, string assemblyFileName, Version assemblyVersion) - { - AssemblyFilePath = assemblyFilePath; - AssemblyFileName = assemblyFileName; - Version = assemblyVersion; - } - - public void SetId(Guid assemblyId) - { - Id = assemblyId; - } - - private Func _dateModifiedFn; - private Action _directoryCreateFn; - public void SetStartupInfo(bool isFirstRun, Func dateModifiedFn, Action directoryCreateFn) - { - IsFirstRun = isFirstRun; - - // hack alert, until the .net core transition is complete - _dateModifiedFn = dateModifiedFn; - _directoryCreateFn = directoryCreateFn; - } - - /// - /// Gets the unique id. - /// - /// The unique id. - public virtual Guid Id { get; private set; } - - /// - /// Gets the plugin version - /// - /// The version. - public Version Version { get; private set; } - - /// - /// Gets the name the assembly file - /// - /// The name of the assembly file. - protected string AssemblyFileName { get; private set; } - - /// - /// Gets the last date modified of the configuration - /// - /// The configuration date last modified. - public DateTime ConfigurationDateLastModified - { - get - { - // Ensure it's been lazy loaded - var config = Configuration; - - return _dateModifiedFn(ConfigurationFilePath); - } - } - - /// - /// Gets the path to the assembly file - /// - /// The assembly file path. - public string AssemblyFilePath { get; private set; } - - /// - /// The _configuration sync lock - /// - private readonly object _configurationSyncLock = new object(); - /// - /// The _configuration - /// - private TConfigurationType _configuration; - /// - /// Gets the plugin's configuration - /// - /// The configuration. - public TConfigurationType Configuration - { - get - { - // Lazy load - if (_configuration == null) - { - lock (_configurationSyncLock) - { - if (_configuration == null) - { - _configuration = LoadConfiguration(); - } - } - } - return _configuration; - } - protected set - { - _configuration = value; - } - } - - private TConfigurationType LoadConfiguration() - { - var path = ConfigurationFilePath; - - try - { - return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path); - } - catch - { - return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType)); - } - } - - /// - /// Gets the name of the configuration file. Subclasses should override - /// - /// The name of the configuration file. - public virtual string ConfigurationFileName - { - get { return Path.ChangeExtension(AssemblyFileName, ".xml"); } - } - - /// - /// Gets the full path to the configuration file - /// - /// The configuration file path. - public string ConfigurationFilePath - { - get - { - return Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName); - } - } - - /// - /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed - /// - /// The data folder path. - public string DataFolderPath - { - get - { - // Give the folder name the same name as the config file name - // We can always make this configurable if/when needed - return Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(ConfigurationFileName)); - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The application paths. - /// The XML serializer. - protected BasePlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) - { - ApplicationPaths = applicationPaths; - XmlSerializer = xmlSerializer; - } - - /// - /// The _save lock - /// - private readonly object _configurationSaveLock = new object(); - - /// - /// Saves the current configuration to the file system - /// - public virtual void SaveConfiguration() - { - lock (_configurationSaveLock) - { - _directoryCreateFn(Path.GetDirectoryName(ConfigurationFilePath)); - - XmlSerializer.SerializeToFile(Configuration, ConfigurationFilePath); - } - } - - /// - /// Completely overwrites the current configuration with a new copy - /// Returns true or false indicating success or failure - /// - /// The configuration. - /// configuration - public virtual void UpdateConfiguration(BasePluginConfiguration configuration) - { - if (configuration == null) - { - throw new ArgumentNullException("configuration"); - } - - Configuration = (TConfigurationType)configuration; - - SaveConfiguration(); - } - - /// - /// Gets the plugin info. - /// - /// PluginInfo. - public PluginInfo GetPluginInfo() - { - var info = new PluginInfo - { - Name = Name, - Version = Version.ToString(), - AssemblyFileName = AssemblyFileName, - ConfigurationDateLastModified = ConfigurationDateLastModified, - Description = Description, - Id = Id.ToString(), - ConfigurationFileName = ConfigurationFileName - }; - - return info; - } - - /// - /// Called when just before the plugin is uninstalled from the server. - /// - public virtual void OnUninstalling() - { - - } - - /// - /// Gets the plugin's configuration - /// - /// The configuration. - BasePluginConfiguration IPlugin.Configuration - { - get { return Configuration; } - } - } - - public interface IPluginAssembly - { - void SetAttributes(string assemblyFilePath, string assemblyFileName, Version assemblyVersion); - void SetId(Guid assemblyId); - } -} diff --git a/MediaBrowser.Common/Plugins/IDependencyModule.cs b/MediaBrowser.Common/Plugins/IDependencyModule.cs deleted file mode 100644 index 37ebe0d5b2..0000000000 --- a/MediaBrowser.Common/Plugins/IDependencyModule.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MediaBrowser.Common.Plugins -{ - public interface IDependencyModule - { - void BindDependencies(IDependencyContainer container); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs deleted file mode 100644 index 999a783fdf..0000000000 --- a/MediaBrowser.Common/Plugins/IPlugin.cs +++ /dev/null @@ -1,110 +0,0 @@ -using MediaBrowser.Model.Plugins; -using System; - -namespace MediaBrowser.Common.Plugins -{ - /// - /// Interface IPlugin - /// - public interface IPlugin - { - /// - /// Gets the name of the plugin - /// - /// The name. - string Name { get; } - - /// - /// Gets the description. - /// - /// The description. - string Description { get; } - - /// - /// Gets the type of configuration this plugin uses - /// - /// The type of the configuration. - Type ConfigurationType { get; } - - /// - /// Gets the unique id. - /// - /// The unique id. - Guid Id { get; } - - /// - /// Gets the plugin version - /// - /// The version. - Version Version { get; } - - /// - /// Gets a value indicating whether this instance is first run. - /// - /// true if this instance is first run; otherwise, false. - bool IsFirstRun { get; } - - /// - /// Gets the last date modified of the configuration - /// - /// The configuration date last modified. - DateTime ConfigurationDateLastModified { get; } - - /// - /// Gets the path to the assembly file - /// - /// The assembly file path. - string AssemblyFilePath { get; } - - /// - /// Gets the plugin's configuration - /// - /// The configuration. - BasePluginConfiguration Configuration { get; } - - /// - /// Gets the name of the configuration file. Subclasses should override - /// - /// The name of the configuration file. - string ConfigurationFileName { get; } - - /// - /// Gets the full path to the configuration file - /// - /// The configuration file path. - string ConfigurationFilePath { get; } - - /// - /// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed - /// - /// The data folder path. - string DataFolderPath { get; } - - /// - /// Saves the current configuration to the file system - /// - /// Cannot call Plugin.SaveConfiguration from the UI. - void SaveConfiguration(); - - /// - /// Completely overwrites the current configuration with a new copy - /// Returns true or false indicating success or failure - /// - /// The configuration. - /// configuration - void UpdateConfiguration(BasePluginConfiguration configuration); - - /// - /// Gets the plugin info. - /// - /// PluginInfo. - PluginInfo GetPluginInfo(); - - /// - /// Called when just before the plugin is uninstalled from the server. - /// - void OnUninstalling(); - - void SetStartupInfo(bool isFirstRun, Func dateModifiedFn, Action directoryCreateFn); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs deleted file mode 100644 index 5b318c6a78..0000000000 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Common.Progress -{ - /// - /// Class ActionableProgress - /// - /// - public class ActionableProgress : IProgress, IDisposable - { - /// - /// The _actions - /// - private readonly List> _actions = new List>(); - public event EventHandler ProgressChanged; - - /// - /// Registers the action. - /// - /// The action. - public void RegisterAction(Action action) - { - _actions.Add(action); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _actions.Clear(); - } - } - - public void Report(T value) - { - if (ProgressChanged != null) - { - ProgressChanged(this, value); - } - - foreach (var action in _actions) - { - action(value); - } - } - } - - public class SimpleProgress : IProgress - { - public event EventHandler ProgressChanged; - - public void Report(T value) - { - if (ProgressChanged != null) - { - ProgressChanged(this, value); - } - } - } -} diff --git a/MediaBrowser.Common/Properties/AssemblyInfo.cs b/MediaBrowser.Common/Properties/AssemblyInfo.cs deleted file mode 100644 index 09fd68f93a..0000000000 --- a/MediaBrowser.Common/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MediaBrowser.Common")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MediaBrowser.Common")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// \ No newline at end of file diff --git a/MediaBrowser.Common/Security/IRequiresRegistration.cs b/MediaBrowser.Common/Security/IRequiresRegistration.cs deleted file mode 100644 index 7b1667c2e2..0000000000 --- a/MediaBrowser.Common/Security/IRequiresRegistration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Security -{ - public interface IRequiresRegistration - { - /// - /// Load all registration information required for this entity. - /// Your class should re-load all MBRegistrationRecords when this is called even if they were - /// previously loaded. - /// - /// - Task LoadRegistrationInfoAsync(); - } -} diff --git a/MediaBrowser.Common/Security/ISecurityManager.cs b/MediaBrowser.Common/Security/ISecurityManager.cs deleted file mode 100644 index b47511c332..0000000000 --- a/MediaBrowser.Common/Security/ISecurityManager.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Model.Entities; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Security -{ - public interface ISecurityManager - { - /// - /// Gets a value indicating whether this instance is MB supporter. - /// - /// true if this instance is MB supporter; otherwise, false. - bool IsMBSupporter { get; } - - /// - /// Gets or sets the supporter key. - /// - /// The supporter key. - string SupporterKey { get; set; } - - /// - /// Gets the registration status. Overload to support existing plug-ins. - /// - /// The feature. - /// The MB2 equivalent. - /// Task{MBRegistrationRecord}. - Task GetRegistrationStatus(string feature, string mb2Equivalent = null); - - /// - /// Gets the registration status. - /// - /// The feature. - /// The MB2 equivalent. - /// The version of the feature - /// Task{MBRegistrationRecord}. - Task GetRegistrationStatus(string feature, string mb2Equivalent, string version); - - /// - /// Load all registration info for all entities that require registration - /// - /// - Task LoadAllRegistrationInfo(); - - /// - /// Register and app store sale with our back-end - /// - /// Json parameters to pass to admin server - Task RegisterAppStoreSale(string parameters); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Security/PaymentRequiredException.cs b/MediaBrowser.Common/Security/PaymentRequiredException.cs deleted file mode 100644 index 27b3e69613..0000000000 --- a/MediaBrowser.Common/Security/PaymentRequiredException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace MediaBrowser.Common.Security -{ - public class PaymentRequiredException : Exception - { - } -} diff --git a/MediaBrowser.Common/Updates/GithubUpdater.cs b/MediaBrowser.Common/Updates/GithubUpdater.cs deleted file mode 100644 index 4275799a98..0000000000 --- a/MediaBrowser.Common/Updates/GithubUpdater.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Updates; - -namespace MediaBrowser.Common.Updates -{ - public class GithubUpdater - { - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _jsonSerializer; - - public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer) - { - _httpClient = httpClient; - _jsonSerializer = jsonSerializer; - } - - public async Task CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, TimeSpan cacheLength, CancellationToken cancellationToken) - { - var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository); - - var options = new HttpRequestOptions - { - Url = url, - EnableKeepAlive = false, - CancellationToken = cancellationToken, - UserAgent = "Emby/3.0", - BufferContent = false - }; - - if (cacheLength.Ticks > 0) - { - options.CacheMode = CacheMode.Unconditional; - options.CacheLength = cacheLength; - } - - using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) - { - using (var stream = response.Content) - { - var obj = _jsonSerializer.DeserializeFromStream(stream); - - return CheckForUpdateResult(obj, minVersion, updateLevel, assetFilename, packageName, targetFilename); - } - } - } - - private CheckForUpdateResult CheckForUpdateResult(RootObject[] obj, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename) - { - if (updateLevel == PackageVersionClass.Release) - { - // Technically all we need to do is check that it's not pre-release - // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly. - obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray(); - } - else if (updateLevel == PackageVersionClass.Beta) - { - obj = obj.Where(i => i.prerelease && i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase)).ToArray(); - } - else if (updateLevel == PackageVersionClass.Dev) - { - obj = obj.Where(i => !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) || i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - var availableUpdate = obj - .Select(i => CheckForUpdateResult(i, minVersion, assetFilename, packageName, targetFilename)) - .Where(i => i != null) - .OrderByDescending(i => Version.Parse(i.AvailableVersion)) - .FirstOrDefault(); - - return availableUpdate ?? new CheckForUpdateResult - { - IsUpdateAvailable = false - }; - } - - private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel) - { - if (updateLevel == PackageVersionClass.Beta) - { - return i.prerelease && i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase); - } - if (updateLevel == PackageVersionClass.Dev) - { - return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) || - i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase); - } - - // Technically all we need to do is check that it's not pre-release - // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly. - return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && - !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase); - } - - public async Task> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken) - { - var list = new List(); - - var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository); - - var options = new HttpRequestOptions - { - Url = url, - EnableKeepAlive = false, - CancellationToken = cancellationToken, - UserAgent = "Emby/3.0", - BufferContent = false - }; - - using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) - { - using (var stream = response.Content) - { - var obj = _jsonSerializer.DeserializeFromStream(stream); - - obj = obj.Where(i => (i.assets ?? new List()).Any(a => IsAsset(a, assetFilename, i.tag_name))).ToArray(); - - list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1)); - list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1)); - list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1)); - - return list; - } - } - } - - public Version GetVersion(RootObject obj) - { - Version version; - if (!Version.TryParse(obj.tag_name, out version)) - { - return new Version(1, 0); - } - - return version; - } - - private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename) - { - Version version; - var versionString = obj.tag_name; - if (!Version.TryParse(versionString, out version)) - { - return null; - } - - if (version < minVersion) - { - return null; - } - - var asset = (obj.assets ?? new List()).FirstOrDefault(i => IsAsset(i, assetFilename, versionString)); - - if (asset == null) - { - return null; - } - - return new CheckForUpdateResult - { - AvailableVersion = version.ToString(), - IsUpdateAvailable = version > minVersion, - Package = new PackageVersionInfo - { - classification = obj.prerelease ? - (obj.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase) ? PackageVersionClass.Dev : PackageVersionClass.Beta) : - PackageVersionClass.Release, - name = packageName, - sourceUrl = asset.browser_download_url, - targetFilename = targetFilename, - versionStr = version.ToString(), - requiredVersionStr = "1.0.0", - description = obj.body, - infoUrl = obj.html_url - } - }; - } - - private bool IsAsset(Asset asset, string assetFilename, string version) - { - var downloadFilename = Path.GetFileName(asset.browser_download_url) ?? string.Empty; - - assetFilename = assetFilename.Replace("{version}", version); - - if (downloadFilename.IndexOf(assetFilename, StringComparison.OrdinalIgnoreCase) != -1) - { - return true; - } - - return string.Equals(assetFilename, downloadFilename, StringComparison.OrdinalIgnoreCase); - } - - public class Uploader - { - public string login { get; set; } - public int id { get; set; } - public string avatar_url { get; set; } - public string gravatar_id { get; set; } - public string url { get; set; } - public string html_url { get; set; } - public string followers_url { get; set; } - public string following_url { get; set; } - public string gists_url { get; set; } - public string starred_url { get; set; } - public string subscriptions_url { get; set; } - public string organizations_url { get; set; } - public string repos_url { get; set; } - public string events_url { get; set; } - public string received_events_url { get; set; } - public string type { get; set; } - public bool site_admin { get; set; } - } - - public class Asset - { - public string url { get; set; } - public int id { get; set; } - public string name { get; set; } - public object label { get; set; } - public Uploader uploader { get; set; } - public string content_type { get; set; } - public string state { get; set; } - public int size { get; set; } - public int download_count { get; set; } - public string created_at { get; set; } - public string updated_at { get; set; } - public string browser_download_url { get; set; } - } - - public class Author - { - public string login { get; set; } - public int id { get; set; } - public string avatar_url { get; set; } - public string gravatar_id { get; set; } - public string url { get; set; } - public string html_url { get; set; } - public string followers_url { get; set; } - public string following_url { get; set; } - public string gists_url { get; set; } - public string starred_url { get; set; } - public string subscriptions_url { get; set; } - public string organizations_url { get; set; } - public string repos_url { get; set; } - public string events_url { get; set; } - public string received_events_url { get; set; } - public string type { get; set; } - public bool site_admin { get; set; } - } - - public class RootObject - { - public string url { get; set; } - public string assets_url { get; set; } - public string upload_url { get; set; } - public string html_url { get; set; } - public int id { get; set; } - public string tag_name { get; set; } - public string target_commitish { get; set; } - public string name { get; set; } - public bool draft { get; set; } - public Author author { get; set; } - public bool prerelease { get; set; } - public string created_at { get; set; } - public string published_at { get; set; } - public List assets { get; set; } - public string tarball_url { get; set; } - public string zipball_url { get; set; } - public string body { get; set; } - } - } -} diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs deleted file mode 100644 index dab38b27c2..0000000000 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ /dev/null @@ -1,121 +0,0 @@ -using MediaBrowser.Common.Plugins; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Updates; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Updates -{ - public interface IInstallationManager : IDisposable - { - event EventHandler PackageInstalling; - event EventHandler PackageInstallationCompleted; - event EventHandler PackageInstallationFailed; - event EventHandler PackageInstallationCancelled; - - /// - /// The current installations - /// - List> CurrentInstallations { get; set; } - - /// - /// The completed installations - /// - IEnumerable CompletedInstallations { get; } - - /// - /// Occurs when [plugin uninstalled]. - /// - event EventHandler> PluginUninstalled; - - /// - /// Occurs when [plugin updated]. - /// - event EventHandler>> PluginUpdated; - - /// - /// Occurs when [plugin updated]. - /// - event EventHandler> PluginInstalled; - - /// - /// Gets all available packages. - /// - /// The cancellation token. - /// if set to true [with registration]. - /// Type of the package. - /// The application version. - /// Task{List{PackageInfo}}. - Task> GetAvailablePackages(CancellationToken cancellationToken, - bool withRegistration = true, - string packageType = null, - Version applicationVersion = null); - - /// - /// Gets all available packages from a static resource. - /// - /// The cancellation token. - /// Task{List{PackageInfo}}. - Task> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken); - - /// - /// Gets the package. - /// - /// The name. - /// The assembly guid - /// The classification. - /// The version. - /// Task{PackageVersionInfo}. - Task GetPackage(string name, string guid, PackageVersionClass classification, Version version); - - /// - /// Gets the latest compatible version. - /// - /// The name. - /// The assembly guid - /// The current server version. - /// The classification. - /// Task{PackageVersionInfo}. - Task GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release); - - /// - /// Gets the latest compatible version. - /// - /// The available packages. - /// The name. - /// The assembly guid - /// The current server version. - /// The classification. - /// PackageVersionInfo. - PackageVersionInfo GetLatestCompatibleVersion(IEnumerable availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release); - - /// - /// Gets the available plugin updates. - /// - /// The current server version. - /// if set to true [with auto update enabled]. - /// The cancellation token. - /// Task{IEnumerable{PackageVersionInfo}}. - Task> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken); - - /// - /// Installs the package. - /// - /// The package. - /// if set to true [is plugin]. - /// The progress. - /// The cancellation token. - /// Task. - /// package - Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress progress, CancellationToken cancellationToken); - - /// - /// Uninstalls a plugin - /// - /// The plugin. - /// - void UninstallPlugin(IPlugin plugin); - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs deleted file mode 100644 index 9dc8ead835..0000000000 --- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MediaBrowser.Model.Updates; - -namespace MediaBrowser.Common.Updates -{ - public class InstallationEventArgs - { - public InstallationInfo InstallationInfo { get; set; } - - public PackageVersionInfo PackageVersionInfo { get; set; } - } -} diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs deleted file mode 100644 index 69dc1ee982..0000000000 --- a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MediaBrowser.Common.Updates -{ - public class InstallationFailedEventArgs : InstallationEventArgs - { - public Exception Exception { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs deleted file mode 100644 index 54faa14432..0000000000 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ /dev/null @@ -1,99 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Querying; -using System; -using System.Linq; -using MediaBrowser.Model.Serialization; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Progress; - -namespace MediaBrowser.Controller.Channels -{ - public class Channel : Folder - { - public override bool IsVisible(User user) - { - if (user.Policy.BlockedChannels != null) - { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - else - { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - - return base.IsVisible(user); - } - - [IgnoreDataMember] - public override bool SupportsInheritedParentImages - { - get - { - return false; - } - } - - [IgnoreDataMember] - public override SourceType SourceType - { - get { return SourceType.Channel; } - } - - protected override QueryResult GetItemsInternal(InternalItemsQuery query) - { - try - { - // Don't blow up here because it could cause parent screens with other content to fail - return ChannelManager.GetChannelItemsInternal(new ChannelItemQuery - { - ChannelId = Id.ToString("N"), - Limit = query.Limit, - StartIndex = query.StartIndex, - UserId = query.User.Id.ToString("N"), - OrderBy = query.OrderBy - - }, new SimpleProgress(), CancellationToken.None).Result; - } - catch - { - // Already logged at lower levels - return new QueryResult(); - } - } - - protected override string GetInternalMetadataPath(string basePath) - { - return GetInternalMetadataPath(basePath, Id); - } - - public static string GetInternalMetadataPath(string basePath, Guid id) - { - return System.IO.Path.Combine(basePath, "channels", id.ToString("N"), "metadata"); - } - - public override bool CanDelete() - { - return false; - } - - protected override bool IsAllowTagFilterEnforced() - { - return false; - } - - internal static bool IsChannelVisible(BaseItem channelItem, User user) - { - var channel = ChannelManager.GetChannel(channelItem.ChannelId); - - return channel.IsVisible(user); - } - } -} diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs deleted file mode 100644 index 57c2f1f7fd..0000000000 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ /dev/null @@ -1,75 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Channels -{ - public class ChannelItemInfo : IHasProviderIds - { - public string Name { get; set; } - - public string SeriesName { get; set; } - - public string Id { get; set; } - - public ChannelItemType Type { get; set; } - - public string OfficialRating { get; set; } - - public string Overview { get; set; } - - public List Genres { get; set; } - public List Studios { get; set; } - public List Tags { get; set; } - - public List People { get; set; } - - public float? CommunityRating { get; set; } - - public long? RunTimeTicks { get; set; } - - public string ImageUrl { get; set; } - - public ChannelMediaType MediaType { get; set; } - public ChannelFolderType FolderType { get; set; } - - public ChannelMediaContentType ContentType { get; set; } - public ExtraType ExtraType { get; set; } - public List TrailerTypes { get; set; } - - public Dictionary ProviderIds { get; set; } - - public DateTime? PremiereDate { get; set; } - public int? ProductionYear { get; set; } - - public DateTime? DateCreated { get; set; } - - public int? IndexNumber { get; set; } - public int? ParentIndexNumber { get; set; } - - public List MediaSources { get; set; } - - public bool IsInfiniteStream { get; set; } - - public string HomePageUrl { get; set; } - - public List Artists { get; set; } - - public List AlbumArtists { get; set; } - - public ChannelItemInfo() - { - MediaSources = new List(); - TrailerTypes = new List(); - Genres = new List(); - Studios = new List(); - People = new List(); - Tags = new List(); - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - Artists = new List(); - AlbumArtists = new List(); - } - } -} diff --git a/MediaBrowser.Controller/Channels/ChannelItemResult.cs b/MediaBrowser.Controller/Channels/ChannelItemResult.cs deleted file mode 100644 index f888818118..0000000000 --- a/MediaBrowser.Controller/Channels/ChannelItemResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Channels -{ - public class ChannelItemResult - { - public List Items { get; set; } - - public int? TotalRecordCount { get; set; } - - public ChannelItemResult() - { - Items = new List(); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ChannelItemType.cs b/MediaBrowser.Controller/Channels/ChannelItemType.cs deleted file mode 100644 index 184ce8a767..0000000000 --- a/MediaBrowser.Controller/Channels/ChannelItemType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Controller.Channels -{ - public enum ChannelItemType - { - Media = 0, - - Folder = 1 - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs deleted file mode 100644 index cf7b6ba6a7..0000000000 --- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs +++ /dev/null @@ -1,117 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Controller.Channels -{ - public class ChannelMediaInfo - { - public string Path { get; set; } - - public Dictionary RequiredHttpHeaders { get; set; } - - public string Container { get; set; } - public string AudioCodec { get; set; } - public string VideoCodec { get; set; } - - public int? AudioBitrate { get; set; } - public int? VideoBitrate { get; set; } - public int? Width { get; set; } - public int? Height { get; set; } - public int? AudioChannels { get; set; } - public int? AudioSampleRate { get; set; } - - public string VideoProfile { get; set; } - public float? VideoLevel { get; set; } - public float? Framerate { get; set; } - - public bool? IsAnamorphic { get; set; } - - public MediaProtocol Protocol { get; set; } - - public long? RunTimeTicks { get; set; } - - public string Id { get; set; } - - public bool ReadAtNativeFramerate { get; set; } - public bool SupportsDirectPlay { get; set; } - - public ChannelMediaInfo() - { - RequiredHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - // This is most common - Protocol = MediaProtocol.Http; - SupportsDirectPlay = true; - } - - public MediaSourceInfo ToMediaSource(Guid itemId) - { - var id = string.IsNullOrWhiteSpace(Path) ? - itemId.ToString("N") : - Path.GetMD5().ToString("N"); - - var source = new MediaSourceInfo - { - MediaStreams = GetMediaStreams(this).ToList(), - - Container = Container, - Protocol = Protocol, - Path = Path, - RequiredHttpHeaders = RequiredHttpHeaders, - RunTimeTicks = RunTimeTicks, - Name = id, - Id = id, - ReadAtNativeFramerate = ReadAtNativeFramerate, - SupportsDirectStream = Protocol == MediaProtocol.Http && !string.IsNullOrWhiteSpace(Container) && !string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase), - SupportsDirectPlay = SupportsDirectPlay, - IsRemote = true - }; - - source.InferTotalBitrate(); - - return source; - } - - private IEnumerable GetMediaStreams(ChannelMediaInfo info) - { - var list = new List(); - - if (!string.IsNullOrWhiteSpace(info.VideoCodec)) - { - list.Add(new MediaStream - { - Type = MediaStreamType.Video, - Width = info.Width, - RealFrameRate = info.Framerate, - Profile = info.VideoProfile, - Level = info.VideoLevel, - Index = -1, - Height = info.Height, - Codec = info.VideoCodec, - BitRate = info.VideoBitrate, - AverageFrameRate = info.Framerate - }); - } - - if (!string.IsNullOrWhiteSpace(info.AudioCodec)) - { - list.Add(new MediaStream - { - Type = MediaStreamType.Audio, - Index = -1, - Codec = info.AudioCodec, - BitRate = info.AudioBitrate, - Channels = info.AudioChannels, - SampleRate = info.AudioSampleRate - }); - } - - return list; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ChannelParentalRating.cs b/MediaBrowser.Controller/Channels/ChannelParentalRating.cs deleted file mode 100644 index d9cc521b38..0000000000 --- a/MediaBrowser.Controller/Channels/ChannelParentalRating.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MediaBrowser.Controller.Channels -{ - public enum ChannelParentalRating - { - GeneralAudience = 0, - - UsPG = 1, - - UsPG13 = 2, - - UsR = 3, - - Adult = 4 - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs b/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs deleted file mode 100644 index c2a51654c7..0000000000 --- a/MediaBrowser.Controller/Channels/ChannelSearchInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MediaBrowser.Controller.Channels -{ - public class ChannelSearchInfo - { - public string SearchTerm { get; set; } - - public string UserId { get; set; } - } - - public class ChannelLatestMediaSearch - { - public string UserId { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs deleted file mode 100644 index dc1d9b00a3..0000000000 --- a/MediaBrowser.Controller/Channels/IChannel.cs +++ /dev/null @@ -1,76 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Channels -{ - public interface IChannel - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the description. - /// - /// The description. - string Description { get; } - - /// - /// Gets the data version. - /// - /// The data version. - string DataVersion { get; } - - /// - /// Gets the home page URL. - /// - /// The home page URL. - string HomePageUrl { get; } - - /// - /// Gets the parental rating. - /// - /// The parental rating. - ChannelParentalRating ParentalRating { get; } - - /// - /// Gets the channel information. - /// - /// ChannelFeatures. - InternalChannelFeatures GetChannelFeatures(); - - /// - /// Determines whether [is enabled for] [the specified user]. - /// - /// The user identifier. - /// true if [is enabled for] [the specified user]; otherwise, false. - bool IsEnabledFor(string userId); - - /// - /// Gets the channel items. - /// - /// The query. - /// The cancellation token. - /// Task{IEnumerable{ChannelItem}}. - Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken); - - /// - /// Gets the channel image. - /// - /// The type. - /// The cancellation token. - /// Task{DynamicImageInfo}. - Task GetChannelImage(ImageType type, CancellationToken cancellationToken); - - /// - /// Gets the supported channel images. - /// - /// IEnumerable{ImageType}. - IEnumerable GetSupportedChannelImages(); - } -} diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs deleted file mode 100644 index 37fc892b36..0000000000 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Channels -{ - public interface IChannelManager - { - /// - /// Adds the parts. - /// - /// The channels. - void AddParts(IEnumerable channels); - - /// - /// Gets the channel features. - /// - /// The identifier. - /// ChannelFeatures. - ChannelFeatures GetChannelFeatures(string id); - - bool SupportsSync(string channelId); - - /// - /// Gets all channel features. - /// - /// IEnumerable{ChannelFeatures}. - ChannelFeatures[] GetAllChannelFeatures(); - - /// - /// Gets the channel. - /// - /// The identifier. - /// Channel. - Channel GetChannel(string id); - - /// - /// Gets the channels internal. - /// - /// The query. - /// The cancellation token. - /// Task<QueryResult<Channel>>. - Task> GetChannelsInternal(ChannelQuery query, CancellationToken cancellationToken); - - /// - /// Gets the channels. - /// - /// The query. - /// The cancellation token. - /// Task{QueryResult{BaseItemDto}}. - Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken); - - /// - /// Gets all media internal. - /// - /// The query. - /// The cancellation token. - /// Task<QueryResult<BaseItem>>. - Task> GetAllMediaInternal(AllChannelMediaQuery query, CancellationToken cancellationToken); - - /// - /// Gets all media. - /// - /// The query. - /// The cancellation token. - /// Task{QueryResult{BaseItemDto}}. - Task> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken); - - /// - /// Gets the latest media. - /// - /// The query. - /// The cancellation token. - /// Task{QueryResult{BaseItemDto}}. - Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken); - - /// - /// Gets the latest channel items internal. - /// - /// The query. - /// The cancellation token. - /// Task<QueryResult<BaseItem>>. - Task> GetLatestChannelItemsInternal(AllChannelMediaQuery query, CancellationToken cancellationToken); - - /// - /// Gets the channel items. - /// - /// The query. - /// The cancellation token. - /// Task{QueryResult{BaseItemDto}}. - Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken); - - /// - /// Gets the channel items internal. - /// - /// The query. - /// The progress. - /// The cancellation token. - /// Task<QueryResult<BaseItem>>. - Task> GetChannelItemsInternal(ChannelItemQuery query, IProgress progress, CancellationToken cancellationToken); - - /// - /// Gets the channel item media sources. - /// - /// The item. - /// The cancellation token. - /// Task{IEnumerable{MediaSourceInfo}}. - IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); - - /// - /// Gets the channel folder. - /// - /// The cancellation token. - /// BaseItemDto. - Folder GetInternalChannelFolder(CancellationToken cancellationToken); - - /// - /// Gets the channel folder. - /// - /// The user identifier. - /// The cancellation token. - BaseItemDto GetChannelFolder(string userId, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Channels/IHasCacheKey.cs b/MediaBrowser.Controller/Channels/IHasCacheKey.cs deleted file mode 100644 index 6376d2f914..0000000000 --- a/MediaBrowser.Controller/Channels/IHasCacheKey.cs +++ /dev/null @@ -1,13 +0,0 @@ - -namespace MediaBrowser.Controller.Channels -{ - public interface IHasCacheKey - { - /// - /// Gets the cache key. - /// - /// The user identifier. - /// System.String. - string GetCacheKey(string userId); - } -} diff --git a/MediaBrowser.Controller/Channels/IIndexableChannel.cs b/MediaBrowser.Controller/Channels/IIndexableChannel.cs deleted file mode 100644 index 0b52585e8f..0000000000 --- a/MediaBrowser.Controller/Channels/IIndexableChannel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Channels -{ - public interface IIndexableChannel - { - /// - /// Gets all media. - /// - /// The query. - /// The cancellation token. - /// Task{ChannelItemResult}. - Task GetAllMedia(InternalAllChannelMediaQuery query, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs b/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs deleted file mode 100644 index b4b6be9baf..0000000000 --- a/MediaBrowser.Controller/Channels/IRequiresMediaInfoCallback.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Channels -{ - public interface IRequiresMediaInfoCallback - { - /// - /// Gets the channel item media information. - /// - /// The identifier. - /// The cancellation token. - /// Task{IEnumerable{ChannelMediaInfo}}. - Task> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs deleted file mode 100644 index d637084942..0000000000 --- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Channels -{ - public interface ISearchableChannel - { - /// - /// Searches the specified search term. - /// - /// The search information. - /// The cancellation token. - /// Task{IEnumerable{ChannelItemInfo}}. - Task> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken); - } - - public interface ISupportsLatestMedia - { - /// - /// Gets the latest media. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{ChannelItemInfo}}. - Task> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/InternalAllChannelMediaQuery.cs b/MediaBrowser.Controller/Channels/InternalAllChannelMediaQuery.cs deleted file mode 100644 index ce091da7ec..0000000000 --- a/MediaBrowser.Controller/Channels/InternalAllChannelMediaQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Channels -{ - public class InternalAllChannelMediaQuery - { - public string UserId { get; set; } - - /// - /// Gets or sets the content types. - /// - /// The content types. - public ChannelMediaContentType[] ContentTypes { get; set; } - - /// - /// Gets or sets the extra types. - /// - /// The extra types. - public ExtraType[] ExtraTypes { get; set; } - public TrailerType[] TrailerTypes { get; set; } - - public InternalAllChannelMediaQuery() - { - ContentTypes = new ChannelMediaContentType[] { }; - ExtraTypes = new ExtraType[] { }; - TrailerTypes = new TrailerType[] { }; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs deleted file mode 100644 index 976808aad9..0000000000 --- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using MediaBrowser.Model.Channels; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Channels -{ - public class InternalChannelFeatures - { - /// - /// Gets or sets the media types. - /// - /// The media types. - public List MediaTypes { get; set; } - - /// - /// Gets or sets the content types. - /// - /// The content types. - public List ContentTypes { get; set; } - - /// - /// Represents the maximum number of records the channel allows retrieving at a time - /// - public int? MaxPageSize { get; set; } - - /// - /// Gets or sets the default sort orders. - /// - /// The default sort orders. - public List DefaultSortFields { get; set; } - - /// - /// Indicates if a sort ascending/descending toggle is supported or not. - /// - public bool SupportsSortOrderToggle { get; set; } - /// - /// Gets or sets the automatic refresh levels. - /// - /// The automatic refresh levels. - public int? AutoRefreshLevels { get; set; } - - /// - /// Gets or sets the daily download limit. - /// - /// The daily download limit. - public int? DailyDownloadLimit { get; set; } - /// - /// Gets or sets a value indicating whether [supports downloading]. - /// - /// true if [supports downloading]; otherwise, false. - public bool SupportsContentDownloading { get; set; } - - public InternalChannelFeatures() - { - MediaTypes = new List(); - ContentTypes = new List(); - - DefaultSortFields = new List(); - } - } -} diff --git a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs deleted file mode 100644 index 82ef6b9462..0000000000 --- a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediaBrowser.Model.Channels; - -namespace MediaBrowser.Controller.Channels -{ - public class InternalChannelItemQuery - { - public string FolderId { get; set; } - - public string UserId { get; set; } - - public int? StartIndex { get; set; } - - public int? Limit { get; set; } - - public ChannelItemSortField? SortBy { get; set; } - - public bool SortDescending { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs deleted file mode 100644 index d1c190ab54..0000000000 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Chapters -{ - /// - /// Interface IChapterManager - /// - public interface IChapterManager - { - /// - /// Gets the chapters. - /// - /// The item identifier. - /// List{ChapterInfo}. - List GetChapters(string itemId); - - /// - /// Saves the chapters. - /// - void SaveChapters(string itemId, List chapters); - } -} diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs deleted file mode 100644 index 7a387e319a..0000000000 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Collections -{ - public class CollectionCreationOptions : IHasProviderIds - { - public string Name { get; set; } - - public Guid? ParentId { get; set; } - - public bool IsLocked { get; set; } - - public Dictionary ProviderIds { get; set; } - - public string[] ItemIdList { get; set; } - public string[] UserIds { get; set; } - - public CollectionCreationOptions() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - ItemIdList = new string[] { }; - UserIds = new string[] { }; - } - } -} diff --git a/MediaBrowser.Controller/Collections/CollectionEvents.cs b/MediaBrowser.Controller/Collections/CollectionEvents.cs deleted file mode 100644 index 80f66a444a..0000000000 --- a/MediaBrowser.Controller/Collections/CollectionEvents.cs +++ /dev/null @@ -1,37 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Collections -{ - public class CollectionCreatedEventArgs : EventArgs - { - /// - /// Gets or sets the collection. - /// - /// The collection. - public BoxSet Collection { get; set; } - - /// - /// Gets or sets the options. - /// - /// The options. - public CollectionCreationOptions Options { get; set; } - } - - public class CollectionModifiedEventArgs : EventArgs - { - /// - /// Gets or sets the collection. - /// - /// The collection. - public BoxSet Collection { get; set; } - - /// - /// Gets or sets the items changed. - /// - /// The items changed. - public List ItemsChanged { get; set; } - } -} diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs deleted file mode 100644 index d89843cc0d..0000000000 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Collections -{ - public interface ICollectionManager - { - /// - /// Occurs when [collection created]. - /// - event EventHandler CollectionCreated; - - /// - /// Occurs when [items added to collection]. - /// - event EventHandler ItemsAddedToCollection; - - /// - /// Occurs when [items removed from collection]. - /// - event EventHandler ItemsRemovedFromCollection; - - /// - /// Creates the collection. - /// - /// The options. - /// Task. - Task CreateCollection(CollectionCreationOptions options); - - /// - /// Adds to collection. - /// - /// The collection identifier. - /// The item ids. - /// Task. - Task AddToCollection(Guid collectionId, IEnumerable itemIds); - - /// - /// Removes from collection. - /// - /// The collection identifier. - /// The item ids. - /// Task. - Task RemoveFromCollection(Guid collectionId, IEnumerable itemIds); - - Task AddToCollection(Guid collectionId, IEnumerable itemIds); - Task RemoveFromCollection(Guid collectionId, IEnumerable itemIds); - - /// - /// Collapses the items within box sets. - /// - /// The items. - /// The user. - /// IEnumerable{BaseItem}. - IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user); - - /// - /// Gets the collections folder. - /// - /// The user identifier. - /// Folder. - Folder GetCollectionsFolder(string userId); - - /// - /// Gets the collections. - /// - /// The user. - /// IEnumerable<BoxSet>. - IEnumerable GetCollections(User user); - } -} diff --git a/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs deleted file mode 100644 index 38d2611f0f..0000000000 --- a/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Controller.Collections -{ - public class ManualCollectionsFolder : BasePluginFolder, IHiddenFromDisplay - { - public ManualCollectionsFolder() - { - Name = "Collections"; - } - - public override bool IsHidden - { - get - { - return true; - } - } - - [IgnoreDataMember] - public override bool SupportsInheritedParentImages - { - get - { - return false; - } - } - - public bool IsHiddenFromUser(User user) - { - return !ConfigurationManager.Configuration.DisplayCollectionsView; - } - - [IgnoreDataMember] - public override string CollectionType - { - get { return Model.Entities.CollectionType.BoxSets; } - } - - public override string GetClientTypeName() - { - return typeof(CollectionFolder).Name; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs deleted file mode 100644 index af57149322..0000000000 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; - -namespace MediaBrowser.Controller.Configuration -{ - /// - /// Interface IServerConfigurationManager - /// - public interface IServerConfigurationManager : IConfigurationManager - { - /// - /// Gets the application paths. - /// - /// The application paths. - IServerApplicationPaths ApplicationPaths { get; } - - /// - /// Gets the configuration. - /// - /// The configuration. - ServerConfiguration Configuration { get; } - - bool SetOptimalValues(); - } -} diff --git a/MediaBrowser.Controller/Connect/ConnectSupporterSummary.cs b/MediaBrowser.Controller/Connect/ConnectSupporterSummary.cs deleted file mode 100644 index 20eef0521b..0000000000 --- a/MediaBrowser.Controller/Connect/ConnectSupporterSummary.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediaBrowser.Model.Connect; -using System.Collections.Generic; -using MediaBrowser.Model.Dto; - -namespace MediaBrowser.Controller.Connect -{ - public class ConnectSupporterSummary - { - public int MaxUsers { get; set; } - public List Users { get; set; } - public List EligibleUsers { get; set; } - - public ConnectSupporterSummary() - { - Users = new List(); - EligibleUsers = new List(); - } - } -} diff --git a/MediaBrowser.Controller/Connect/IConnectManager.cs b/MediaBrowser.Controller/Connect/IConnectManager.cs deleted file mode 100644 index 70bdc52e63..0000000000 --- a/MediaBrowser.Controller/Connect/IConnectManager.cs +++ /dev/null @@ -1,77 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Connect; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Connect -{ - public interface IConnectManager - { - /// - /// Gets the wan API address. - /// - /// The wan API address. - string WanApiAddress { get; } - - /// - /// Links the user. - /// - /// The user identifier. - /// The connect username. - /// Task. - Task LinkUser(string userId, string connectUsername); - - /// - /// Removes the link. - /// - /// The user identifier. - /// Task. - Task RemoveConnect(string userId); - - /// - /// Invites the user. - /// - /// The request. - /// Task<UserLinkResult>. - Task InviteUser(ConnectAuthorizationRequest request); - - /// - /// Gets the pending guests. - /// - /// Task<List<ConnectAuthorization>>. - Task> GetPendingGuests(); - - /// - /// Gets the user from exchange token. - /// - /// The token. - /// User. - User GetUserFromExchangeToken(string token); - - /// - /// Cancels the authorization. - /// - /// The identifier. - /// Task. - Task CancelAuthorization(string id); - - /// - /// Authenticates the specified username. - /// - Task Authenticate(string username, string password, string passwordMd5); - - /// - /// Gets the local user. - /// - /// The connect user identifier. - /// Task<User>. - Task GetLocalUser(string connectUserId); - - /// - /// Determines whether [is authorization token valid] [the specified token]. - /// - /// The token. - /// true if [is authorization token valid] [the specified token]; otherwise, false. - bool IsAuthorizationTokenValid(string token); - } -} diff --git a/MediaBrowser.Controller/Connect/UserLinkResult.cs b/MediaBrowser.Controller/Connect/UserLinkResult.cs deleted file mode 100644 index 16ebfc70a3..0000000000 --- a/MediaBrowser.Controller/Connect/UserLinkResult.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace MediaBrowser.Controller.Connect -{ - public class UserLinkResult - { - public bool IsPending { get; set; } - public bool IsNewUserInvitation { get; set; } - public string GuestDisplayName { get; set; } - } -} diff --git a/MediaBrowser.Controller/Devices/CameraImageUploadInfo.cs b/MediaBrowser.Controller/Devices/CameraImageUploadInfo.cs deleted file mode 100644 index b3f3bb9025..0000000000 --- a/MediaBrowser.Controller/Devices/CameraImageUploadInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediaBrowser.Model.Devices; - -namespace MediaBrowser.Controller.Devices -{ - public class CameraImageUploadInfo - { - public LocalFileInfo FileInfo { get; set; } - public DeviceInfo Device { get; set; } - } -} diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs deleted file mode 100644 index 676db09aa7..0000000000 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ /dev/null @@ -1,96 +0,0 @@ -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Session; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Devices -{ - public interface IDeviceManager - { - /// - /// Occurs when [device options updated]. - /// - event EventHandler> DeviceOptionsUpdated; - /// - /// Occurs when [camera image uploaded]. - /// - event EventHandler> CameraImageUploaded; - - /// - /// Registers the device. - /// - /// The reported identifier. - /// The name. - /// Name of the application. - /// The application version. - /// The used by user identifier. - /// Task. - DeviceInfo RegisterDevice(string reportedId, string name, string appName, string appVersion, string usedByUserId); - - /// - /// Saves the capabilities. - /// - /// The reported identifier. - /// The capabilities. - /// Task. - void SaveCapabilities(string reportedId, ClientCapabilities capabilities); - - /// - /// Gets the capabilities. - /// - /// The reported identifier. - /// ClientCapabilities. - ClientCapabilities GetCapabilities(string reportedId); - - /// - /// Gets the device information. - /// - /// The identifier. - /// DeviceInfo. - DeviceInfo GetDevice(string id); - - /// - /// Updates the device information. - /// - /// The identifier. - /// The options. - /// Task. - void UpdateDeviceInfo(string id, DeviceOptions options); - - /// - /// Gets the devices. - /// - /// The query. - /// IEnumerable<DeviceInfo>. - QueryResult GetDevices(DeviceQuery query); - - void DeleteDevice(string id); - - /// - /// Gets the upload history. - /// - /// The device identifier. - /// ContentUploadHistory. - ContentUploadHistory GetCameraUploadHistory(string deviceId); - - /// - /// Accepts the upload. - /// - /// The device identifier. - /// The stream. - /// The file. - /// Task. - Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file); - - /// - /// Determines whether this instance [can access device] the specified user identifier. - /// - /// The user identifier. - /// The device identifier. - /// true if this instance [can access device] the specified user identifier; otherwise, false. - bool CanAccessDevice(string userId, string deviceId); - } -} diff --git a/MediaBrowser.Controller/Devices/IDeviceRepository.cs b/MediaBrowser.Controller/Devices/IDeviceRepository.cs deleted file mode 100644 index b9ebbb6c71..0000000000 --- a/MediaBrowser.Controller/Devices/IDeviceRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Session; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Devices -{ - public interface IDeviceRepository - { - /// - /// Registers the device. - /// - /// The device. - /// Task. - void SaveDevice(DeviceInfo device); - - /// - /// Saves the capabilities. - /// - /// The identifier. - /// The capabilities. - /// Task. - void SaveCapabilities(string id, ClientCapabilities capabilities); - - /// - /// Gets the capabilities. - /// - /// The identifier. - /// ClientCapabilities. - ClientCapabilities GetCapabilities(string id); - - /// - /// Gets the device information. - /// - /// The identifier. - /// DeviceInfo. - DeviceInfo GetDevice(string id); - - List GetDevices(); - - /// - /// Deletes the device. - /// - /// The identifier. - /// Task. - void DeleteDevice(string id); - - /// - /// Gets the upload history. - /// - /// The device identifier. - /// ContentUploadHistory. - ContentUploadHistory GetCameraUploadHistory(string deviceId); - - /// - /// Saves the camera upload history. - /// - /// The device identifier. - /// The file. - void AddCameraUpload(string deviceId, LocalFileInfo file); - } -} diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs deleted file mode 100644 index 2f64cd1946..0000000000 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ /dev/null @@ -1,76 +0,0 @@ -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Dlna; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Dlna -{ - public interface IDlnaManager - { - /// - /// Gets the profile infos. - /// - /// IEnumerable{DeviceProfileInfo}. - IEnumerable GetProfileInfos(); - - /// - /// Gets the profile. - /// - /// The headers. - /// DeviceProfile. - DeviceProfile GetProfile(IDictionary headers); - - /// - /// Gets the default profile. - /// - /// DeviceProfile. - DeviceProfile GetDefaultProfile(); - - /// - /// Creates the profile. - /// - /// The profile. - void CreateProfile(DeviceProfile profile); - - /// - /// Updates the profile. - /// - /// The profile. - void UpdateProfile(DeviceProfile profile); - - /// - /// Deletes the profile. - /// - /// The identifier. - void DeleteProfile(string id); - - /// - /// Gets the profile. - /// - /// The identifier. - /// DeviceProfile. - DeviceProfile GetProfile(string id); - - /// - /// Gets the profile. - /// - /// The device information. - /// DeviceProfile. - DeviceProfile GetProfile(DeviceIdentification deviceInfo); - - /// - /// Gets the server description XML. - /// - /// The headers. - /// The server uu identifier. - /// The server address. - /// System.String. - string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress); - - /// - /// Gets the icon. - /// - /// The filename. - /// DlnaIconResponse. - ImageStream GetIcon(string filename); - } -} diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs deleted file mode 100644 index 757448eb26..0000000000 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using MediaBrowser.Model.Drawing; - -namespace MediaBrowser.Controller.Drawing -{ - public interface IImageEncoder - { - /// - /// Gets the supported input formats. - /// - /// The supported input formats. - string[] SupportedInputFormats { get; } - /// - /// Gets the supported output formats. - /// - /// The supported output formats. - ImageFormat[] SupportedOutputFormats { get; } - - /// - /// Encodes the image. - /// - string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); - - /// - /// Creates the image collage. - /// - /// The options. - void CreateImageCollage(ImageCollageOptions options); - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets a value indicating whether [supports image collage creation]. - /// - /// true if [supports image collage creation]; otherwise, false. - bool SupportsImageCollageCreation { get; } - - /// - /// Gets a value indicating whether [supports image encoding]. - /// - /// true if [supports image encoding]; otherwise, false. - bool SupportsImageEncoding { get; } - - ImageSize GetImageSize(string path); - } -} diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs deleted file mode 100644 index 0bc92ac7e7..0000000000 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Drawing -{ - /// - /// Interface IImageProcessor - /// - public interface IImageProcessor - { - /// - /// Gets the supported input formats. - /// - /// The supported input formats. - string[] SupportedInputFormats { get; } - - /// - /// Gets the image enhancers. - /// - /// The image enhancers. - IImageEnhancer[] ImageEnhancers { get; } - - ImageSize GetImageSize(string path); - - /// - /// Gets the size of the image. - /// - /// The information. - /// ImageSize. - ImageSize GetImageSize(BaseItem item, ItemImageInfo info); - - ImageSize GetImageSize(BaseItem item, ItemImageInfo info, bool allowSlowMethods, bool updateItem); - - /// - /// Adds the parts. - /// - /// The enhancers. - void AddParts(IEnumerable enhancers); - - /// - /// Gets the supported enhancers. - /// - /// The item. - /// Type of the image. - /// IEnumerable{IImageEnhancer}. - List GetSupportedEnhancers(IHasMetadata item, ImageType imageType); - - /// - /// Gets the image cache tag. - /// - /// The item. - /// The image. - /// Guid. - string GetImageCacheTag(IHasMetadata item, ItemImageInfo image); - - /// - /// Gets the image cache tag. - /// - /// The item. - /// The image. - /// The image enhancers. - /// Guid. - string GetImageCacheTag(IHasMetadata item, ItemImageInfo image, List imageEnhancers); - - /// - /// Processes the image. - /// - /// The options. - /// To stream. - /// Task. - Task ProcessImage(ImageProcessingOptions options, Stream toStream); - - /// - /// Processes the image. - /// - /// The options. - /// Task. - Task> ProcessImage(ImageProcessingOptions options); - - /// - /// Gets the enhanced image. - /// - /// The item. - /// Type of the image. - /// Index of the image. - /// Task{System.String}. - Task GetEnhancedImage(IHasMetadata item, ImageType imageType, int imageIndex); - - /// - /// Gets the supported image output formats. - /// - /// ImageOutputFormat[]. - ImageFormat[] GetSupportedImageOutputFormats(); - - /// - /// Creates the image collage. - /// - /// The options. - void CreateImageCollage(ImageCollageOptions options); - - /// - /// Gets a value indicating whether [supports image collage creation]. - /// - /// true if [supports image collage creation]; otherwise, false. - bool SupportsImageCollageCreation { get; } - - IImageEncoder ImageEncoder { get; set; } - - bool SupportsTransparency(string path); - } -} diff --git a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs deleted file mode 100644 index 92a7f5ac92..0000000000 --- a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ - -namespace MediaBrowser.Controller.Drawing -{ - public class ImageCollageOptions - { - /// - /// Gets or sets the input paths. - /// - /// The input paths. - public string[] InputPaths { get; set; } - /// - /// Gets or sets the output path. - /// - /// The output path. - public string OutputPath { get; set; } - /// - /// Gets or sets the width. - /// - /// The width. - public int Width { get; set; } - /// - /// Gets or sets the height. - /// - /// The height. - public int Height { get; set; } - } -} diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs deleted file mode 100644 index 9936b10362..0000000000 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Drawing -{ - public static class ImageHelper - { - public static ImageSize GetNewImageSize(ImageProcessingOptions options, ImageSize? originalImageSize) - { - if (originalImageSize.HasValue) - { - // Determine the output size based on incoming parameters - var newSize = DrawingUtils.Resize(originalImageSize.Value, options.Width, options.Height, options.MaxWidth, options.MaxHeight); - - return newSize; - } - return GetSizeEstimate(options); - } - - public static IImageProcessor ImageProcessor { get; set; } - - private static ImageSize GetSizeEstimate(ImageProcessingOptions options) - { - if (options.Width.HasValue && options.Height.HasValue) - { - return new ImageSize(options.Width.Value, options.Height.Value); - } - - var aspect = GetEstimatedAspectRatio(options.Image.Type, options.Item); - - var width = options.Width ?? options.MaxWidth; - - if (width.HasValue) - { - var heightValue = width.Value / aspect; - return new ImageSize(width.Value, heightValue); - } - - var height = options.Height ?? options.MaxHeight ?? 200; - var widthValue = aspect * height; - return new ImageSize(widthValue, height); - } - - private static double GetEstimatedAspectRatio(ImageType type, IHasMetadata item) - { - switch (type) - { - case ImageType.Art: - case ImageType.Backdrop: - case ImageType.Chapter: - case ImageType.Screenshot: - case ImageType.Thumb: - return 1.78; - case ImageType.Banner: - return 5.4; - case ImageType.Box: - case ImageType.BoxRear: - case ImageType.Disc: - case ImageType.Menu: - return 1; - case ImageType.Logo: - return 2.58; - case ImageType.Primary: - return item.GetDefaultPrimaryImageAspectRatio() ?? .667; - default: - return 1; - } - } - } -} diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs deleted file mode 100644 index 26283b5eae..0000000000 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ /dev/null @@ -1,108 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MediaBrowser.Controller.Drawing -{ - public class ImageProcessingOptions - { - public ImageProcessingOptions() - { - RequiresAutoOrientation = true; - } - - public string ItemId { get; set; } - public string ItemType { get; set; } - public IHasMetadata Item { get; set; } - - public ItemImageInfo Image { get; set; } - - public int ImageIndex { get; set; } - - public bool CropWhiteSpace { get; set; } - - public int? Width { get; set; } - - public int? Height { get; set; } - - public int? MaxWidth { get; set; } - - public int? MaxHeight { get; set; } - - public int Quality { get; set; } - - public List Enhancers { get; set; } - - public ImageFormat[] SupportedOutputFormats { get; set; } - - public bool AddPlayedIndicator { get; set; } - - public int? UnplayedCount { get; set; } - public int? Blur { get; set; } - - public double PercentPlayed { get; set; } - - public string BackgroundColor { get; set; } - public string ForegroundLayer { get; set; } - public bool RequiresAutoOrientation { get; set; } - - public bool HasDefaultOptions(string originalImagePath) - { - return HasDefaultOptionsWithoutSize(originalImagePath) && - !Width.HasValue && - !Height.HasValue && - !MaxWidth.HasValue && - !MaxHeight.HasValue; - } - - public bool HasDefaultOptions(string originalImagePath, ImageSize size) - { - if (!HasDefaultOptionsWithoutSize(originalImagePath)) - { - return false; - } - - if (Width.HasValue && !size.Width.Equals(Width.Value)) - { - return false; - } - if (Height.HasValue && !size.Height.Equals(Height.Value)) - { - return false; - } - if (MaxWidth.HasValue && size.Width > MaxWidth.Value) - { - return false; - } - if (MaxHeight.HasValue && size.Height > MaxHeight.Value) - { - return false; - } - - return true; - } - - public bool HasDefaultOptionsWithoutSize(string originalImagePath) - { - return (Quality >= 90) && - IsFormatSupported(originalImagePath) && - !AddPlayedIndicator && - PercentPlayed.Equals(0) && - !UnplayedCount.HasValue && - !Blur.HasValue && - !CropWhiteSpace && - string.IsNullOrEmpty(BackgroundColor) && - string.IsNullOrEmpty(ForegroundLayer); - } - - private bool IsFormatSupported(string originalImagePath) - { - var ext = Path.GetExtension(originalImagePath); - return SupportedOutputFormats.Any(outputFormat => string.Equals(ext, "." + outputFormat, StringComparison.OrdinalIgnoreCase)); - } - } -} diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs deleted file mode 100644 index 5dfa94e1ea..0000000000 --- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Drawing -{ - public static class ImageProcessorExtensions - { - public static string GetImageCacheTag(this IImageProcessor processor, IHasMetadata item, ImageType imageType) - { - return processor.GetImageCacheTag(item, imageType, 0); - } - - public static string GetImageCacheTag(this IImageProcessor processor, IHasMetadata item, ImageType imageType, int imageIndex) - { - var imageInfo = item.GetImageInfo(imageType, imageIndex); - - if (imageInfo == null) - { - return null; - } - - return processor.GetImageCacheTag(item, imageInfo); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs deleted file mode 100644 index b5e14eb6c8..0000000000 --- a/MediaBrowser.Controller/Drawing/ImageStream.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MediaBrowser.Model.Drawing; -using System; -using System.IO; - -namespace MediaBrowser.Controller.Drawing -{ - public class ImageStream : IDisposable - { - /// - /// Gets or sets the stream. - /// - /// The stream. - public Stream Stream { get; set; } - /// - /// Gets or sets the format. - /// - /// The format. - public ImageFormat Format { get; set; } - - public void Dispose() - { - if (Stream != null) - { - Stream.Dispose(); - } - GC.SuppressFinalize(this); - } - } -} diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs deleted file mode 100644 index f05ae4e713..0000000000 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ /dev/null @@ -1,69 +0,0 @@ -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Controller.Dto -{ - public class DtoOptions - { - private static readonly List DefaultExcludedFields = new List - { - ItemFields.SeasonUserData, - ItemFields.RefreshState - }; - - public ItemFields[] Fields { get; set; } - public ImageType[] ImageTypes { get; set; } - public int ImageTypeLimit { get; set; } - public bool EnableImages { get; set; } - public bool AddProgramRecordingInfo { get; set; } - public string DeviceId { get; set; } - public bool EnableUserData { get; set; } - public bool AddCurrentProgram { get; set; } - - public DtoOptions() - : this(true) - { - } - - private static readonly ImageType[] AllImageTypes = Enum.GetNames(typeof(ImageType)) - .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true)) - .ToArray(); - - private static readonly ItemFields[] AllItemFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .Except(DefaultExcludedFields) - .ToArray(); - - public DtoOptions(bool allFields) - { - ImageTypeLimit = int.MaxValue; - EnableImages = true; - EnableUserData = true; - AddCurrentProgram = true; - - if (allFields) - { - Fields = AllItemFields; - } - else - { - Fields = new ItemFields[] { }; - } - - ImageTypes = AllImageTypes; - } - - public int GetImageLimit(ImageType type) - { - if (EnableImages && ImageTypes.Contains(type)) - { - return ImageTypeLimit; - } - - return 0; - } - } -} diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs deleted file mode 100644 index 5ba6e036e4..0000000000 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ /dev/null @@ -1,86 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using System.Collections.Generic; -using MediaBrowser.Controller.Sync; - -namespace MediaBrowser.Controller.Dto -{ - /// - /// Interface IDtoService - /// - public interface IDtoService - { - /// - /// Gets the dto id. - /// - /// The item. - /// System.String. - string GetDtoId(BaseItem item); - - /// - /// Attaches the primary image aspect ratio. - /// - /// The dto. - /// The item. - void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item); - - /// - /// Gets the primary image aspect ratio. - /// - /// The item. - /// System.Nullable<System.Double>. - double? GetPrimaryImageAspectRatio(BaseItem item); - - /// - /// Gets the base item dto. - /// - /// The item. - /// The fields. - /// The user. - /// The owner. - BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null); - - /// - /// Gets the base item dto. - /// - /// The item. - /// The options. - /// The user. - /// The owner. - /// BaseItemDto. - BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null); - - /// - /// Gets the base item dtos. - /// - /// The items. - /// The options. - /// The user. - /// The owner. - BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null); - - BaseItemDto[] GetBaseItemDtos(List items, DtoOptions options, User user = null, BaseItem owner = null); - - /// - /// Gets the chapter information dto. - /// - /// The item. - /// ChapterInfoDto. - List GetChapterInfoDtos(BaseItem item); - - /// - /// Gets the user item data dto. - /// - /// The data. - /// UserItemDataDto. - UserItemDataDto GetUserItemDataDto(UserItemData data); - - /// - /// Gets the item by name dto. - /// - BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, Dictionary syncProgress, User user = null); - - Dictionary GetSyncedItemProgress(DtoOptions options); - } -} diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs deleted file mode 100644 index 00fac1eabd..0000000000 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ /dev/null @@ -1,225 +0,0 @@ -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Controller.Entities -{ - /// - /// Specialized folder that can have items added to it's children by external entities. - /// Used for our RootFolder so plug-ins can add items. - /// - public class AggregateFolder : Folder - { - public AggregateFolder() - { - PhysicalLocationsList = EmptyStringArray; - } - - [IgnoreDataMember] - public override bool IsPhysicalRoot - { - get { return true; } - } - - public override bool CanDelete() - { - return false; - } - - [IgnoreDataMember] - public override bool SupportsPlayedStatus - { - get - { - return false; - } - } - - /// - /// The _virtual children - /// - private readonly ConcurrentBag _virtualChildren = new ConcurrentBag(); - - /// - /// Gets the virtual children. - /// - /// The virtual children. - public ConcurrentBag VirtualChildren - { - get { return _virtualChildren; } - } - - [IgnoreDataMember] - public override string[] PhysicalLocations - { - get - { - return PhysicalLocationsList; - } - } - - public string[] PhysicalLocationsList { get; set; } - - protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) - { - return CreateResolveArgs(directoryService, true).FileSystemChildren; - } - - private Guid[] _childrenIds = null; - private readonly object _childIdsLock = new object(); - protected override List LoadChildren() - { - lock (_childIdsLock) - { - if (_childrenIds == null || _childrenIds.Length == 0) - { - var list = base.LoadChildren(); - _childrenIds = list.Select(i => i.Id).ToArray(); - return list; - } - - return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); - } - } - - private void ClearCache() - { - lock (_childIdsLock) - { - _childrenIds = null; - } - } - - private bool _requiresRefresh; - public override bool RequiresRefresh() - { - var changed = base.RequiresRefresh() || _requiresRefresh; - - if (!changed) - { - var locations = PhysicalLocations; - - var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations; - - if (!locations.SequenceEqual(newLocations)) - { - changed = true; - } - } - - return changed; - } - - public override bool BeforeMetadataRefresh() - { - ClearCache(); - - var changed = base.BeforeMetadataRefresh() || _requiresRefresh; - _requiresRefresh = false; - return changed; - } - - private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) - { - ClearCache(); - - var path = ContainingFolderPath; - - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) - { - FileInfo = FileSystem.GetDirectoryInfo(path), - Path = path, - Parent = GetParent() as Folder - }; - - // Gather child folder and files - if (args.IsDirectory) - { - var isPhysicalRoot = args.IsPhysicalRoot; - - // When resolving the root, we need it's grandchildren (children of user views) - var flattenFolderDepth = isPhysicalRoot ? 2 : 0; - - var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); - - // Need to remove subpaths that may have been resolved from shortcuts - // Example: if \\server\movies exists, then strip out \\server\movies\action - if (isPhysicalRoot) - { - files = LibraryManager.NormalizeRootPathList(files).ToArray(); - } - - args.FileSystemChildren = files; - } - - _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations); - if (setPhysicalLocations) - { - PhysicalLocationsList = args.PhysicalLocations; - } - - return args; - } - - protected override IEnumerable GetNonCachedChildren(IDirectoryService directoryService) - { - return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); - } - - protected override async Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) - { - ClearCache(); - - await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) - .ConfigureAwait(false); - - ClearCache(); - } - - /// - /// Adds the virtual child. - /// - /// The child. - /// - public void AddVirtualChild(BaseItem child) - { - if (child == null) - { - throw new ArgumentNullException(); - } - - _virtualChildren.Add(child); - } - - /// - /// Finds the virtual child. - /// - /// The id. - /// BaseItem. - /// id - public BaseItem FindVirtualChild(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - foreach (var child in _virtualChildren) - { - if (child.Id == id) - { - return child; - } - } - return null; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs deleted file mode 100644 index 16fd75d2e9..0000000000 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ /dev/null @@ -1,296 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Controller.Entities.Audio -{ - /// - /// Class Audio - /// - public class Audio : BaseItem, - IHasAlbumArtist, - IHasArtist, - IHasMusicGenres, - IHasLookupInfo, - IHasMediaSources - { - /// - /// Gets or sets the artist. - /// - /// The artist. - [IgnoreDataMember] - public string[] Artists { get; set; } - - [IgnoreDataMember] - public string[] AlbumArtists { get; set; } - - [IgnoreDataMember] - public override bool EnableRefreshOnDateModifiedChange - { - get { return true; } - } - - public Audio() - { - Artists = EmptyStringArray; - AlbumArtists = EmptyStringArray; - } - - public override double? GetDefaultPrimaryImageAspectRatio() - { - return 1; - } - - [IgnoreDataMember] - public override bool SupportsPlayedStatus - { - get - { - return true; - } - } - - [IgnoreDataMember] - public override bool SupportsPeople - { - get { return false; } - } - - [IgnoreDataMember] - public override bool SupportsAddingToPlaylist - { - get { return true; } - } - - [IgnoreDataMember] - public override bool SupportsInheritedParentImages - { - get { return true; } - } - - [IgnoreDataMember] - protected override bool SupportsOwnedItems - { - get - { - return false; - } - } - - [IgnoreDataMember] - public override Folder LatestItemsIndexContainer - { - get - { - return AlbumEntity; - } - } - - public override bool CanDownload() - { - var locationType = LocationType; - return locationType != LocationType.Remote && - locationType != LocationType.Virtual; - } - - [IgnoreDataMember] - public string[] AllArtists - { - get - { - var list = new string[AlbumArtists.Length + Artists.Length]; - - var index = 0; - foreach (var artist in AlbumArtists) - { - list[index] = artist; - index++; - } - foreach (var artist in Artists) - { - list[index] = artist; - index++; - } - - return list; - - } - } - - [IgnoreDataMember] - public MusicAlbum AlbumEntity - { - get { return FindParent(); } - } - - /// - /// Gets the type of the media. - /// - /// The type of the media. - [IgnoreDataMember] - public override string MediaType - { - get - { - return Model.Entities.MediaType.Audio; - } - } - - /// - /// Creates the name of the sort. - /// - /// System.String. - protected override string CreateSortName() - { - return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "") - + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name; - } - - public override List GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty; - - - if (ParentIndexNumber.HasValue) - { - songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey; - } - songKey += Name; - - if (!string.IsNullOrWhiteSpace(Album)) - { - songKey = Album + "-" + songKey; - } - - var albumArtist = AlbumArtists.Length == 0 ? null : AlbumArtists[0]; - if (!string.IsNullOrWhiteSpace(albumArtist)) - { - songKey = albumArtist + "-" + songKey; - } - - list.Insert(0, songKey); - - return list; - } - - public override UnratedItem GetBlockUnratedType() - { - if (SourceType == SourceType.Library) - { - return UnratedItem.Music; - } - return base.GetBlockUnratedType(); - } - - public List GetMediaStreams() - { - return MediaSourceManager.GetMediaStreams(new MediaStreamQuery - { - ItemId = Id - }); - } - - public List GetMediaStreams(MediaStreamType type) - { - return MediaSourceManager.GetMediaStreams(new MediaStreamQuery - { - ItemId = Id, - Type = type - }); - } - - public SongInfo GetLookupInfo() - { - var info = GetItemLookupInfo(); - - info.AlbumArtists = AlbumArtists; - info.Album = Album; - info.Artists = Artists; - - return info; - } - - public virtual List GetMediaSources(bool enablePathSubstitution) - { - if (SourceType == SourceType.Channel) - { - var sources = ChannelManager.GetStaticMediaSources(this, CancellationToken.None) - .ToList(); - - if (sources.Count > 0) - { - return sources; - } - - var list = new List - { - GetVersionInfo(this, enablePathSubstitution) - }; - - foreach (var mediaSource in list) - { - if (string.IsNullOrWhiteSpace(mediaSource.Path)) - { - mediaSource.Type = MediaSourceType.Placeholder; - } - } - - return list; - } - - var result = new List - { - GetVersionInfo(this, enablePathSubstitution) - }; - - return result; - } - - private static MediaSourceInfo GetVersionInfo(Audio i, bool enablePathSubstituion) - { - var locationType = i.LocationType; - - var info = new MediaSourceInfo - { - Id = i.Id.ToString("N"), - Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File, - MediaStreams = MediaSourceManager.GetMediaStreams(i.Id), - Name = i.Name, - Path = enablePathSubstituion ? GetMappedPath(i, i.Path, locationType) : i.Path, - RunTimeTicks = i.RunTimeTicks, - Container = i.Container, - Size = i.Size - }; - - if (info.Protocol == MediaProtocol.File) - { - info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"); - } - - if (string.IsNullOrEmpty(info.Container)) - { - if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual) - { - info.Container = System.IO.Path.GetExtension(i.Path).TrimStart('.'); - } - } - - info.Bitrate = i.TotalBitrate; - info.InferTotalBitrate(); - - return info; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs b/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs deleted file mode 100644 index 1b717b900a..0000000000 --- a/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Controller.Entities.Audio -{ - public class AudioPodcast : Audio, IHasLookupInfo - { - [IgnoreDataMember] - public override bool SupportsPositionTicksResume - { - get - { - return true; - } - } - - [IgnoreDataMember] - public override bool SupportsPlayedStatus - { - get - { - return true; - } - } - - public override double? GetDefaultPrimaryImageAspectRatio() - { - return 1; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs deleted file mode 100644 index b2dedada47..0000000000 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ /dev/null @@ -1,15 +0,0 @@ - -namespace MediaBrowser.Controller.Entities.Audio -{ - public interface IHasAlbumArtist - { - string[] AlbumArtists { get; set; } - } - - public interface IHasArtist - { - string[] AllArtists { get; } - - string[] Artists { get; set; } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs deleted file mode 100644 index fdf939e359..0000000000 --- a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Entities.Audio -{ - public interface IHasMusicGenres - { - List Genres { get; } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs deleted file mode 100644 index acda9ae02b..0000000000 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Users; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.Serialization; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; - -namespace MediaBrowser.Controller.Entities.Audio -{ - /// - /// Class MusicAlbum - /// - public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer - { - public string[] AlbumArtists { get; set; } - public string[] Artists { get; set; } - - public MusicAlbum() - { - Artists = EmptyStringArray; - AlbumArtists = EmptyStringArray; - } - - [IgnoreDataMember] - public override bool SupportsAddingToPlaylist - { - get { return true; } - } - - [IgnoreDataMember] - public override bool SupportsInheritedParentImages - { - get { return true; } - } - - [IgnoreDataMember] - public MusicArtist MusicArtist - { - get { return GetMusicArtist(new DtoOptions(true)); } - } - - public MusicArtist GetMusicArtist(DtoOptions options) - { - var parents = GetParents(); - foreach (var parent in parents) - { - var artist = parent as MusicArtist; - if (artist != null) - { - return artist; - } - } - - var name = AlbumArtist; - if (!string.IsNullOrWhiteSpace(name)) - { - return LibraryManager.GetArtist(name, options); - } - return null; - } - - [IgnoreDataMember] - public override bool SupportsPlayedStatus - { - get - { - return false; - } - } - - [IgnoreDataMember] - public override bool SupportsCumulativeRunTimeTicks - { - get - { - return true; - } - } - - [IgnoreDataMember] - public string[] AllArtists - { - get - { - var list = new string[AlbumArtists.Length + Artists.Length]; - - var index = 0; - foreach (var artist in AlbumArtists) - { - list[index] = artist; - index++; - } - foreach (var artist in Artists) - { - list[index] = artist; - index++; - } - - return list; - } - } - - [IgnoreDataMember] - public string AlbumArtist - { - get { return AlbumArtists.Length == 0 ? null : AlbumArtists[0]; } - } - - [IgnoreDataMember] - public override bool SupportsPeople - { - get { return false; } - } - - /// - /// Gets the tracks. - /// - /// The tracks. - [IgnoreDataMember] - public IEnumerable Tracks - { - get - { - return GetRecursiveChildren(i => i is Audio); - } - } - - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) - { - return Tracks; - } - - public override double? GetDefaultPrimaryImageAspectRatio() - { - return 1; - } - - public override List GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - var albumArtist = AlbumArtist; - if (!string.IsNullOrWhiteSpace(albumArtist)) - { - list.Insert(0, albumArtist + "-" + Name); - } - - var id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum); - - if (!string.IsNullOrWhiteSpace(id)) - { - list.Insert(0, "MusicAlbum-Musicbrainz-" + id); - } - - id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); - - if (!string.IsNullOrWhiteSpace(id)) - { - list.Insert(0, "MusicAlbum-MusicBrainzReleaseGroup-" + id); - } - - return list; - } - - protected override bool GetBlockUnratedValue(UserPolicy config) - { - return config.BlockUnratedItems.Contains(UnratedItem.Music); - } - - public override UnratedItem GetBlockUnratedType() - { - return UnratedItem.Music; - } - - public AlbumInfo GetLookupInfo() - { - var id = GetItemLookupInfo(); - - id.AlbumArtists = AlbumArtists; - - var artist = GetMusicArtist(new DtoOptions(false)); - - if (artist != null) - { - id.ArtistProviderIds = artist.ProviderIds; - } - - id.SongInfos = GetRecursiveChildren(i => i is Audio) - .Cast