From 6d250c4050ceb0bda33aad6a484fd05e508c4e27 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Nov 2016 04:31:05 -0400 Subject: [PATCH] make dlna project portable --- .../BaseApplicationHost.cs | 6 + .../Net/DisposableManagedObjectBase.cs | 74 ++++ .../Net/SocketFactory.cs | 113 ++++++ .../Net}/UdpSocket.cs | 51 +-- .../Reflection/AssemblyInfo.cs | 8 + .../ConnectionManager/ConnectionManager.cs | 7 +- Emby.Dlna/ConnectionManager/ControlHandler.cs | 12 +- .../ContentDirectory/ContentDirectory.cs | 8 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 220 ++++++----- Emby.Dlna/Didl/DidlBuilder.cs | 357 +++++++++--------- Emby.Dlna/Didl/StringWriterWithEncoding.cs | 62 +++ Emby.Dlna/DlnaManager.cs | 37 +- Emby.Dlna/Emby.Dlna.csproj | 202 ++++++++++ Emby.Dlna/Emby.Dlna.xproj | 24 -- Emby.Dlna/Main/DlnaEntryPoint.cs | 18 +- .../MediaReceiverRegistrar/ControlHandler.cs | 9 +- .../MediaReceiverRegistrar.cs | 7 +- Emby.Dlna/PlayTo/Device.cs | 14 +- Emby.Dlna/PlayTo/PlayToManager.cs | 8 +- Emby.Dlna/Properties/AssemblyInfo.cs | 31 +- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 12 +- Emby.Dlna/Server/Headers.cs | 9 +- Emby.Dlna/Server/UpnpDevice.cs | 5 +- Emby.Dlna/Service/BaseControlHandler.cs | 173 +++++++-- Emby.Dlna/Service/ControlErrorHandler.cs | 52 ++- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 12 +- Emby.Dlna/Ssdp/Extensions.cs | 1 - Emby.Dlna/project.json | 57 --- MediaBrowser.Api/Dlna/DlnaServerService.cs | 31 +- .../Dlna/ControlRequest.cs | 3 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 + .../Net}/ISocketFactory.cs | 19 +- .../Net}/IUdpSocket.cs | 17 +- MediaBrowser.Model/Net/IpEndPointInfo.cs | 18 + MediaBrowser.Model/Net/ReceivedUdpData.cs | 24 ++ .../Reflection/IAssemblyInfo.cs | 1 + .../ApplicationHost.cs | 13 +- .../MediaBrowser.Server.Startup.Common.csproj | 14 +- MediaBrowser.sln | 128 +++---- RSSDP/ISsdpCommunicationsServer.cs | 5 +- RSSDP/Properties/AssemblyInfo.cs | 31 +- RSSDP/{Rssdp.Portable.csproj => RSSDP.csproj} | 71 ++-- RSSDP/RSSDP.xproj | 21 -- RSSDP/ReceivedUdpData.cs | 29 -- RSSDP/RequestReceivedEventArgs.cs | 7 +- RSSDP/ResponseReceivedEventArgs.cs | 9 +- RSSDP/SocketFactory.cs | 114 ------ RSSDP/SsdpCommunicationsServer.cs | 15 +- RSSDP/SsdpDeviceLocator.cs | 13 +- RSSDP/SsdpDeviceLocatorBase.cs | 10 +- RSSDP/SsdpDevicePublisher.cs | 72 +--- RSSDP/SsdpDevicePublisherBase.cs | 23 +- RSSDP/UdpEndPoint.cs | 37 -- RSSDP/project.json | 48 --- 54 files changed, 1324 insertions(+), 1042 deletions(-) create mode 100644 Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs create mode 100644 Emby.Common.Implementations/Net/SocketFactory.cs rename {RSSDP => Emby.Common.Implementations/Net}/UdpSocket.cs (88%) create mode 100644 Emby.Dlna/Didl/StringWriterWithEncoding.cs create mode 100644 Emby.Dlna/Emby.Dlna.csproj delete mode 100644 Emby.Dlna/Emby.Dlna.xproj delete mode 100644 Emby.Dlna/project.json rename {RSSDP => MediaBrowser.Model/Net}/ISocketFactory.cs (59%) rename {RSSDP => MediaBrowser.Model/Net}/IUdpSocket.cs (50%) create mode 100644 MediaBrowser.Model/Net/IpEndPointInfo.cs create mode 100644 MediaBrowser.Model/Net/ReceivedUdpData.cs rename RSSDP/{Rssdp.Portable.csproj => RSSDP.csproj} (56%) delete mode 100644 RSSDP/RSSDP.xproj delete mode 100644 RSSDP/ReceivedUdpData.cs delete mode 100644 RSSDP/SocketFactory.cs delete mode 100644 RSSDP/UdpEndPoint.cs delete mode 100644 RSSDP/project.json diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index e1f7ef08aa..bdebe894ef 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -28,11 +28,13 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using Emby.Common.Implementations.Cryptography; using Emby.Common.Implementations.Diagnostics; +using Emby.Common.Implementations.Net; using Emby.Common.Implementations.Threading; using MediaBrowser.Common; using MediaBrowser.Common.IO; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Threading; @@ -153,6 +155,7 @@ namespace Emby.Common.Implementations protected IProcessFactory ProcessFactory { get; private set; } protected ITimerFactory TimerFactory { get; private set; } + protected ISocketFactory SocketFactory { get; private set; } /// /// Gets the name. @@ -549,6 +552,9 @@ return null; TimerFactory = new TimerFactory(); RegisterSingleInstance(TimerFactory); + SocketFactory = new SocketFactory(null); + RegisterSingleInstance(SocketFactory); + RegisterSingleInstance(CryptographyProvider); return Task.FromResult(true); diff --git a/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs b/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs new file mode 100644 index 0000000000..8476cea326 --- /dev/null +++ b/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Emby.Common.Implementations.Net +{ + /// + /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property. + /// + public abstract class DisposableManagedObjectBase : IDisposable + { + + #region Public Methods + + /// + /// Override this method and dispose any objects you own the lifetime of if disposing is true; + /// + /// True if managed objects should be disposed, if false, only unmanaged resources should be released. + protected abstract void Dispose(bool disposing); + + /// + /// Throws and if the property is true. + /// + /// + /// Thrown if the property is true. + /// + protected virtual void ThrowIfDisposed() + { + if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName); + } + + #endregion + + #region Public Properties + + /// + /// Sets or returns a boolean indicating whether or not this instance has been disposed. + /// + /// + public bool IsDisposed + { + get; + private set; + } + + #endregion + + #region IDisposable Members + + /// + /// Disposes this object instance and all internally managed resources. + /// + /// + /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. + /// + /// + public void Dispose() + { + try + { + IsDisposed = true; + + Dispose(true); + } + finally + { + GC.SuppressFinalize(this); + } + } + + #endregion + } +} diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs new file mode 100644 index 0000000000..3a2cea12a7 --- /dev/null +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Common.Implementations.Net +{ + public class SocketFactory : ISocketFactory + { + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + // Not entirely happy with this. Would have liked to have done something more generic/reusable, + // but that wasn't really the point so kept to YAGNI principal for now, even if the + // interfaces are a bit ugly, specific and make assumptions. + + /// + /// Used by RSSDP components to create implementations of the interface, to perform platform agnostic socket communications. + /// + private IPAddress _LocalIP; + + /// + /// Default constructor. + /// + /// A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use . + public SocketFactory(string localIP) + { + if (String.IsNullOrEmpty(localIP)) + _LocalIP = IPAddress.Any; + else + _LocalIP = IPAddress.Parse(localIP); + } + + #region ISocketFactory Members + + /// + /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. + /// + /// An integer specifying the local port to bind the socket to. + /// An implementation of the interface used by RSSDP components to perform socket operations. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpSocket(int localPort) + { + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + try + { + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + /// + /// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port. + /// + /// The multicast IP address to make the socket a member of. + /// The multicast time to live value for the socket. + /// The number of the local port to bind to. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) + { + if (ipAddress == null) throw new ArgumentNullException("ipAddress"); + if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress"); + if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive"); + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + try + { +#if NETSTANDARD1_3 + // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket + // See https://github.com/dotnet/corefx/pull/11509 for more details + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + retVal.ExclusiveAddressUse = false; + } +#else + retVal.ExclusiveAddressUse = false; +#endif + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + retVal.MulticastLoopback = true; + + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + #endregion + } +} diff --git a/RSSDP/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs similarity index 88% rename from RSSDP/UdpSocket.cs rename to Emby.Common.Implementations/Net/UdpSocket.cs index ea5d6ae977..86ce9c83be 100644 --- a/RSSDP/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -1,15 +1,13 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Security; -using System.Text; -using System.Threading; using System.Threading.Tasks; -using Rssdp.Infrastructure; +using MediaBrowser.Model.Net; -namespace Rssdp +namespace Emby.Common.Implementations.Net { // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS // Be careful to check any changes compile and work for all platform projects it is shared in. @@ -48,7 +46,7 @@ namespace Rssdp #region IUdpSocket Members - public System.Threading.Tasks.Task ReceiveAsync() + public Task ReceiveAsync() { ThrowIfDisposed(); @@ -76,7 +74,7 @@ namespace Rssdp return tcs.Task; } - public Task SendTo(byte[] messageData, UdpEndPoint endPoint) + public Task SendTo(byte[] messageData, IpEndPointInfo endPoint) { ThrowIfDisposed(); @@ -84,14 +82,14 @@ namespace Rssdp if (endPoint == null) throw new ArgumentNullException("endPoint"); #if NETSTANDARD1_6 - _Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + _Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); try { - _Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port), result => + _Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => { try { @@ -166,11 +164,7 @@ namespace Rssdp { Buffer = state.Buffer, ReceivedBytes = bytesRead, - ReceivedFrom = new UdpEndPoint() - { - IPAddress = ipEndPoint.Address.ToString(), - Port = ipEndPoint.Port - } + ReceivedFrom = ToIpEndPointInfo(ipEndPoint) } ); } @@ -191,6 +185,25 @@ namespace Rssdp } } + private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) + { + if (endpoint == null) + { + return null; + } + + return new IpEndPointInfo + { + IpAddress = new IpAddressInfo + { + Address = endpoint.Address.ToString(), + IsIpv6 = endpoint.AddressFamily == AddressFamily.InterNetworkV6 + }, + + Port = endpoint.Port + }; + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] private void ProcessResponse(IAsyncResult asyncResult) { @@ -206,11 +219,7 @@ namespace Rssdp { Buffer = state.Buffer, ReceivedBytes = bytesRead, - ReceivedFrom = new UdpEndPoint() - { - IPAddress = ipEndPoint.Address.ToString(), - Port = ipEndPoint.Port - } + ReceivedFrom = ToIpEndPointInfo(ipEndPoint) } ); } @@ -245,7 +254,7 @@ namespace Rssdp } public EndPoint EndPoint; - public byte[] Buffer = new byte[SsdpConstants.DefaultUdpSocketBufferSize]; + public byte[] Buffer = new byte[8192]; public System.Net.Sockets.Socket Socket { get; private set; } @@ -256,4 +265,4 @@ namespace Rssdp #endregion } -} \ No newline at end of file +} diff --git a/Emby.Common.Implementations/Reflection/AssemblyInfo.cs b/Emby.Common.Implementations/Reflection/AssemblyInfo.cs index 820856da5f..bd2cb7cf0b 100644 --- a/Emby.Common.Implementations/Reflection/AssemblyInfo.cs +++ b/Emby.Common.Implementations/Reflection/AssemblyInfo.cs @@ -14,5 +14,13 @@ namespace Emby.Common.Implementations.Reflection #endif return type.GetTypeInfo().Assembly.GetManifestResourceStream(resource); } + + public string[] GetManifestResourceNames(Type type) + { +#if NET46 + return type.Assembly.GetManifestResourceNames(); +#endif + return type.GetTypeInfo().Assembly.GetManifestResourceNames(); + } } } diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index 53253543b5..3f33f3ebf1 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Dlna; using Emby.Dlna.Service; using MediaBrowser.Model.Logging; using System.Collections.Generic; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.ConnectionManager { @@ -12,13 +13,15 @@ namespace Emby.Dlna.ConnectionManager private readonly IDlnaManager _dlna; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; + protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient) + public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, httpClient) { _dlna = dlna; _config = config; _logger = logger; + XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public string GetServiceXml(IDictionary headers) @@ -31,7 +34,7 @@ namespace Emby.Dlna.ConnectionManager var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile(); - return new ControlHandler(_logger, profile, _config).ProcessControlRequest(request); + return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request); } } } diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index e9af5cd1de..0bc44db170 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.ConnectionManager { @@ -13,12 +14,6 @@ namespace Emby.Dlna.ConnectionManager { private readonly DeviceProfile _profile; - public ControlHandler(ILogger logger, DeviceProfile profile, IServerConfigurationManager config) - : base(config, logger) - { - _profile = profile; - } - protected override IEnumerable> GetResult(string methodName, Headers methodParams) { if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) @@ -37,5 +32,10 @@ namespace Emby.Dlna.ConnectionManager { "Sink", "" } }; } + + public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory) + { + _profile = profile; + } } } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index e4cbb59f00..d9da7c81b4 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.ContentDirectory { @@ -29,6 +30,7 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaSourceManager _mediaSourceManager; private readonly IUserViewManager _userViewManager; private readonly Func _mediaEncoder; + protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; public ContentDirectory(IDlnaManager dlna, IUserDataManager userDataManager, @@ -37,7 +39,7 @@ namespace Emby.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func mediaEncoder) + IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, httpClient) { _dlna = dlna; @@ -51,6 +53,7 @@ namespace Emby.Dlna.ContentDirectory _mediaSourceManager = mediaSourceManager; _userViewManager = userViewManager; _mediaEncoder = mediaEncoder; + XmlReaderSettingsFactory = xmlReaderSettingsFactory; } private int SystemUpdateId @@ -93,7 +96,8 @@ namespace Emby.Dlna.ContentDirectory _channelManager, _mediaSourceManager, _userViewManager, - _mediaEncoder()) + _mediaEncoder(), + XmlReaderSettingsFactory) .ProcessControlRequest(request); } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 89260facae..dc599b993a 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -24,6 +25,7 @@ using System.Threading.Tasks; using System.Xml; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.ContentDirectory { @@ -35,7 +37,6 @@ namespace Emby.Dlna.ContentDirectory private readonly IServerConfigurationManager _config; private readonly User _user; private readonly IUserViewManager _userViewManager; - private readonly IMediaEncoder _mediaEncoder; private const string NS_DC = "http://purl.org/dc/elements/1.1/"; private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; @@ -49,8 +50,8 @@ 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) - : base(config, logger) + 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) + : base(config, logger, xmlReaderSettingsFactory) { _libraryManager = libraryManager; _userDataManager = userDataManager; @@ -58,11 +59,10 @@ namespace Emby.Dlna.ContentDirectory _systemUpdateId = systemUpdateId; _channelManager = channelManager; _userViewManager = userViewManager; - _mediaEncoder = mediaEncoder; _profile = profile; _config = config; - _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, _mediaEncoder); + _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder); } protected override IEnumerable> GetResult(string methodName, Headers methodParams) @@ -209,73 +209,92 @@ namespace Emby.Dlna.ContentDirectory start = startVal; } - //var root = GetItem(id) as IMediaFolder; - var result = new XmlDocument(); + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CloseOutput = false, + OmitXmlDeclaration = true, + ConformanceLevel = ConformanceLevel.Fragment + }; - var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL); - didl.SetAttribute("xmlns:dc", NS_DC); - didl.SetAttribute("xmlns:dlna", NS_DLNA); - didl.SetAttribute("xmlns:upnp", NS_UPNP); - //didl.SetAttribute("xmlns:sec", NS_SEC); - result.AppendChild(didl); - - var serverItem = GetItemFromObjectId(id, user); - var item = serverItem.Item; + StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); int totalCount; - if (string.Equals(flag, "BrowseMetadata")) + using (XmlWriter writer = XmlWriter.Create(builder, settings)) { - totalCount = 1; + //writer.WriteStartDocument(); - if (item.IsFolder || serverItem.StubType.HasValue) - { - var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false)); + writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id)); - } - else + 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); + + foreach (var att in _profile.XmlRootAttributes) { - result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, item, null, null, deviceId, filter)); + writer.WriteAttributeString(att.Name, att.Value); } - provided++; - } - else - { - var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false)); - totalCount = childrenResult.TotalRecordCount; + var serverItem = GetItemFromObjectId(id, user); + var item = serverItem.Item; - provided = childrenResult.Items.Length; - - foreach (var i in childrenResult.Items) + if (string.Equals(flag, "BrowseMetadata")) { - var childItem = i.Item; - var displayStubType = i.StubType; + totalCount = 1; - if (childItem.IsFolder || displayStubType.HasValue) + if (item.IsFolder || serverItem.StubType.HasValue) { - var childCount = (await GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false)) - .TotalRecordCount; + var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false)); - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, childItem, displayStubType, item, childCount, filter)); + _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id); } else { - result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, childItem, item, serverItem.StubType, deviceId, filter)); + _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, item, null, null, deviceId, filter); + } + + provided++; + } + else + { + var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false)); + totalCount = childrenResult.TotalRecordCount; + + provided = childrenResult.Items.Length; + + foreach (var i in childrenResult.Items) + { + var childItem = i.Item; + var displayStubType = i.StubType; + + if (childItem.IsFolder || displayStubType.HasValue) + { + var childCount = (await GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false)) + .TotalRecordCount; + + _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter); + } + else + { + _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, childItem, item, serverItem.StubType, deviceId, filter); + } } } + writer.WriteEndElement(); + //writer.WriteEndDocument(); } - var resXML = result.OuterXml; + var resXML = builder.ToString(); return new List> - { - new KeyValuePair("Result", resXML), - new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), - new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)), - new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture)) - }; + { + new KeyValuePair("Result", resXML), + new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), + new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)), + new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture)) + }; } private async Task>> HandleSearch(Headers sparams, User user, string deviceId) @@ -303,55 +322,72 @@ namespace Emby.Dlna.ContentDirectory start = startVal; } - //var root = GetItem(id) as IMediaFolder; - var result = new XmlDocument(); - - var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL); - didl.SetAttribute("xmlns:dc", NS_DC); - didl.SetAttribute("xmlns:dlna", NS_DLNA); - didl.SetAttribute("xmlns:upnp", NS_UPNP); - - foreach (var att in _profile.XmlRootAttributes) + var settings = new XmlWriterSettings { - didl.SetAttribute(att.Name, att.Value); + Encoding = Encoding.UTF8, + CloseOutput = false, + OmitXmlDeclaration = true, + ConformanceLevel = ConformanceLevel.Fragment + }; + + StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); + int totalCount = 0; + int provided = 0; + + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + //writer.WriteStartDocument(); + + 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); + + foreach (var att in _profile.XmlRootAttributes) + { + writer.WriteAttributeString(att.Name, att.Value); + } + + var serverItem = GetItemFromObjectId(sparams["ContainerID"], user); + + var item = serverItem.Item; + + var childrenResult = (await GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount).ConfigureAwait(false)); + + totalCount = childrenResult.TotalRecordCount; + + provided = childrenResult.Items.Length; + + foreach (var i in childrenResult.Items) + { + if (i.IsFolder) + { + var childCount = (await GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false)) + .TotalRecordCount; + + _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter); + } + else + { + _didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, i, item, serverItem.StubType, deviceId, filter); + } + } + + writer.WriteEndElement(); + //writer.WriteEndDocument(); } - result.AppendChild(didl); - - var serverItem = GetItemFromObjectId(sparams["ContainerID"], user); - - var item = serverItem.Item; - - var childrenResult = (await GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount).ConfigureAwait(false)); - - var totalCount = childrenResult.TotalRecordCount; - - var provided = childrenResult.Items.Length; - - foreach (var i in childrenResult.Items) - { - if (i.IsFolder) - { - var childCount = (await GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false)) - .TotalRecordCount; - - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, i, null, item, childCount, filter)); - } - else - { - result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, i, item, serverItem.StubType, deviceId, filter)); - } - } - - var resXML = result.OuterXml; + var resXML = builder.ToString(); return new List> - { - new KeyValuePair("Result", resXML), - new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), - new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)), - new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture)) - }; + { + new KeyValuePair("Result", resXML), + new KeyValuePair("NumberReturned", provided.ToString(_usCulture)), + new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)), + new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture)) + }; } private Task> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index dabd11510a..50668f5557 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -17,6 +17,7 @@ using System; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Xml; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; @@ -62,50 +63,66 @@ namespace Emby.Dlna.Didl public string GetItemDidl(DlnaOptions options, BaseItem item, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) { - var result = new XmlDocument(); - - var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL); - didl.SetAttribute("xmlns:dc", NS_DC); - didl.SetAttribute("xmlns:dlna", NS_DLNA); - didl.SetAttribute("xmlns:upnp", NS_UPNP); - //didl.SetAttribute("xmlns:sec", NS_SEC); - - foreach (var att in _profile.XmlRootAttributes) + var settings = new XmlWriterSettings { - didl.SetAttribute(att.Name, att.Value); + Encoding = Encoding.UTF8, + CloseOutput = false, + OmitXmlDeclaration = true, + ConformanceLevel = ConformanceLevel.Fragment + }; + + StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); + + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + //writer.WriteStartDocument(); + + 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); + + foreach (var att in _profile.XmlRootAttributes) + { + writer.WriteAttributeString(att.Name, att.Value); + } + + WriteItemElement(options, writer, item, context, null, deviceId, filter, streamInfo); + + writer.WriteEndElement(); + //writer.WriteEndDocument(); } - result.AppendChild(didl); - - result.DocumentElement.AppendChild(GetItemElement(options, result, item, context, null, deviceId, filter, streamInfo)); - - return result.DocumentElement.OuterXml; + return builder.ToString(); } - public XmlElement GetItemElement(DlnaOptions options, XmlDocument doc, BaseItem item, BaseItem context, StubType? contextStubType, string deviceId, Filter filter, StreamInfo streamInfo = null) + public void WriteItemElement(DlnaOptions options, XmlWriter writer, BaseItem item, BaseItem context, StubType? contextStubType, string deviceId, Filter filter, StreamInfo streamInfo = null) { var clientId = GetClientId(item, null); - var element = doc.CreateElement(string.Empty, "item", NS_DIDL); - element.SetAttribute("restricted", "1"); - element.SetAttribute("id", clientId); + writer.WriteStartElement(string.Empty, "item", NS_DIDL); + + writer.WriteAttributeString("restricted", "1"); + writer.WriteAttributeString("id", clientId); if (context != null) { - element.SetAttribute("parentID", GetClientId(context, contextStubType)); + writer.WriteAttributeString("parentID", GetClientId(context, contextStubType)); } else { var parent = item.DisplayParentId; if (parent.HasValue) { - element.SetAttribute("parentID", GetClientId(parent.Value, null)); + writer.WriteAttributeString("parentID", GetClientId(parent.Value, null)); } } //AddBookmarkInfo(item, user, element); - AddGeneralProperties(item, null, context, element, filter); + AddGeneralProperties(item, null, context, writer, filter); // refID? // storeAttribute(itemNode, object, ClassProperties.REF_ID, false); @@ -116,17 +133,16 @@ namespace Emby.Dlna.Didl { if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - AddAudioResource(options, element, hasMediaSources, deviceId, filter, streamInfo); + AddAudioResource(options, writer, hasMediaSources, deviceId, filter, streamInfo); } else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - AddVideoResource(options, element, hasMediaSources, deviceId, filter, streamInfo); + AddVideoResource(options, writer, hasMediaSources, deviceId, filter, streamInfo); } } - AddCover(item, context, null, element); - - return element; + AddCover(item, context, null, writer); + writer.WriteEndElement(); } private ILogger GetStreamBuilderLogger(DlnaOptions options) @@ -139,7 +155,7 @@ namespace Emby.Dlna.Didl return new NullLogger(); } - private void AddVideoResource(DlnaOptions options, XmlElement container, IHasMediaSources video, string deviceId, Filter filter, StreamInfo streamInfo = null) + private void AddVideoResource(DlnaOptions options, XmlWriter writer, IHasMediaSources video, string deviceId, Filter filter, StreamInfo streamInfo = null) { if (streamInfo == null) { @@ -182,14 +198,14 @@ namespace Emby.Dlna.Didl foreach (var contentFeature in contentFeatureList) { - AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo); + AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo); } foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken)) { if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External) { - var subtitleAdded = AddSubtitleElement(container, subtitle); + var subtitleAdded = AddSubtitleElement(writer, subtitle); if (subtitleAdded && _profile.EnableSingleSubtitleLimit) { @@ -199,7 +215,7 @@ namespace Emby.Dlna.Didl } } - private bool AddSubtitleElement(XmlElement container, SubtitleStreamInfo info) + private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info) { var subtitleProfile = _profile.SubtitleProfiles .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External); @@ -216,52 +232,45 @@ namespace Emby.Dlna.Didl // http://192.168.1.3:9999/video.srt // http://192.168.1.3:9999/video.srt - var res = container.OwnerDocument.CreateElement("CaptionInfoEx", "sec"); + writer.WriteStartElement("sec", "CaptionInfoEx", null); + writer.WriteAttributeString("sec", "type", null, info.Format.ToLower()); - res.InnerText = info.Url; - - //// TODO: attribute needs SEC: - res.SetAttribute("type", "sec", info.Format.ToLower()); - container.AppendChild(res); + writer.WriteString(info.Url); + writer.WriteEndElement(); } else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase)) { - var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NS_DIDL); - res.InnerText = info.Url; + writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*"); - res.SetAttribute("protocolInfo", "http-get:*:smi/caption:*"); - - container.AppendChild(res); + writer.WriteString(info.Url); + writer.WriteEndElement(); } else { - var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); - - res.InnerText = info.Url; - + writer.WriteStartElement(string.Empty, "res", NS_DIDL); var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower()); - res.SetAttribute("protocolInfo", protocolInfo); + writer.WriteAttributeString("protocolInfo", protocolInfo); - container.AppendChild(res); + writer.WriteString(info.Url); + writer.WriteEndElement(); } return true; } - private void AddVideoResource(XmlElement container, IHasMediaSources video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo) + private void AddVideoResource(XmlWriter writer, IHasMediaSources video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo) { - var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NS_DIDL); var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken); - res.InnerText = url; - var mediaSource = streamInfo.MediaSource; if (mediaSource.RunTimeTicks.HasValue) { - res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); } if (filter.Contains("res@size")) @@ -272,7 +281,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - res.SetAttribute("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); } } } @@ -286,25 +295,25 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); } if (filter.Contains("res@resolution")) { if (targetWidth.HasValue && targetHeight.HasValue) { - res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value)); + writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value)); } } if (targetSampleRate.HasValue) { - res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); } if (totalBitrate.HasValue) { - res.SetAttribute("bitrate", totalBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture)); } var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container, @@ -332,13 +341,15 @@ namespace Emby.Dlna.Didl ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; - res.SetAttribute("protocolInfo", String.Format( + writer.WriteAttributeString("protocolInfo", String.Format( "http-get:*:{0}:{1}", mimeType, contentFeatures )); - container.AppendChild(res); + writer.WriteString(url); + + writer.WriteEndElement(); } private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context) @@ -382,32 +393,30 @@ namespace Emby.Dlna.Didl return item.Name; } - private void AddAudioResource(DlnaOptions options, XmlElement container, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null) + private void AddAudioResource(DlnaOptions options, XmlWriter writer, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { - var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NS_DIDL); if (streamInfo == null) { var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions - { - ItemId = GetClientId(audio), - MediaSources = sources, - Profile = _profile, - DeviceId = deviceId - }); + { + ItemId = GetClientId(audio), + MediaSources = sources, + Profile = _profile, + DeviceId = deviceId + }); } var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken); - res.InnerText = url; - var mediaSource = streamInfo.MediaSource; if (mediaSource.RunTimeTicks.HasValue) { - res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); } if (filter.Contains("res@size")) @@ -418,7 +427,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - res.SetAttribute("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); } } } @@ -429,17 +438,17 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); } if (targetSampleRate.HasValue) { - res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); } if (targetAudioBitrate.HasValue) { - res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); } var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container, @@ -462,13 +471,15 @@ namespace Emby.Dlna.Didl streamInfo.RunTimeTicks, streamInfo.TranscodeSeekInfo); - res.SetAttribute("protocolInfo", String.Format( + writer.WriteAttributeString("protocolInfo", String.Format( "http-get:*:{0}:{1}", mimeType, contentFeatures )); - container.AppendChild(res); + writer.WriteString(url); + + writer.WriteEndElement(); } public static bool IsIdRoot(string id) @@ -486,47 +497,48 @@ namespace Emby.Dlna.Didl return false; } - public XmlElement GetFolderElement(XmlDocument doc, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null) + public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null) { - var container = doc.CreateElement(string.Empty, "container", NS_DIDL); - container.SetAttribute("restricted", "0"); - container.SetAttribute("searchable", "1"); - container.SetAttribute("childCount", childCount.ToString(_usCulture)); + writer.WriteStartElement(string.Empty, "container", NS_DIDL); + + writer.WriteAttributeString("restricted", "0"); + writer.WriteAttributeString("searchable", "1"); + writer.WriteAttributeString("childCount", childCount.ToString(_usCulture)); var clientId = GetClientId(folder, stubType); if (string.Equals(requestedId, "0")) { - container.SetAttribute("id", "0"); - container.SetAttribute("parentID", "-1"); + writer.WriteAttributeString("id", "0"); + writer.WriteAttributeString("parentID", "-1"); } else { - container.SetAttribute("id", clientId); + writer.WriteAttributeString("id", clientId); if (context != null) { - container.SetAttribute("parentID", GetClientId(context, null)); + writer.WriteAttributeString("parentID", GetClientId(context, null)); } else { var parent = folder.DisplayParentId; if (!parent.HasValue) { - container.SetAttribute("parentID", "0"); + writer.WriteAttributeString("parentID", "0"); } else { - container.SetAttribute("parentID", GetClientId(parent.Value, null)); + writer.WriteAttributeString("parentID", GetClientId(parent.Value, null)); } } } - AddCommonFields(folder, stubType, null, container, filter); + AddCommonFields(folder, stubType, null, writer, filter); - AddCover(folder, context, stubType, container); + AddCover(folder, context, stubType, writer); - return container; + writer.WriteEndElement(); } //private void AddBookmarkInfo(BaseItem item, User user, XmlElement element) @@ -544,27 +556,22 @@ namespace Emby.Dlna.Didl /// /// Adds fields used by both items and folders /// - /// The item. - /// Type of the item stub. - /// The context. - /// The element. - /// The filter. - private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter) + private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) { // Don't filter on dc:title because not all devices will include it in the filter // MediaMonkey for example won't display content without a title //if (filter.Contains("dc:title")) { - AddValue(element, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); + AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); } - element.AppendChild(CreateObjectClass(element.OwnerDocument, item, itemStubType)); + WriteObjectClass(writer, item, itemStubType); if (filter.Contains("dc:date")) { if (item.PremiereDate.HasValue) { - AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC); + AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC); } } @@ -572,13 +579,13 @@ namespace Emby.Dlna.Didl { foreach (var genre in item.Genres) { - AddValue(element, "upnp", "genre", genre, NS_UPNP); + AddValue(writer, "upnp", "genre", genre, NS_UPNP); } } foreach (var studio in item.Studios) { - AddValue(element, "upnp", "publisher", studio, NS_UPNP); + AddValue(writer, "upnp", "publisher", studio, NS_UPNP); } if (filter.Contains("dc:description")) @@ -592,14 +599,14 @@ namespace Emby.Dlna.Didl if (!string.IsNullOrWhiteSpace(desc)) { - AddValue(element, "dc", "description", desc, NS_DC); + AddValue(writer, "dc", "description", desc, NS_DC); } } if (filter.Contains("upnp:longDescription")) { if (!string.IsNullOrWhiteSpace(item.Overview)) { - AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP); + AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP); } } @@ -607,23 +614,23 @@ namespace Emby.Dlna.Didl { if (filter.Contains("dc:rating")) { - AddValue(element, "dc", "rating", item.OfficialRating, NS_DC); + AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC); } if (filter.Contains("upnp:rating")) { - AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP); + AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP); } } - AddPeople(item, element); + AddPeople(item, writer); } - private XmlElement CreateObjectClass(XmlDocument result, BaseItem item, StubType? stubType) + private void WriteObjectClass(XmlWriter writer, BaseItem item, StubType? stubType) { // More types here // http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs - var objectClass = result.CreateElement("upnp", "class", NS_UPNP); + writer.WriteStartElement("upnp", "class", NS_UPNP); if (item.IsFolder || stubType.HasValue) { @@ -653,48 +660,48 @@ namespace Emby.Dlna.Didl } } - objectClass.InnerText = classType ?? "object.container.storageFolder"; + writer.WriteString(classType ?? "object.container.storageFolder"); } else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - objectClass.InnerText = "object.item.audioItem.musicTrack"; + writer.WriteString("object.item.audioItem.musicTrack"); } else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) { - objectClass.InnerText = "object.item.imageItem.photo"; + writer.WriteString("object.item.imageItem.photo"); } else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { if (!_profile.RequiresPlainVideoItems && item is Movie) { - objectClass.InnerText = "object.item.videoItem.movie"; + writer.WriteString("object.item.videoItem.movie"); } else if (!_profile.RequiresPlainVideoItems && item is MusicVideo) { - objectClass.InnerText = "object.item.videoItem.musicVideoClip"; + writer.WriteString("object.item.videoItem.musicVideoClip"); } else { - objectClass.InnerText = "object.item.videoItem"; + writer.WriteString("object.item.videoItem"); } } else if (item is MusicGenre) { - objectClass.InnerText = _profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre"; + writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre"); } else if (item is Genre || item is GameGenre) { - objectClass.InnerText = _profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre"; + writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre"); } else { - objectClass.InnerText = "object.item"; + writer.WriteString("object.item"); } - return objectClass; + writer.WriteEndElement(); } - private void AddPeople(BaseItem item, XmlElement element) + private void AddPeople(BaseItem item, XmlWriter writer) { var types = new[] { @@ -718,7 +725,7 @@ namespace Emby.Dlna.Didl var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) ?? PersonType.Actor; - AddValue(element, "upnp", type.ToLower(), actor.Name, NS_UPNP); + AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP); index++; @@ -729,9 +736,9 @@ namespace Emby.Dlna.Didl } } - private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter) + private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) { - AddCommonFields(item, itemStubType, context, element, filter); + AddCommonFields(item, itemStubType, context, writer, filter); var audio = item as Audio; @@ -739,17 +746,17 @@ namespace Emby.Dlna.Didl { foreach (var artist in audio.Artists) { - AddValue(element, "upnp", "artist", artist, NS_UPNP); + AddValue(writer, "upnp", "artist", artist, NS_UPNP); } if (!string.IsNullOrEmpty(audio.Album)) { - AddValue(element, "upnp", "album", audio.Album, NS_UPNP); + AddValue(writer, "upnp", "album", audio.Album, NS_UPNP); } foreach (var artist in audio.AlbumArtists) { - AddAlbumArtist(element, artist); + AddAlbumArtist(writer, artist); } } @@ -759,12 +766,12 @@ namespace Emby.Dlna.Didl { foreach (var artist in album.AlbumArtists) { - AddAlbumArtist(element, artist); - AddValue(element, "upnp", "artist", artist, NS_UPNP); + AddAlbumArtist(writer, artist); + AddValue(writer, "upnp", "artist", artist, NS_UPNP); } foreach (var artist in album.Artists) { - AddValue(element, "upnp", "artist", artist, NS_UPNP); + AddValue(writer, "upnp", "artist", artist, NS_UPNP); } } @@ -774,37 +781,37 @@ namespace Emby.Dlna.Didl { foreach (var artist in musicVideo.Artists) { - AddValue(element, "upnp", "artist", artist, NS_UPNP); - AddAlbumArtist(element, artist); + AddValue(writer, "upnp", "artist", artist, NS_UPNP); + AddAlbumArtist(writer, artist); } if (!string.IsNullOrEmpty(musicVideo.Album)) { - AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP); + AddValue(writer, "upnp", "album", musicVideo.Album, NS_UPNP); } } if (item.IndexNumber.HasValue) { - AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); + AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); if (item is Episode) { - AddValue(element, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); + AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); } } } - private void AddAlbumArtist(XmlElement elem, string name) + private void AddAlbumArtist(XmlWriter writer, string name) { try { - var newNode = elem.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP); - newNode.InnerText = name; + writer.WriteStartElement("upnp", "artist", NS_UPNP); + writer.WriteAttributeString("role", "AlbumArtist"); - newNode.SetAttribute("role", "AlbumArtist"); + writer.WriteString(name); - elem.AppendChild(newNode); + writer.WriteEndElement(); } catch (XmlException) { @@ -812,13 +819,11 @@ namespace Emby.Dlna.Didl } } - private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri) + private void AddValue(XmlWriter writer, string prefix, string name, string value, string namespaceUri) { try { - var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri); - date.InnerText = value; - elem.AppendChild(date); + writer.WriteElementString(prefix, name, namespaceUri, value); } catch (XmlException) { @@ -826,11 +831,11 @@ namespace Emby.Dlna.Didl } } - private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlElement element) + private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer) { if (stubType.HasValue && stubType.Value == StubType.People) { - AddEmbeddedImageAsCover("people", element); + AddEmbeddedImageAsCover("people", writer); return; } @@ -860,8 +865,6 @@ namespace Emby.Dlna.Didl return; } - var result = element.OwnerDocument; - var playbackPercentage = 0; var unplayedCount = 0; @@ -891,18 +894,14 @@ namespace Emby.Dlna.Didl var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, unplayedCount, "jpg"); - var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP); - var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); - profile.InnerText = _profile.AlbumArtPn; - icon.SetAttributeNode(profile); - icon.InnerText = albumartUrlInfo.Url; - element.AppendChild(icon); + writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP); + writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn); + writer.WriteString(albumartUrlInfo.Url); + writer.WriteEndElement(); // TOOD: Remove these default values var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, unplayedCount, "jpg"); - icon = result.CreateElement("upnp", "icon", NS_UPNP); - icon.InnerText = iconUrlInfo.Url; - element.AppendChild(icon); + writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url); if (!_profile.EnableAlbumArtInDidl) { @@ -916,36 +915,30 @@ namespace Emby.Dlna.Didl } } - AddImageResElement(item, element, 160, 160, playbackPercentage, unplayedCount, "jpg", "JPEG_TN"); + AddImageResElement(item, writer, 160, 160, playbackPercentage, unplayedCount, "jpg", "JPEG_TN"); if (!_profile.EnableSingleAlbumArtLimit) { - AddImageResElement(item, element, 4096, 4096, playbackPercentage, unplayedCount, "jpg", "JPEG_LRG"); - AddImageResElement(item, element, 1024, 768, playbackPercentage, unplayedCount, "jpg", "JPEG_MED"); - AddImageResElement(item, element, 640, 480, playbackPercentage, unplayedCount, "jpg", "JPEG_SM"); - AddImageResElement(item, element, 4096, 4096, playbackPercentage, unplayedCount, "png", "PNG_LRG"); - AddImageResElement(item, element, 160, 160, playbackPercentage, unplayedCount, "png", "PNG_TN"); + 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"); } } - private void AddEmbeddedImageAsCover(string name, XmlElement element) + private void AddEmbeddedImageAsCover(string name, XmlWriter writer) { - var result = element.OwnerDocument; + writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP); + writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn); + writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg"); + writer.WriteEndElement(); - var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP); - var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); - profile.InnerText = _profile.AlbumArtPn; - icon.SetAttributeNode(profile); - icon.InnerText = _serverAddress + "/Dlna/icons/people480.jpg"; - element.AppendChild(icon); - - icon = result.CreateElement("upnp", "icon", NS_UPNP); - icon.InnerText = _serverAddress + "/Dlna/icons/people48.jpg"; - element.AppendChild(icon); + writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg"); } private void AddImageResElement(BaseItem item, - XmlElement element, + XmlWriter writer, int maxWidth, int maxHeight, int playbackPercentage, @@ -960,13 +953,9 @@ namespace Emby.Dlna.Didl return; } - var result = element.OwnerDocument; - var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, unplayedCount, format); - var res = result.CreateElement(string.Empty, "res", NS_DIDL); - - res.InnerText = albumartUrlInfo.Url; + writer.WriteStartElement(string.Empty, "res", NS_DIDL); var width = albumartUrlInfo.Width; var height = albumartUrlInfo.Height; @@ -974,7 +963,7 @@ namespace Emby.Dlna.Didl var contentFeatures = new ContentFeatureBuilder(_profile) .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); - res.SetAttribute("protocolInfo", String.Format( + writer.WriteAttributeString("protocolInfo", String.Format( "http-get:*:{0}:{1}", MimeTypes.GetMimeType("file." + format), contentFeatures @@ -982,10 +971,12 @@ namespace Emby.Dlna.Didl if (width.HasValue && height.HasValue) { - res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value)); + writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width.Value, height.Value)); } - element.AppendChild(res); + writer.WriteString(albumartUrlInfo.Url); + + writer.WriteEndElement(); } private ImageDownloadInfo GetImageInfo(BaseItem item) diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs new file mode 100644 index 0000000000..052d6610b1 --- /dev/null +++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Emby.Dlna.Didl +{ + public class StringWriterWithEncoding : StringWriter + { + private readonly Encoding _encoding; + + public StringWriterWithEncoding() + { + } + + public StringWriterWithEncoding(IFormatProvider formatProvider) + : base(formatProvider) + { + } + + public StringWriterWithEncoding(StringBuilder sb) + : base(sb) + { + } + + public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider) + : base(sb, formatProvider) + { + } + + + public StringWriterWithEncoding(Encoding encoding) + { + _encoding = encoding; + } + + public StringWriterWithEncoding(IFormatProvider formatProvider, Encoding encoding) + : base(formatProvider) + { + _encoding = encoding; + } + + public StringWriterWithEncoding(StringBuilder sb, Encoding encoding) + : base(sb) + { + _encoding = encoding; + } + + public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider, Encoding encoding) + : base(sb, formatProvider) + { + _encoding = encoding; + } + + public override Encoding Encoding + { + get { return (null == _encoding) ? base.Encoding : _encoding; } + } + } +} diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index c348e658cc..143c6d4f01 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -16,12 +16,8 @@ using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; -#if NETSTANDARD1_6 -using System.Reflection; -#endif +using MediaBrowser.Model.Reflection; namespace Emby.Dlna { @@ -33,6 +29,7 @@ namespace Emby.Dlna private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; + private readonly IAssemblyInfo _assemblyInfo; private readonly Dictionary> _profiles = new Dictionary>(StringComparer.Ordinal); @@ -40,7 +37,7 @@ namespace Emby.Dlna IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, - IJsonSerializer jsonSerializer, IServerApplicationHost appHost) + IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; @@ -48,6 +45,7 @@ namespace Emby.Dlna _logger = logger; _jsonSerializer = jsonSerializer; _appHost = appHost; + _assemblyInfo = assemblyInfo; } public void InitProfiles() @@ -306,7 +304,7 @@ namespace Emby.Dlna .Where(i => i != null) .ToList(); } - catch (DirectoryNotFoundException) + catch (IOException) { return new List(); } @@ -400,17 +398,11 @@ namespace Emby.Dlna private void ExtractSystemProfiles() { -#if NET46 - var assembly = GetType().Assembly; var namespaceName = GetType().Namespace + ".Profiles.Json."; -#elif NETSTANDARD1_6 - var assembly = GetType().GetTypeInfo().Assembly; - var namespaceName = GetType().GetTypeInfo().Namespace + ".Profiles.Json."; -#endif var systemProfilesPath = SystemProfilesPath; - foreach (var name in assembly.GetManifestResourceNames() + foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType()) .Where(i => i.StartsWith(namespaceName)) .ToList()) { @@ -418,9 +410,9 @@ namespace Emby.Dlna var path = Path.Combine(systemProfilesPath, filename); - using (var stream = assembly.GetManifestResourceStream(name)) + using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name)) { - var fileInfo = new FileInfo(path); + var fileInfo = _fileSystem.GetFileInfo(path); if (!fileInfo.Exists || fileInfo.Length != stream.Length) { @@ -512,7 +504,7 @@ namespace Emby.Dlna try { - File.Delete(Path.ChangeExtension(path, ".xml")); + _fileSystem.DeleteFile(Path.ChangeExtension(path, ".xml")); } catch { @@ -562,20 +554,11 @@ namespace Emby.Dlna var resource = GetType().Namespace + ".Images." + filename.ToLower(); -#if NET46 return new ImageStream { Format = format, - Stream = GetType().Assembly.GetManifestResourceStream(resource) + Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource) }; -#elif NETSTANDARD1_6 - return new ImageStream - { - Format = format, - Stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(resource) - }; -#endif - throw new NotImplementedException(); } } diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj new file mode 100644 index 0000000000..0441cb3be0 --- /dev/null +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -0,0 +1,202 @@ + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {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 + + + + + \ No newline at end of file diff --git a/Emby.Dlna/Emby.Dlna.xproj b/Emby.Dlna/Emby.Dlna.xproj deleted file mode 100644 index ad5cf50f58..0000000000 --- a/Emby.Dlna/Emby.Dlna.xproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - f40e364d-01d9-4bbf-b82c-5d6c55e0a1f5 - Emby.Dlna - .\obj - .\bin\ - v4.5.2 - - - 2.0 - - - - - - - - \ No newline at end of file diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 69f63189e8..14fbbcfa20 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -19,6 +19,8 @@ using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Threading; using Rssdp; using Rssdp.Infrastructure; @@ -29,7 +31,6 @@ namespace Emby.Dlna.Main private readonly IServerConfigurationManager _config; private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; - private readonly INetworkManager _network; private PlayToManager _manager; private readonly ISessionManager _sessionManager; @@ -49,10 +50,13 @@ namespace Emby.Dlna.Main private bool _dlnaServerStarted; private SsdpDevicePublisher _Publisher; + private readonly ITimerFactory _timerFactory; + private readonly ISocketFactory _socketFactory; + + public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, - INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, ILibraryManager libraryManager, @@ -62,11 +66,10 @@ namespace Emby.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory) { _config = config; _appHost = appHost; - _network = network; _sessionManager = sessionManager; _httpClient = httpClient; _libraryManager = libraryManager; @@ -78,6 +81,8 @@ namespace Emby.Dlna.Main _mediaSourceManager = mediaSourceManager; _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; + _socketFactory = socketFactory; + _timerFactory = timerFactory; _logger = logManager.GetLogger("Dlna"); } @@ -164,7 +169,7 @@ namespace Emby.Dlna.Main private void StartPublishing() { SsdpDevicePublisherBase.LogFunction = LogMessage; - _Publisher = new SsdpDevicePublisher(); + _Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, "Windows", "10"); } private void StartDeviceDiscovery() @@ -328,7 +333,8 @@ namespace Emby.Dlna.Main _userDataManager, _localization, _mediaSourceManager, - _mediaEncoder); + _mediaEncoder, + _timerFactory); _manager.Start(); } diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 572fcb6629..5e232aeaca 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -5,15 +5,12 @@ using Emby.Dlna.Service; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.MediaReceiverRegistrar { public class ControlHandler : BaseControlHandler { - public ControlHandler(IServerConfigurationManager config, ILogger logger) : base(config, logger) - { - } - protected override IEnumerable> GetResult(string methodName, Headers methodParams) { if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) @@ -39,5 +36,9 @@ namespace Emby.Dlna.MediaReceiverRegistrar { "Result", "1" } }; } + + public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory) + { + } } } diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index b96aabf62c..365354efd9 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -5,17 +5,20 @@ using Emby.Dlna.Service; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.MediaReceiverRegistrar { public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar, IDisposable { private readonly IServerConfigurationManager _config; + protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config) + public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, httpClient) { _config = config; + XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public string GetServiceXml(IDictionary headers) @@ -27,7 +30,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { return new ControlHandler( _config, - Logger) + Logger, XmlReaderSettingsFactory) .ProcessControlRequest(request); } diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 5f3555de19..b14f1657a5 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -14,6 +14,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Emby.Dlna.Server; +using MediaBrowser.Model.Threading; namespace Emby.Dlna.PlayTo { @@ -24,7 +25,7 @@ namespace Emby.Dlna.PlayTo #region Fields & Properties - private Timer _timer; + private ITimer _timer; public DeviceInfo Properties { get; set; } @@ -96,12 +97,15 @@ namespace Emby.Dlna.PlayTo public DateTime DateLastActivity { get; private set; } public Action OnDeviceUnavailable { get; set; } - public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config) + private readonly ITimerFactory _timerFactory; + + public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory) { Properties = deviceProperties; _httpClient = httpClient; _logger = logger; _config = config; + _timerFactory = timerFactory; } private int GetPlaybackTimerIntervalMs() @@ -116,7 +120,7 @@ namespace Emby.Dlna.PlayTo public void Start() { - _timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs()); + _timer = _timerFactory.Create(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs()); _timerActive = false; } @@ -830,7 +834,7 @@ namespace Emby.Dlna.PlayTo set; } - public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger) + public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory) { var ssdpHttpClient = new SsdpHttpClient(httpClient, config); @@ -922,7 +926,7 @@ namespace Emby.Dlna.PlayTo } } - var device = new Device(deviceProperties, httpClient, logger, config); + var device = new Device(deviceProperties, httpClient, logger, config, timerFactory); if (isRenderer) { diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 8b4d57b82c..b714f4ac01 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -11,12 +11,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Threading; namespace Emby.Dlna.PlayTo { @@ -38,11 +38,12 @@ namespace Emby.Dlna.PlayTo private readonly IDeviceDiscovery _deviceDiscovery; private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; + private readonly ITimerFactory _timerFactory; private readonly List _nonRendererUrls = new List(); private DateTime _lastRendererClear; - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) + public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory) { _logger = logger; _sessionManager = sessionManager; @@ -58,6 +59,7 @@ namespace Emby.Dlna.PlayTo _localization = localization; _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; + _timerFactory = timerFactory; } public void Start() @@ -107,7 +109,7 @@ namespace Emby.Dlna.PlayTo var uri = info.Location; _logger.Debug("Attempting to create PlayToController from location {0}", location); - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); + var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory).ConfigureAwait(false); if (device.RendererCommands == null) { diff --git a/Emby.Dlna/Properties/AssemblyInfo.cs b/Emby.Dlna/Properties/AssemblyInfo.cs index 04c8f83d84..6f924f9e96 100644 --- a/Emby.Dlna/Properties/AssemblyInfo.cs +++ b/Emby.Dlna/Properties/AssemblyInfo.cs @@ -1,19 +1,30 @@ -using System.Reflection; +using System.Resources; +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.Dlna2")] +[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Emby.Dlna")] +[assembly: AssemblyProduct("Emby.Dlna2")] +[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] -// 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("f40e364d-01d9-4bbf-b82c-5d6c55e0a1f5")] +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index a7a243c660..2a4a5792fa 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -216,7 +216,17 @@ namespace Emby.Dlna.Server return "Emby - " + _serverName; } - var characters = _serverName.Where(c => (char.IsLetterOrDigit(c) || c == '-')).ToArray(); + var characterList = new List(); + + foreach (var c in _serverName) + { + if (char.IsLetterOrDigit(c) || c == '-') + { + characterList.Add(c); + } + } + + var characters = characterList.ToArray(); var serverName = new string(characters); diff --git a/Emby.Dlna/Server/Headers.cs b/Emby.Dlna/Server/Headers.cs index 9899937521..47dd8e3215 100644 --- a/Emby.Dlna/Server/Headers.cs +++ b/Emby.Dlna/Server/Headers.cs @@ -11,7 +11,7 @@ namespace Emby.Dlna.Server { private readonly bool _asIs = false; private readonly Dictionary _dict = new Dictionary(); - private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.IgnoreCase); public Headers(bool asIs) { @@ -42,13 +42,6 @@ namespace Emby.Dlna.Server return hb.ToString(); } } - public Stream HeaderStream - { - get - { - return new MemoryStream(Encoding.ASCII.GetBytes(HeaderBlock)); - } - } public bool IsReadOnly { get diff --git a/Emby.Dlna/Server/UpnpDevice.cs b/Emby.Dlna/Server/UpnpDevice.cs index 457cdc1ab8..46f3d1c833 100644 --- a/Emby.Dlna/Server/UpnpDevice.cs +++ b/Emby.Dlna/Server/UpnpDevice.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using MediaBrowser.Model.Net; namespace Emby.Dlna.Server { @@ -9,9 +10,9 @@ namespace Emby.Dlna.Server public readonly string Type; public readonly string USN; public readonly string Uuid; - public readonly IPAddress Address; + public readonly IpAddressInfo Address; - public UpnpDevice(string aUuid, string aType, Uri aDescriptor, IPAddress address) + public UpnpDevice(string aUuid, string aType, Uri aDescriptor, IpAddressInfo address) { Uuid = aUuid; Type = aType; diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 7bf119111e..df4d29e8b4 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -4,9 +4,12 @@ using Emby.Dlna.Server; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Xml; +using Emby.Dlna.Didl; +using MediaBrowser.Model.Xml; namespace Emby.Dlna.Service { @@ -16,11 +19,13 @@ namespace Emby.Dlna.Service protected readonly IServerConfigurationManager Config; protected readonly ILogger Logger; + protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) + protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) { Config = config; Logger = logger; + XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public ControlResponse ProcessControlRequest(ControlRequest request) @@ -53,47 +58,57 @@ namespace Emby.Dlna.Service private ControlResponse ProcessControlRequestInternal(ControlRequest request) { - var soap = new XmlDocument(); - soap.LoadXml(request.InputXml); - var sparams = new Headers(); - var body = soap.GetElementsByTagName("Body", NS_SOAPENV).Item(0); + ControlRequestInfo requestInfo = null; - var method = body.FirstChild; - - foreach (var p in method.ChildNodes) + using (var streamReader = new StreamReader(request.InputXml)) { - var e = p as XmlElement; - if (e == null) + var readerSettings = XmlReaderSettingsFactory.Create(false); + + readerSettings.CheckCharacters = false; + readerSettings.IgnoreProcessingInstructions = true; + readerSettings.IgnoreComments = true; + + using (var reader = XmlReader.Create(streamReader, readerSettings)) { - continue; + requestInfo = ParseRequest(reader); } - sparams.Add(e.LocalName, e.InnerText.Trim()); } - Logger.Debug("Received control request {0}", method.LocalName); + Logger.Debug("Received control request {0}", requestInfo.LocalName); - var result = GetResult(method.LocalName, sparams); + var result = GetResult(requestInfo.LocalName, requestInfo.Headers); - var env = new XmlDocument(); - env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", string.Empty)); - var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV); - env.AppendChild(envelope); - envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); - - var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV); - env.DocumentElement.AppendChild(rbody); - - var response = env.CreateElement(String.Format("u:{0}Response", method.LocalName), method.NamespaceURI); - rbody.AppendChild(response); - - foreach (var i in result) + var settings = new XmlWriterSettings { - var ri = env.CreateElement(i.Key); - ri.InnerText = i.Value; - response.AppendChild(ri); + Encoding = Encoding.UTF8, + CloseOutput = false + }; + + StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); + + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + writer.WriteStartDocument(true); + + writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV); + writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); + + writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV); + writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI); + foreach (var i in result) + { + writer.WriteStartElement(i.Key); + writer.WriteString(i.Value); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteEndElement(); + + writer.WriteEndElement(); + writer.WriteEndDocument(); } - var xml = env.OuterXml.Replace("xmlns:m=", "xmlns:u="); + var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u="); var controlResponse = new ControlResponse { @@ -108,6 +123,102 @@ namespace Emby.Dlna.Service return controlResponse; } + private ControlRequestInfo ParseRequest(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.LocalName) + { + case "Body": + { + using (var subReader = reader.ReadSubtree()) + { + return ParseBodyTag(subReader); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return new ControlRequestInfo(); + } + + private ControlRequestInfo ParseBodyTag(XmlReader reader) + { + var result = new ControlRequestInfo(); + + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element) + { + result.LocalName = reader.LocalName; + result.NamespaceURI = reader.NamespaceURI; + + using (var subReader = reader.ReadSubtree()) + { + result.Headers = ParseFirstBodyChild(subReader); + + return result; + } + } + else + { + reader.Read(); + } + } + + return result; + } + + private Headers ParseFirstBodyChild(XmlReader reader) + { + var result = new Headers(); + + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element) + { + result.Add(reader.LocalName, reader.ReadElementContentAsString()); + } + else + { + reader.Read(); + } + } + + return result; + } + + private class ControlRequestInfo + { + public string LocalName; + public string NamespaceURI; + public Headers Headers = new Headers(); + } + protected abstract IEnumerable> GetResult(string methodName, Headers methodParams); private void LogRequest(ControlRequest request) diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs index 139f99931e..07b4dad605 100644 --- a/Emby.Dlna/Service/ControlErrorHandler.cs +++ b/Emby.Dlna/Service/ControlErrorHandler.cs @@ -1,6 +1,9 @@ using MediaBrowser.Controller.Dlna; using System; +using System.IO; +using System.Text; using System.Xml; +using Emby.Dlna.Didl; namespace Emby.Dlna.Service { @@ -10,30 +13,41 @@ namespace Emby.Dlna.Service public ControlResponse GetResponse(Exception ex) { - var env = new XmlDocument(); - env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", "yes")); - var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV); - env.AppendChild(envelope); - envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CloseOutput = false + }; - var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV); - env.DocumentElement.AppendChild(rbody); + StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); - var fault = env.CreateElement("SOAP-ENV", "Fault", NS_SOAPENV); - var faultCode = env.CreateElement("faultcode"); - faultCode.InnerText = "500"; - fault.AppendChild(faultCode); - var faultString = env.CreateElement("faultstring"); - faultString.InnerText = ex.ToString(); - fault.AppendChild(faultString); - var detail = env.CreateDocumentFragment(); - detail.InnerXml = "401Invalid Action"; - fault.AppendChild(detail); - rbody.AppendChild(fault); + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + writer.WriteStartDocument(true); + + writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV); + writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); + + writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV); + writer.WriteStartElement("SOAP-ENV", "Fault", NS_SOAPENV); + + writer.WriteElementString("faultcode", "500"); + writer.WriteElementString("faultstring", ex.Message); + + writer.WriteStartElement("detail"); + writer.WriteRaw("401Invalid Action"); + writer.WriteEndElement(); + + writer.WriteEndElement(); + writer.WriteEndElement(); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } return new ControlResponse { - Xml = env.OuterXml, + Xml = builder.ToString(), IsSuccessful = false }; } diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index f106496b9d..6f58429680 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -7,12 +7,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Threading; using Rssdp; namespace Emby.Dlna.Ssdp @@ -30,18 +31,23 @@ namespace Emby.Dlna.Ssdp private SsdpDeviceLocator _DeviceLocator; - public DeviceDiscovery(ILogger logger, IServerConfigurationManager config) + private readonly ITimerFactory _timerFactory; + private readonly ISocketFactory _socketFactory; + + public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, ISocketFactory socketFactory, ITimerFactory timerFactory) { _tokenSource = new CancellationTokenSource(); _logger = logger; _config = config; + _socketFactory = socketFactory; + _timerFactory = timerFactory; } // Call this method from somewhere in your code to start the search. public void BeginSearch() { - _DeviceLocator = new SsdpDeviceLocator(); + _DeviceLocator = new SsdpDeviceLocator(_socketFactory, _timerFactory); // (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 diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/Extensions.cs index 731f3ab0b5..611bf7e022 100644 --- a/Emby.Dlna/Ssdp/Extensions.cs +++ b/Emby.Dlna/Ssdp/Extensions.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Net; -using System.Net.Sockets; using System.Threading.Tasks; using System.Xml.Linq; diff --git a/Emby.Dlna/project.json b/Emby.Dlna/project.json deleted file mode 100644 index 2410a0593b..0000000000 --- a/Emby.Dlna/project.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "version": "1.0.0-*", - - "dependencies": { - - }, - - "frameworks": { - "net46": { - "frameworkAssemblies": { - "System.Collections": "4.0.0.0", - "System.IO": "4.0.0.0", - "System.Xml": "4.0.0.0", - "System.Xml.Linq": "4.0.0.0", - "System.Xml.Serialization": "4.0.0.0" - }, - "dependencies": { - "MediaBrowser.Controller": { - "target": "project" - }, - "MediaBrowser.Common": { - "target": "project" - }, - "MediaBrowser.Model": { - "target": "project" - }, - "RSSDP": { - "target": "project" - } - } - }, - "netstandard1.6": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.6.0", - "MediaBrowser.Controller": { - "target": "project" - }, - "MediaBrowser.Common": { - "target": "project" - }, - "MediaBrowser.Model": { - "target": "project" - }, - "System.Xml.XmlSerializer": "4.0.11", - "System.Xml.XDocument": "4.0.11", - "System.Xml.XmlDocument": "4.0.1", - "System.Reflection": "4.1.0", - "System.Reflection.Primitives": "4.0.1", - "System.Runtime.Loader": "4.0.0", - "RSSDP": { - "target": "project" - } - } - } - } -} diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 3f9f7bd067..93d0b3d551 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Api.Dlna { [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", 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; } } @@ -152,41 +152,38 @@ namespace MediaBrowser.Api.Dlna return ResultFactory.GetResult(xml, XMLContentType); } - public async Task Post(ProcessMediaReceiverRegistrarControlRequest request) + public object Post(ProcessMediaReceiverRegistrarControlRequest request) { - var response = await PostAsync(request.RequestStream, _mediaReceiverRegistrar).ConfigureAwait(false); + var response = PostAsync(request.RequestStream, _mediaReceiverRegistrar); return ResultFactory.GetResult(response.Xml, XMLContentType); } - public async Task Post(ProcessContentDirectoryControlRequest request) + public object Post(ProcessContentDirectoryControlRequest request) { - var response = await PostAsync(request.RequestStream, _contentDirectory).ConfigureAwait(false); + var response = PostAsync(request.RequestStream, _contentDirectory); return ResultFactory.GetResult(response.Xml, XMLContentType); } - public async Task Post(ProcessConnectionManagerControlRequest request) + public object Post(ProcessConnectionManagerControlRequest request) { - var response = await PostAsync(request.RequestStream, _connectionManager).ConfigureAwait(false); + var response = PostAsync(request.RequestStream, _connectionManager); return ResultFactory.GetResult(response.Xml, XMLContentType); } - private async Task PostAsync(Stream requestStream, IUpnpService service) + private ControlResponse PostAsync(Stream requestStream, IUpnpService service) { var id = GetPathValue(2); - using (var reader = new StreamReader(requestStream)) + return service.ProcessControlRequest(new ControlRequest { - return service.ProcessControlRequest(new ControlRequest - { - Headers = Request.Headers.ToDictionary(), - InputXml = await reader.ReadToEndAsync().ConfigureAwait(false), - TargetServerUuId = id, - RequestedUrl = Request.AbsoluteUri - }); - } + Headers = Request.Headers.ToDictionary(), + InputXml = requestStream, + TargetServerUuId = id, + RequestedUrl = Request.AbsoluteUri + }); } public object Get(GetIcon request) diff --git a/MediaBrowser.Controller/Dlna/ControlRequest.cs b/MediaBrowser.Controller/Dlna/ControlRequest.cs index 7020cc0d9f..ff951ec9eb 100644 --- a/MediaBrowser.Controller/Dlna/ControlRequest.cs +++ b/MediaBrowser.Controller/Dlna/ControlRequest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; namespace MediaBrowser.Controller.Dlna { @@ -6,7 +7,7 @@ namespace MediaBrowser.Controller.Dlna { public IDictionary Headers { get; set; } - public string InputXml { get; set; } + public Stream InputXml { get; set; } public string TargetServerUuId { get; set; } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 5999f02db2..03bbafe60a 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -137,6 +137,10 @@ + + + + diff --git a/RSSDP/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs similarity index 59% rename from RSSDP/ISocketFactory.cs rename to MediaBrowser.Model/Net/ISocketFactory.cs index 3e7d7facb9..c0e0440c25 100644 --- a/RSSDP/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Rssdp.Infrastructure + +namespace MediaBrowser.Model.Net { - /// - /// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform interface. - /// - public interface ISocketFactory + /// + /// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform interface. + /// + public interface ISocketFactory { /// @@ -26,8 +21,6 @@ namespace Rssdp.Infrastructure /// The multicast time to live value. Actually a maximum number of network hops for UDP packets. /// The local port to bind to. /// A implementation. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Justification="IP is a well known and understood abbreviation and the full name is excessive.")] IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort); - } } diff --git a/RSSDP/IUdpSocket.cs b/MediaBrowser.Model/Net/IUdpSocket.cs similarity index 50% rename from RSSDP/IUdpSocket.cs rename to MediaBrowser.Model/Net/IUdpSocket.cs index bcab4ecf12..cbeb8a995f 100644 --- a/RSSDP/IUdpSocket.cs +++ b/MediaBrowser.Model/Net/IUdpSocket.cs @@ -4,25 +4,24 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Rssdp.Infrastructure +namespace MediaBrowser.Model.Net { - /// - /// Provides a common interface across platforms for UDP sockets used by this SSDP implementation. - /// - public interface IUdpSocket : IDisposable + /// + /// Provides a common interface across platforms for UDP sockets used by this SSDP implementation. + /// + public interface IUdpSocket : IDisposable { /// /// Waits for and returns the next UDP message sent to this socket (uni or multicast). /// /// - System.Threading.Tasks.Task ReceiveAsync(); + Task ReceiveAsync(); /// /// Sends a UDP message to a particular end point (uni or multicast). /// /// The data to send. - /// The providing the address and port to send to. - Task SendTo(byte[] messageData, UdpEndPoint endPoint); - + /// The providing the address and port to send to. + Task SendTo(byte[] messageData, IpEndPointInfo endPoint); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Net/IpEndPointInfo.cs b/MediaBrowser.Model/Net/IpEndPointInfo.cs new file mode 100644 index 0000000000..5fd331a166 --- /dev/null +++ b/MediaBrowser.Model/Net/IpEndPointInfo.cs @@ -0,0 +1,18 @@ +using System; + +namespace MediaBrowser.Model.Net +{ + public class IpEndPointInfo + { + public IpAddressInfo IpAddress { get; set; } + + public int Port { get; set; } + + public override string ToString() + { + var ipAddresString = IpAddress == null ? string.Empty : IpAddress.ToString(); + + return ipAddresString + ":" + this.Port.ToString(); + } + } +} diff --git a/MediaBrowser.Model/Net/ReceivedUdpData.cs b/MediaBrowser.Model/Net/ReceivedUdpData.cs new file mode 100644 index 0000000000..1fdb22c930 --- /dev/null +++ b/MediaBrowser.Model/Net/ReceivedUdpData.cs @@ -0,0 +1,24 @@ + +namespace MediaBrowser.Model.Net +{ + /// + /// Used by the sockets wrapper to hold raw data received from a UDP socket. + /// + public sealed class ReceivedUdpData + { + /// + /// The buffer to place received data into. + /// + public byte[] Buffer { get; set; } + + /// + /// The number of bytes received. + /// + public int ReceivedBytes { get; set; } + + /// + /// The the data was received from. + /// + public IpEndPointInfo ReceivedFrom { get; set; } + } +} diff --git a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs b/MediaBrowser.Model/Reflection/IAssemblyInfo.cs index 1c65985cb2..634fadc1b4 100644 --- a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs +++ b/MediaBrowser.Model/Reflection/IAssemblyInfo.cs @@ -6,5 +6,6 @@ namespace MediaBrowser.Model.Reflection public interface IAssemblyInfo { Stream GetManifestResourceStream(Type type, string resource); + string[] GetManifestResourceNames(Type type); } } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index e9d6b49999..3a5939df1a 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -558,7 +558,8 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(() => new BdInfoExaminer(FileSystemManager, textEncoding)); RegisterSingleInstance(new XmlReaderSettingsFactory()); - RegisterSingleInstance(new AssemblyInfo()); + IAssemblyInfo assemblyInfo = new AssemblyInfo(); + RegisterSingleInstance(assemblyInfo); UserDataManager = new UserDataManager(LogManager, ServerConfigurationManager); RegisterSingleInstance(UserDataManager); @@ -648,10 +649,10 @@ namespace MediaBrowser.Server.Startup.Common 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); + 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); + var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient, new XmlReaderSettingsFactory()); RegisterSingleInstance(connectionManager); CollectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"), ProviderManager); @@ -666,10 +667,10 @@ namespace MediaBrowser.Server.Startup.Common 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); + var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager, () => MediaEncoder, new XmlReaderSettingsFactory()); RegisterSingleInstance(contentDirectory); - var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager); + var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager, new XmlReaderSettingsFactory()); RegisterSingleInstance(mediaRegistrar); NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager); @@ -678,7 +679,7 @@ namespace MediaBrowser.Server.Startup.Common SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager); RegisterSingleInstance(SubtitleManager); - RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager)); + RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory)); ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository); RegisterSingleInstance(ChapterManager); diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 4aecb11dc9..d30d892c93 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -35,9 +35,6 @@ ..\ThirdParty\emby\Emby.Common.Implementations.dll - - ..\ThirdParty\emby\Emby.Dlna.dll - ..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll True @@ -50,9 +47,6 @@ ..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll True - - ..\ThirdParty\emby\RSSDP.dll - False ..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll @@ -99,6 +93,10 @@ + + {805844ab-e92f-45e6-9d99-4f6d48d129a5} + Emby.Dlna + {08fff49b-f175-4807-a2b5-73b0ebd9f716} Emby.Drawing @@ -155,6 +153,10 @@ {4a4402d4-e910-443b-b8fc-2c18286a2ca0} OpenSubtitlesHandler + + {21002819-c39a-4d3e-be83-2a276a77fb1f} + RSSDP + diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 34c50de384..7e0e10b7a4 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -64,14 +64,14 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Emby.Common.Implementations EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RSSDP", "RSSDP\RSSDP.xproj", "{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.xproj", "{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Mono.Nat", "Mono.Nat\Mono.Nat.xproj", "{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -566,36 +566,6 @@ Global {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x64.Build.0 = Release|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.ActiveCfg = Release|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Win32.ActiveCfg = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Win32.Build.0 = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x64.ActiveCfg = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x64.Build.0 = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x86.ActiveCfg = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x86.Build.0 = Debug|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Any CPU.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Win32.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Win32.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x64.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x64.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x86.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x86.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Any CPU.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Win32.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Win32.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x64.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x64.Build.0 = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x86.ActiveCfg = Release|Any CPU - {C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x86.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -626,36 +596,6 @@ Global {E383961B-9356-4D5D-8233-9A1079D03055}.Release|x64.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.ActiveCfg = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Win32.ActiveCfg = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Win32.Build.0 = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x64.ActiveCfg = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x64.Build.0 = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x86.ActiveCfg = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x86.Build.0 = Debug|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Any CPU.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Win32.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Win32.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x64.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x64.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x86.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x86.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Any CPU.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Win32.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Win32.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x64.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x64.Build.0 = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x86.ActiveCfg = Release|Any CPU - {F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x86.Build.0 = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -686,6 +626,66 @@ Global {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x64.Build.0 = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.ActiveCfg = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Win32.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Win32.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x64.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x64.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x86.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x86.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Win32.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Win32.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x64.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x64.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x86.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x86.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Win32.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Win32.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x64.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x64.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Win32.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Win32.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x64.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x64.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x86.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x86.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Win32.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Win32.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x64.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x64.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x86.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x86.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Win32.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Win32.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x64.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x64.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index 990b21d05e..b1e84dc9f5 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using MediaBrowser.Model.Net; namespace Rssdp.Infrastructure { @@ -44,8 +45,8 @@ namespace Rssdp.Infrastructure /// Sends a message to a particular address (uni or multicast) and port. /// /// A byte array containing the data to send. - /// A representing the destination address for the data. Can be either a multicast or unicast destination. - Task SendMessage(byte[] messageData, UdpEndPoint destination); + /// A representing the destination address for the data. Can be either a multicast or unicast destination. + Task SendMessage(byte[] messageData, IpEndPointInfo destination); /// /// Sends a message to the SSDP multicast address and port. diff --git a/RSSDP/Properties/AssemblyInfo.cs b/RSSDP/Properties/AssemblyInfo.cs index 63f3af0832..1ce64b159d 100644 --- a/RSSDP/Properties/AssemblyInfo.cs +++ b/RSSDP/Properties/AssemblyInfo.cs @@ -1,19 +1,30 @@ -using System.Reflection; +using System.Resources; +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("RSSDP2")] +[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("RSSDP")] +[assembly: AssemblyProduct("RSSDP2")] +[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] -// 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("c227adb7-e256-4e70-a8b9-22b9e0cf4f55")] +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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/RSSDP/Rssdp.Portable.csproj b/RSSDP/RSSDP.csproj similarity index 56% rename from RSSDP/Rssdp.Portable.csproj rename to RSSDP/RSSDP.csproj index a8169f4b30..fb4d67e1ab 100644 --- a/RSSDP/Rssdp.Portable.csproj +++ b/RSSDP/RSSDP.csproj @@ -1,23 +1,20 @@  - + - 12.0 + 11.0 Debug AnyCPU - {67F9D3A8-F71E-4428-913F-C37AE82CDB24} + {21002819-C39A-4D3E-BE83-2A276A77FB1F} Library Properties - Rssdp - Rssdp.Portable - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile44 - v4.6 + RSSDP + RSSDP + en-US 512 - 1c5b2aa5 - ..\ - true - true + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 true @@ -27,79 +24,57 @@ DEBUG;TRACE prompt 4 - true - ..\RssdpRuleset.ruleset - bin\Debug\Rssdp.Portable.XML pdbonly true - ..\lib\portable-net45+win+wpa81+wp80\ + bin\Release\ TRACE prompt 4 - ..\RssdpRuleset.ruleset - ..\lib\portable-net45+win+wpa81+wp80\Rssdp.Portable.XML - true - - Properties\AssemblyInfoCommon.cs - + + + + - - - - - - - - - - + + + + + - - - - - - - Properties\CodeAnalysisDictionary.xml - Designer - + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - -