瀏覽代碼

added upnp ConnectionManager.cs

Luke Pulverenti 11 年之前
父節點
當前提交
1774e5b1ac
共有 23 個文件被更改,包括 779 次插入281 次删除
  1. 40 8
      MediaBrowser.Api/Dlna/DlnaServerService.cs
  2. 7 0
      MediaBrowser.Controller/Dlna/IConnectionManager.cs
  3. 2 16
      MediaBrowser.Controller/Dlna/IContentDirectory.cs
  4. 21 0
      MediaBrowser.Controller/Dlna/IUpnpService.cs
  5. 2 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  6. 34 0
      MediaBrowser.Dlna/ConnectionManager/ConnectionManager.cs
  7. 106 0
      MediaBrowser.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
  8. 30 0
      MediaBrowser.Dlna/ConnectionManager/ControlHandler.cs
  9. 205 0
      MediaBrowser.Dlna/ConnectionManager/ServiceActionListBuilder.cs
  10. 4 7
      MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
  11. 5 93
      MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
  12. 24 132
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  13. 1 1
      MediaBrowser.Dlna/ContentDirectory/ServiceActionListBuilder.cs
  14. 8 3
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  15. 12 4
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  16. 9 0
      MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
  17. 112 0
      MediaBrowser.Dlna/Service/BaseControlHandler.cs
  18. 41 0
      MediaBrowser.Dlna/Service/ControlErrorHandler.cs
  19. 90 0
      MediaBrowser.Dlna/Service/ServiceXmlBuilder.cs
  20. 1 1
      MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
  21. 18 14
      MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
  22. 2 2
      MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
  23. 5 0
      MediaBrowser.ServerApplication/ApplicationHost.cs

+ 40 - 8
MediaBrowser.Api/Dlna/DlnaServerService.cs

@@ -25,8 +25,23 @@ namespace MediaBrowser.Api.Dlna
     {
     }
 
+    [Route("/Dlna/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")]
+    [Route("/Dlna/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")]
+    public class GetConnnectionManager
+    {
+    }
+
     [Route("/Dlna/contentdirectory/{UuId}/control", "POST", Summary = "Processes a control request")]
-    public class ProcessControlRequest : IRequiresRequestStream
+    public class ProcessContentDirectoryControlRequest : IRequiresRequestStream
+    {
+        [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string UuId { get; set; }
+
+        public Stream RequestStream { get; set; }
+    }
+
+    [Route("/Dlna/connectionmanager/{UuId}/control", "POST", Summary = "Processes a control request")]
+    public class ProcessConnectionManagerControlRequest : IRequiresRequestStream
     {
         [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string UuId { get; set; }
@@ -35,6 +50,7 @@ namespace MediaBrowser.Api.Dlna
     }
 
     [Route("/Dlna/contentdirectory/{UuId}/events", Summary = "Processes an event subscription request")]
+    [Route("/Dlna/connectionmanager/{UuId}/events", Summary = "Processes an event subscription request")]
     public class ProcessEventRequest
     {
         [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
@@ -53,12 +69,14 @@ namespace MediaBrowser.Api.Dlna
         private readonly IDlnaManager _dlnaManager;
         private readonly IContentDirectory _contentDirectory;
         private readonly IEventManager _eventManager;
+        private readonly IConnectionManager _connectionManager;
 
-        public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IEventManager eventManager)
+        public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IEventManager eventManager, IConnectionManager connectionManager)
         {
             _dlnaManager = dlnaManager;
             _contentDirectory = contentDirectory;
             _eventManager = eventManager;
+            _connectionManager = connectionManager;
         }
 
         public object Get(GetDescriptionXml request)
@@ -70,26 +88,40 @@ namespace MediaBrowser.Api.Dlna
 
         public object Get(GetContentDirectory request)
         {
-            var xml = _contentDirectory.GetContentDirectoryXml(GetRequestHeaders());
+            var xml = _contentDirectory.GetServiceXml(GetRequestHeaders());
 
             return ResultFactory.GetResult(xml, "text/xml");
         }
 
-        public object Post(ProcessControlRequest request)
+        public object Get(GetConnnectionManager request)
+        {
+            var xml = _connectionManager.GetServiceXml(GetRequestHeaders());
+
+            return ResultFactory.GetResult(xml, "text/xml");
+        }
+
+        public object Post(ProcessContentDirectoryControlRequest request)
+        {
+            var response = PostAsync(request.RequestStream, _contentDirectory).Result;
+
+            return ResultFactory.GetResult(response.Xml, "text/xml");
+        }
+
+        public object Post(ProcessConnectionManagerControlRequest request)
         {
-            var response = PostAsync(request).Result;
+            var response = PostAsync(request.RequestStream, _connectionManager).Result;
 
             return ResultFactory.GetResult(response.Xml, "text/xml");
         }
 
-        private async Task<ControlResponse> PostAsync(ProcessControlRequest request)
+        private async Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
         {
             var pathInfo = PathInfo.Parse(Request.PathInfo);
             var id = pathInfo.GetArgumentValue<string>(2);
 
-            using (var reader = new StreamReader(request.RequestStream))
+            using (var reader = new StreamReader(requestStream))
             {
-                return _contentDirectory.ProcessControlRequest(new ControlRequest
+                return service.ProcessControlRequest(new ControlRequest
                 {
                     Headers = GetRequestHeaders(),
                     InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),

+ 7 - 0
MediaBrowser.Controller/Dlna/IConnectionManager.cs

@@ -0,0 +1,7 @@
+
+namespace MediaBrowser.Controller.Dlna
+{
+    public interface IConnectionManager : IUpnpService
+    {
+    }
+}

+ 2 - 16
MediaBrowser.Controller/Dlna/IContentDirectory.cs

@@ -1,21 +1,7 @@
-using System.Collections.Generic;
-
+
 namespace MediaBrowser.Controller.Dlna
 {
-    public interface IContentDirectory
+    public interface IContentDirectory : IUpnpService
     {
-        /// <summary>
-        /// Gets the content directory XML.
-        /// </summary>
-        /// <param name="headers">The headers.</param>
-        /// <returns>System.String.</returns>
-        string GetContentDirectoryXml(IDictionary<string, string> headers);
-
-        /// <summary>
-        /// Processes the control request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>ControlResponse.</returns>
-        ControlResponse ProcessControlRequest(ControlRequest request);
     }
 }

+ 21 - 0
MediaBrowser.Controller/Dlna/IUpnpService.cs

@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Dlna
+{
+    public interface IUpnpService
+    {
+        /// <summary>
+        /// Gets the content directory XML.
+        /// </summary>
+        /// <param name="headers">The headers.</param>
+        /// <returns>System.String.</returns>
+        string GetServiceXml(IDictionary<string, string> headers);
+        
+        /// <summary>
+        /// Processes the control request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>ControlResponse.</returns>
+        ControlResponse ProcessControlRequest(ControlRequest request);
+    }
+}

+ 2 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -92,9 +92,11 @@
     <Compile Include="Dlna\ControlResponse.cs" />
     <Compile Include="Dlna\DlnaIconResponse.cs" />
     <Compile Include="Dlna\EventSubscriptionResponse.cs" />
+    <Compile Include="Dlna\IConnectionManager.cs" />
     <Compile Include="Dlna\IContentDirectory.cs" />
     <Compile Include="Dlna\IDlnaManager.cs" />
     <Compile Include="Dlna\IEventManager.cs" />
+    <Compile Include="Dlna\IUpnpService.cs" />
     <Compile Include="Drawing\IImageProcessor.cs" />
     <Compile Include="Drawing\ImageFormat.cs" />
     <Compile Include="Drawing\ImageProcessingOptions.cs" />

+ 34 - 0
MediaBrowser.Dlna/ConnectionManager/ConnectionManager.cs

@@ -0,0 +1,34 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Dlna.ConnectionManager
+{
+    public class ConnectionManager : IConnectionManager
+    {
+        private readonly IDlnaManager _dlna;
+        private readonly ILogger _logger;
+        private readonly IServerConfigurationManager _config;
+
+        public ConnectionManager(IDlnaManager dlna, ILogManager logManager, IServerConfigurationManager config)
+        {
+            _dlna = dlna;
+            _config = config;
+            _logger = logManager.GetLogger("UpnpConnectionManager");
+        }
+
+        public string GetServiceXml(IDictionary<string, string> headers)
+        {
+            return new ConnectionManagerXmlBuilder().GetXml();
+        }
+
+        public ControlResponse ProcessControlRequest(ControlRequest request)
+        {
+            var profile = _dlna.GetProfile(request.Headers) ??
+                         _dlna.GetDefaultProfile();
+
+            return new ControlHandler(_logger, profile, _config).ProcessControlRequest(request);
+        }
+    }
+}

+ 106 - 0
MediaBrowser.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs

@@ -0,0 +1,106 @@
+using MediaBrowser.Dlna.Common;
+using MediaBrowser.Dlna.Service;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Dlna.ConnectionManager
+{
+    public class ConnectionManagerXmlBuilder
+    {
+        public string GetXml()
+        {
+            return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), GetStateVariables());
+        }
+
+        private IEnumerable<StateVariable> GetStateVariables()
+        {
+            var list = new List<StateVariable>();
+
+            list.Add(new StateVariable
+            {
+                Name = "SourceProtocolInfo",
+                DataType = "string",
+                SendsEvents = true
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "SinkProtocolInfo",
+                DataType = "string",
+                SendsEvents = true
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "CurrentConnectionIDs",
+                DataType = "string",
+                SendsEvents = true
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_ConnectionStatus",
+                DataType = "string",
+                SendsEvents = false,
+
+                AllowedValues = new List<string>
+                {
+                    "OK",
+                    "ContentFormatMismatch",
+                    "InsufficientBandwidth",
+                    "UnreliableChannel",
+                    "Unknown"
+                }
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_ConnectionManager",
+                DataType = "string",
+                SendsEvents = false
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_Direction",
+                DataType = "string",
+                SendsEvents = false,
+
+                AllowedValues = new List<string>
+                {
+                    "Output",
+                    "Input"
+                }
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_ProtocolInfo",
+                DataType = "string",
+                SendsEvents = false
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_ConnectionID",
+                DataType = "ui4",
+                SendsEvents = false
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_AVTransportID",
+                DataType = "ui4",
+                SendsEvents = false
+            });
+
+            list.Add(new StateVariable
+            {
+                Name = "A_ARG_TYPE_RcsID",
+                DataType = "ui4",
+                SendsEvents = false
+            });
+
+            return list;
+        }
+    }
+}

+ 30 - 0
MediaBrowser.Dlna/ConnectionManager/ControlHandler.cs

@@ -0,0 +1,30 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Dlna.Server;
+using MediaBrowser.Dlna.Service;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace MediaBrowser.Dlna.ConnectionManager
+{
+    public class ControlHandler : BaseControlHandler
+    {
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        private readonly DeviceProfile _profile;
+
+        public ControlHandler(ILogger logger, DeviceProfile profile, IServerConfigurationManager config)
+            : base(config, logger)
+        {
+            _profile = profile;
+        }
+
+        protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
+        {
+            var deviceId = "test";
+
+            throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
+        }
+    }
+}

+ 205 - 0
MediaBrowser.Dlna/ConnectionManager/ServiceActionListBuilder.cs

@@ -0,0 +1,205 @@
+using MediaBrowser.Dlna.Common;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Dlna.ConnectionManager
+{
+    public class ServiceActionListBuilder
+    {
+        public IEnumerable<ServiceAction> GetActions()
+        {
+            var list = new List<ServiceAction>
+            {
+                GetCurrentConnectionInfo(),
+                GetProtocolInfo(),
+                GetCurrentConnectionIDs(),
+                ConnectionComplete(),
+                PrepareForConnection()
+            };
+
+            return list;
+        }
+
+        private ServiceAction PrepareForConnection()
+        {
+            var action = new ServiceAction
+            {
+                Name = "PrepareForConnection"
+            };
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "RemoteProtocolInfo",
+                Direction = "in",
+                RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "PeerConnectionManager",
+                Direction = "in",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionManager"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "PeerConnectionID",
+                Direction = "in",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "Direction",
+                Direction = "in",
+                RelatedStateVariable = "A_ARG_TYPE_Direction"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "ConnectionID",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "AVTransportID",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_AVTransportID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "RcsID",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_RcsID"
+            });
+
+            return action;
+        }
+        
+        private ServiceAction GetCurrentConnectionInfo()
+        {
+            var action = new ServiceAction
+            {
+                Name = "GetCurrentConnectionInfo"
+            };
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "ConnectionID",
+                Direction = "in",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "RcsID",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_RcsID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "AVTransportID",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_AVTransportID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "ProtocolInfo",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "PeerConnectionManager",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionManager"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "PeerConnectionID",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "Direction",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_Direction"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "Status",
+                Direction = "out",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionStatus"
+            });
+
+            return action;
+        }
+
+        private ServiceAction GetProtocolInfo()
+        {
+            var action = new ServiceAction
+            {
+                Name = "GetProtocolInfo"
+            };
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "Source",
+                Direction = "out",
+                RelatedStateVariable = "SourceProtocolInfo"
+            });
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "Sink",
+                Direction = "out",
+                RelatedStateVariable = "SinkProtocolInfo"
+            });
+
+            return action;
+        }
+
+        private ServiceAction GetCurrentConnectionIDs()
+        {
+            var action = new ServiceAction
+            {
+                Name = "GetCurrentConnectionIDs"
+            };
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "ConnectionIDs",
+                Direction = "out",
+                RelatedStateVariable = "CurrentConnectionIDs"
+            });
+
+            return action;
+        }
+
+        private ServiceAction ConnectionComplete()
+        {
+            var action = new ServiceAction
+            {
+                Name = "ConnectionComplete"
+            };
+
+            action.ArgumentList.Add(new Argument
+            {
+                Name = "ConnectionID",
+                Direction = "in",
+                RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
+            });
+
+            return action;
+        }
+    }
+}

+ 4 - 7
MediaBrowser.Dlna/Server/ContentDirectory.cs → MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs

@@ -10,7 +10,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 
-namespace MediaBrowser.Dlna.Server
+namespace MediaBrowser.Dlna.ContentDirectory
 {
     public class ContentDirectory : IContentDirectory, IDisposable
     {
@@ -43,7 +43,7 @@ namespace MediaBrowser.Dlna.Server
             _config = config;
             _userManager = userManager;
             _eventManager = eventManager;
-            _logger = logManager.GetLogger("DlnaContentDirectory");
+            _logger = logManager.GetLogger("UpnpContentDirectory");
         }
 
         private int SystemUpdateId
@@ -56,12 +56,9 @@ namespace MediaBrowser.Dlna.Server
             }
         }
 
-        public string GetContentDirectoryXml(IDictionary<string, string> headers)
+        public string GetServiceXml(IDictionary<string, string> headers)
         {
-            var profile = _dlna.GetProfile(headers) ??
-                          _dlna.GetDefaultProfile();
-
-            return new ContentDirectoryXmlBuilder(profile).GetXml();
+            return new ContentDirectoryXmlBuilder().GetXml();
         }
 
         public ControlResponse ProcessControlRequest(ControlRequest request)

+ 5 - 93
MediaBrowser.Dlna/Server/ContentDirectoryXmlBuilder.cs → MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs

@@ -1,98 +1,15 @@
 using MediaBrowser.Dlna.Common;
-using MediaBrowser.Model.Dlna;
+using MediaBrowser.Dlna.Service;
 using System.Collections.Generic;
-using System.Security;
-using System.Text;
 
-namespace MediaBrowser.Dlna.Server
+namespace MediaBrowser.Dlna.ContentDirectory
 {
     public class ContentDirectoryXmlBuilder
     {
-        private readonly DeviceProfile _profile;
-
-        public ContentDirectoryXmlBuilder(DeviceProfile profile)
-        {
-            _profile = profile;
-        }
-
         public string GetXml()
         {
-            var builder = new StringBuilder();
-
-            builder.Append("<?xml version=\"1.0\"?>");
-            builder.Append("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">");
-
-            builder.Append("<specVersion>");
-            builder.Append("<major>1</major>");
-            builder.Append("<minor>0</minor>");
-            builder.Append("</specVersion>");
-
-            AppendActionList(builder);
-            AppendServiceStateTable(builder);
-
-            builder.Append("</scpd>");
-
-            return builder.ToString();
-        }
-
-        private void AppendActionList(StringBuilder builder)
-        {
-            builder.Append("<actionList>");
-
-            foreach (var item in new ServiceActionListBuilder().GetActions())
-            {
-                builder.Append("<action>");
-
-                builder.Append("<name>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</name>");
-
-                builder.Append("<argumentList>");
-
-                foreach (var argument in item.ArgumentList)
-                {
-                    builder.Append("<argument>");
-
-                    builder.Append("<name>" + SecurityElement.Escape(argument.Name ?? string.Empty) + "</name>");
-                    builder.Append("<direction>" + SecurityElement.Escape(argument.Direction ?? string.Empty) + "</direction>");
-                    builder.Append("<relatedStateVariable>" + SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>");
-                    
-                    builder.Append("</argument>");
-                }
-
-                builder.Append("</argumentList>");
-                
-                builder.Append("</action>");
-            }
-            
-            builder.Append("</actionList>");
-        }
-
-        private void AppendServiceStateTable(StringBuilder builder)
-        {
-            builder.Append("<serviceStateTable>");
-
-            foreach (var item in GetStateVariables())
-            {
-                var sendEvents = item.SendsEvents ? "yes" : "no";
-
-                builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">");
-
-                builder.Append("<name>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</name>");
-                builder.Append("<dataType>" + SecurityElement.Escape(item.DataType ?? string.Empty) + "</dataType>");
-
-                if (item.AllowedValues.Count > 0)
-                {
-                    builder.Append("<allowedValueList>");
-                    foreach (var allowedValue in item.AllowedValues)
-                    {
-                        builder.Append("<allowedValue>" + SecurityElement.Escape(allowedValue) + "</allowedValue>");
-                    }
-                    builder.Append("</allowedValueList>");
-                }
-
-                builder.Append("</stateVariable>");
-            }
-
-            builder.Append("</serviceStateTable>");
+            return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), 
+                GetStateVariables());
         }
 
         private IEnumerable<StateVariable> GetStateVariables()
@@ -223,13 +140,8 @@ namespace MediaBrowser.Dlna.Server
                 DataType = "string",
                 SendsEvents = false
             });
-            
-            return list;
-        }
 
-        public override string ToString()
-        {
-            return GetXml();
+            return list;
         }
     }
 }

+ 24 - 132
MediaBrowser.Dlna/Server/ControlHandler.cs → MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

@@ -1,15 +1,16 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Dlna.Didl;
+using MediaBrowser.Dlna.Server;
+using MediaBrowser.Dlna.Service;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
@@ -21,20 +22,17 @@ using System.Text;
 using System.Threading;
 using System.Xml;
 
-namespace MediaBrowser.Dlna.Server
+namespace MediaBrowser.Dlna.ContentDirectory
 {
-    public class ControlHandler
+    public class ControlHandler : BaseControlHandler
     {
-        private readonly ILogger _logger;
         private readonly ILibraryManager _libraryManager;
         private readonly IUserDataManager _userDataManager;
-        private readonly IServerConfigurationManager _config;
         private readonly User _user;
 
         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/";
         private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
-        private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
         private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
 
         private readonly int _systemUpdateId;
@@ -45,151 +43,45 @@ namespace MediaBrowser.Dlna.Server
         private readonly DeviceProfile _profile;
 
         public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config)
+            : base(config, logger)
         {
-            _logger = logger;
             _libraryManager = libraryManager;
             _userDataManager = userDataManager;
             _user = user;
             _systemUpdateId = systemUpdateId;
-            _config = config;
             _profile = profile;
 
             _didlBuilder = new DidlBuilder(profile, imageProcessor, serverAddress, dtoService);
         }
 
-        public ControlResponse ProcessControlRequest(ControlRequest request)
-        {
-            try
-            {
-                if (_config.Configuration.DlnaOptions.EnableDebugLogging)
-                {
-                    LogRequest(request);
-                }
-
-                return ProcessControlRequestInternal(request);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error processing control request", ex);
-
-                return GetErrorResponse(ex);
-            }
-        }
-
-        private void LogRequest(ControlRequest request)
-        {
-            var builder = new StringBuilder();
-
-            var headers = string.Join(", ", request.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
-            builder.AppendFormat("Headers: {0}", headers);
-            builder.AppendLine();
-            builder.Append(request.InputXml);
-
-            _logger.LogMultiline("Control request", LogSeverity.Debug, builder);
-        }
-
-        private ControlResponse ProcessControlRequestInternal(ControlRequest request)
+        protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
         {
-            var soap = new XmlDocument();
-            soap.LoadXml(request.InputXml);
-            var sparams = new Headers();
-            var body = soap.GetElementsByTagName("Body", NS_SOAPENV).Item(0);
-
-            var method = body.FirstChild;
-
-            foreach (var p in method.ChildNodes)
-            {
-                var e = p as XmlElement;
-                if (e == null)
-                {
-                    continue;
-                }
-                sparams.Add(e.LocalName, e.InnerText.Trim());
-            }
-
             var deviceId = "test";
 
-            IEnumerable<KeyValuePair<string, string>> result;
-
-            _logger.Debug("Received control request {0}", method.Name);
-
             var user = _user;
 
-            if (string.Equals(method.LocalName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
-                result = HandleGetSearchCapabilities();
-            else if (string.Equals(method.LocalName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
-                result = HandleGetSortCapabilities();
-            else if (string.Equals(method.LocalName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
-                result = HandleGetSystemUpdateID();
-            else if (string.Equals(method.LocalName, "Browse", StringComparison.OrdinalIgnoreCase))
-                result = HandleBrowse(sparams, user, deviceId);
-            else if (string.Equals(method.LocalName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
-                result = HandleXGetFeatureList();
-            else if (string.Equals(method.LocalName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
-                result = HandleXSetBookmark(sparams, user);
-            else if (string.Equals(method.LocalName, "Search", StringComparison.OrdinalIgnoreCase))
-                result = HandleSearch(sparams, user, deviceId);
-            else
-                throw new ResourceNotFoundException("Unexpected control request name: " + method.LocalName);
+            if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
+                return HandleGetSearchCapabilities();
 
-            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/");
+            if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
+                return HandleGetSortCapabilities();
 
-            var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
-            env.DocumentElement.AppendChild(rbody);
+            if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
+                return HandleGetSystemUpdateID();
 
-            var response = env.CreateElement(String.Format("u:{0}Response", method.LocalName), method.NamespaceURI);
-            rbody.AppendChild(response);
+            if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
+                return HandleBrowse(methodParams, user, deviceId);
 
-            foreach (var i in result)
-            {
-                var ri = env.CreateElement(i.Key);
-                ri.InnerText = i.Value;
-                response.AppendChild(ri);
-            }
-
-            var controlResponse = new ControlResponse
-            {
-                Xml = env.OuterXml,
-                IsSuccessful = true
-            };
+            if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
+                return HandleXGetFeatureList();
 
-            controlResponse.Headers.Add("EXT", string.Empty);
+            if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
+                return HandleXSetBookmark(methodParams, user);
 
-            return controlResponse;
-        }
+            if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
+                return HandleSearch(methodParams, user, deviceId);
 
-        private ControlResponse GetErrorResponse(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 rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
-            env.DocumentElement.AppendChild(rbody);
-
-            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 = "<detail><UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\"><errorCode>401</errorCode><errorDescription>Invalid Action</errorDescription></UPnPError></detail>";
-            fault.AppendChild(detail);
-            rbody.AppendChild(fault);
-
-            return new ControlResponse
-            {
-                Xml = env.OuterXml,
-                IsSuccessful = false
-            };
+            throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
         }
 
         private IEnumerable<KeyValuePair<string, string>> HandleXSetBookmark(IDictionary<string, string> sparams, User user)
@@ -224,7 +116,7 @@ namespace MediaBrowser.Dlna.Server
         {
             var headers = new Headers(true);
             headers.Add("Id", _systemUpdateId.ToString(_usCulture));
-            return headers;           
+            return headers;
         }
 
         private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()

+ 1 - 1
MediaBrowser.Dlna/Server/ServiceActionListBuilder.cs → MediaBrowser.Dlna/ContentDirectory/ServiceActionListBuilder.cs

@@ -1,7 +1,7 @@
 using MediaBrowser.Dlna.Common;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna.Server
+namespace MediaBrowser.Dlna.ContentDirectory
 {
     public class ServiceActionListBuilder
     {

+ 8 - 3
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -345,7 +345,9 @@ namespace MediaBrowser.Dlna.Didl
         /// <param name="filter">The filter.</param>
         private void AddCommonFields(BaseItem item, XmlElement element, Filter filter)
         {
-            if (filter.Contains("dc:title"))
+            // 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", item.Name, NS_DC);
             }
@@ -360,9 +362,12 @@ namespace MediaBrowser.Dlna.Didl
                 }
             }
 
-            foreach (var genre in item.Genres)
+            if (filter.Contains("upnp:genre"))
             {
-                AddValue(element, "upnp", "genre", genre, NS_UPNP);
+                foreach (var genre in item.Genres)
+                {
+                    AddValue(element, "upnp", "genre", genre, NS_UPNP);
+                }
             }
 
             foreach (var studio in item.Studios)

+ 12 - 4
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

@@ -51,6 +51,10 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="ConnectionManager\ConnectionManager.cs" />
+    <Compile Include="ConnectionManager\ConnectionManagerXmlBuilder.cs" />
+    <Compile Include="ConnectionManager\ControlHandler.cs" />
+    <Compile Include="ConnectionManager\ServiceActionListBuilder.cs" />
     <Compile Include="DlnaManager.cs" />
     <Compile Include="Common\Argument.cs" />
     <Compile Include="Eventing\EventManager.cs" />
@@ -79,10 +83,13 @@
     <Compile Include="Profiles\Windows81Profile.cs" />
     <Compile Include="Profiles\WindowsMediaCenterProfile.cs" />
     <Compile Include="Profiles\WindowsPhoneProfile.cs" />
-    <Compile Include="Server\ContentDirectory.cs" />
-    <Compile Include="Server\ControlHandler.cs" />
-    <Compile Include="Server\ServiceActionListBuilder.cs" />
-    <Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
+    <Compile Include="ContentDirectory\ContentDirectory.cs" />
+    <Compile Include="ContentDirectory\ControlHandler.cs" />
+    <Compile Include="ContentDirectory\ServiceActionListBuilder.cs" />
+    <Compile Include="ContentDirectory\ContentDirectoryXmlBuilder.cs" />
+    <Compile Include="Service\BaseControlHandler.cs" />
+    <Compile Include="Service\ControlErrorHandler.cs" />
+    <Compile Include="Service\ServiceXmlBuilder.cs" />
     <Compile Include="Ssdp\Datagram.cs" />
     <Compile Include="Server\DescriptionXmlBuilder.cs" />
     <Compile Include="Ssdp\SsdpHelper.cs" />
@@ -159,6 +166,7 @@
     <EmbeddedResource Include="Images\logo48.jpg" />
     <EmbeddedResource Include="Images\logo48.png" />
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 9 - 0
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs

@@ -185,6 +185,15 @@ namespace MediaBrowser.Dlna.Server
                 EventSubUrl = "/mediabrowser/dlna/contentdirectory/" + _serverUdn + "/events"
             });
 
+            list.Add(new DeviceService
+            {
+                ServiceType = "urn:schemas-upnp-org:service:ConnectionManager:1",
+                ServiceId = "urn:upnp-org:serviceId:ConnectionManager",
+                ScpdUrl = "/mediabrowser/dlna/connectionmanager/connectionmanager.xml",
+                ControlUrl = "/mediabrowser/dlna/connectionmanager/" + _serverUdn + "/control",
+                EventSubUrl = "/mediabrowser/dlna/connectionmanager/" + _serverUdn + "/events"
+            });
+
             return list;
         }
 

+ 112 - 0
MediaBrowser.Dlna/Service/BaseControlHandler.cs

@@ -0,0 +1,112 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Dlna.Server;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml;
+
+namespace MediaBrowser.Dlna.Service
+{
+    public abstract class BaseControlHandler
+    {
+        private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
+        
+        protected readonly IServerConfigurationManager Config;
+        protected readonly ILogger Logger;
+
+        protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
+        {
+            Config = config;
+            Logger = logger;
+        }
+
+        public ControlResponse ProcessControlRequest(ControlRequest request)
+        {
+            try
+            {
+                if (Config.Configuration.DlnaOptions.EnableDebugLogging)
+                {
+                    LogRequest(request);
+                }
+
+                return ProcessControlRequestInternal(request);
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error processing control request", ex);
+
+                return new ControlErrorHandler().GetResponse(ex);
+            }
+        }
+
+        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);
+
+            var method = body.FirstChild;
+
+            foreach (var p in method.ChildNodes)
+            {
+                var e = p as XmlElement;
+                if (e == null)
+                {
+                    continue;
+                }
+                sparams.Add(e.LocalName, e.InnerText.Trim());
+            }
+
+            Logger.Debug("Received control request {0}", method.LocalName);
+
+            IEnumerable<KeyValuePair<string, string>> result = GetResult(method.LocalName, sparams);
+
+            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 ri = env.CreateElement(i.Key);
+                ri.InnerText = i.Value;
+                response.AppendChild(ri);
+            }
+
+            var controlResponse = new ControlResponse
+            {
+                Xml = env.OuterXml,
+                IsSuccessful = true
+            };
+
+            controlResponse.Headers.Add("EXT", string.Empty);
+
+            return controlResponse;
+        }
+
+        protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams);
+
+        private void LogRequest(ControlRequest request)
+        {
+            var builder = new StringBuilder();
+
+            var headers = string.Join(", ", request.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
+            builder.AppendFormat("Headers: {0}", headers);
+            builder.AppendLine();
+            builder.Append(request.InputXml);
+
+            Logger.LogMultiline("Control request", LogSeverity.Debug, builder);
+        }
+    }
+}

+ 41 - 0
MediaBrowser.Dlna/Service/ControlErrorHandler.cs

@@ -0,0 +1,41 @@
+using MediaBrowser.Controller.Dlna;
+using System;
+using System.Xml;
+
+namespace MediaBrowser.Dlna.Service
+{
+    public class ControlErrorHandler
+    {
+        private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
+        
+        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 rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
+            env.DocumentElement.AppendChild(rbody);
+
+            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 = "<detail><UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\"><errorCode>401</errorCode><errorDescription>Invalid Action</errorDescription></UPnPError></detail>";
+            fault.AppendChild(detail);
+            rbody.AppendChild(fault);
+
+            return new ControlResponse
+            {
+                Xml = env.OuterXml,
+                IsSuccessful = false
+            };
+        }
+    }
+}

+ 90 - 0
MediaBrowser.Dlna/Service/ServiceXmlBuilder.cs

@@ -0,0 +1,90 @@
+using MediaBrowser.Dlna.Common;
+using System.Collections.Generic;
+using System.Security;
+using System.Text;
+
+namespace MediaBrowser.Dlna.Service
+{
+    public class ServiceXmlBuilder
+    {
+        public string GetXml(IEnumerable<ServiceAction> actions, IEnumerable<StateVariable> stateVariables)
+        {
+            var builder = new StringBuilder();
+
+            builder.Append("<?xml version=\"1.0\"?>");
+            builder.Append("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">");
+
+            builder.Append("<specVersion>");
+            builder.Append("<major>1</major>");
+            builder.Append("<minor>0</minor>");
+            builder.Append("</specVersion>");
+
+            AppendActionList(builder, actions);
+            AppendServiceStateTable(builder, stateVariables);
+
+            builder.Append("</scpd>");
+
+            return builder.ToString();
+        }
+
+        private void AppendActionList(StringBuilder builder, IEnumerable<ServiceAction> actions)
+        {
+            builder.Append("<actionList>");
+
+            foreach (var item in actions)
+            {
+                builder.Append("<action>");
+
+                builder.Append("<name>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</name>");
+
+                builder.Append("<argumentList>");
+
+                foreach (var argument in item.ArgumentList)
+                {
+                    builder.Append("<argument>");
+
+                    builder.Append("<name>" + SecurityElement.Escape(argument.Name ?? string.Empty) + "</name>");
+                    builder.Append("<direction>" + SecurityElement.Escape(argument.Direction ?? string.Empty) + "</direction>");
+                    builder.Append("<relatedStateVariable>" + SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>");
+
+                    builder.Append("</argument>");
+                }
+
+                builder.Append("</argumentList>");
+
+                builder.Append("</action>");
+            }
+
+            builder.Append("</actionList>");
+        }
+
+        private void AppendServiceStateTable(StringBuilder builder, IEnumerable<StateVariable> stateVariables)
+        {
+            builder.Append("<serviceStateTable>");
+
+            foreach (var item in stateVariables)
+            {
+                var sendEvents = item.SendsEvents ? "yes" : "no";
+
+                builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">");
+
+                builder.Append("<name>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</name>");
+                builder.Append("<dataType>" + SecurityElement.Escape(item.DataType ?? string.Empty) + "</dataType>");
+
+                if (item.AllowedValues.Count > 0)
+                {
+                    builder.Append("<allowedValueList>");
+                    foreach (var allowedValue in item.AllowedValues)
+                    {
+                        builder.Append("<allowedValue>" + SecurityElement.Escape(allowedValue) + "</allowedValue>");
+                    }
+                    builder.Append("</allowedValueList>");
+                }
+
+                builder.Append("</stateVariable>");
+            }
+
+            builder.Append("</serviceStateTable>");
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -267,7 +267,7 @@ namespace MediaBrowser.Server.Implementations.Channels
             {
                 providerStartIndex = query.StartIndex;
 
-                if (!query.Limit.HasValue || query.Limit.Value > channelInfo.MaxPageSize.Value)
+                if (query.Limit.HasValue && query.Limit.Value > channelInfo.MaxPageSize.Value)
                 {
                     throw new ArgumentException(string.Format("Channel {0} only supports a maximum of {1} records at a time.", channel.Name, channelInfo.MaxPageSize.Value));
                 }

+ 18 - 14
MediaBrowser.Server.Implementations/Session/HttpSessionController.cs

@@ -63,17 +63,18 @@ namespace MediaBrowser.Server.Implementations.Session
 
         private Task SendMessage(string name, CancellationToken cancellationToken)
         {
-            return SendMessage(name, new NameValueCollection(), cancellationToken);
+            return SendMessage(name, new Dictionary<string, string>(), cancellationToken);
         }
 
-        private Task SendMessage(string name, NameValueCollection args, CancellationToken cancellationToken)
+        private Task SendMessage(string name, Dictionary<string, string> args, CancellationToken cancellationToken)
         {
-            return SendMessage(new WebSocketMessage<string>
-            {
-                MessageType = name,
-                Data = string.Empty
+            var url = _postUrl + "/" + name + ToQueryString(args);
 
-            }, cancellationToken);
+            return _httpClient.Post(new HttpRequestOptions
+            {
+                Url = url,
+                CancellationToken = cancellationToken
+            });
         }
 
         public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
@@ -141,12 +142,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
         public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
         {
-            return SendMessage(new WebSocketMessage<GeneralCommand>
-            {
-                MessageType = "GeneralCommand",
-                Data = command
-
-            }, cancellationToken);
+            return SendMessage(command.Name, command.Arguments, cancellationToken);
         }
 
         private string ToQueryString(Dictionary<string, string> nvc)
@@ -154,7 +150,15 @@ namespace MediaBrowser.Server.Implementations.Session
             var array = (from item in nvc
                          select string.Format("{0}={1}", WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)))
                 .ToArray();
-            return "?" + string.Join("&", array);
+
+            var args = string.Join("&", array);
+
+            if (string.IsNullOrEmpty(args))
+            {
+                return args;
+            }
+
+            return "?" + args;
         }
     }
 }

+ 2 - 2
MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs

@@ -154,9 +154,9 @@ namespace MediaBrowser.Server.Implementations.WebSocket
             {
                 if (WebSocketServer != null)
                 {
-                    // Calling dispose will also call stop
                     _logger.Debug("Disposing alchemy server");
-                    WebSocketServer.Stop();
+
+                    WebSocketServer.Dispose();
                     WebSocketServer = null;
                 }
             }

+ 5 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -35,6 +35,8 @@ using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Controller.Themes;
 using MediaBrowser.Dlna;
+using MediaBrowser.Dlna.ConnectionManager;
+using MediaBrowser.Dlna.ContentDirectory;
 using MediaBrowser.Dlna.Eventing;
 using MediaBrowser.Dlna.Main;
 using MediaBrowser.Dlna.Server;
@@ -526,6 +528,9 @@ namespace MediaBrowser.ServerApplication
             var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, DtoService, LibraryManager, LogManager, ServerConfigurationManager, UserManager, dlnaEventManager);
             RegisterSingleInstance<IContentDirectory>(contentDirectory);
 
+            var connectionManager = new ConnectionManager(dlnaManager, LogManager, ServerConfigurationManager);
+            RegisterSingleInstance<IConnectionManager>(connectionManager);
+
             var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
             RegisterSingleInstance<ICollectionManager>(collectionManager);