From 1774e5b1ac9f809fd97c1d95666fc563afa87914 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 20 May 2014 20:56:24 -0400 Subject: added upnp ConnectionManager.cs --- MediaBrowser.Api/Dlna/DlnaServerService.cs | 48 +- MediaBrowser.Controller/Dlna/IConnectionManager.cs | 7 + MediaBrowser.Controller/Dlna/IContentDirectory.cs | 18 +- MediaBrowser.Controller/Dlna/IUpnpService.cs | 21 + .../MediaBrowser.Controller.csproj | 2 + .../ConnectionManager/ConnectionManager.cs | 34 ++ .../ConnectionManagerXmlBuilder.cs | 106 +++++ .../ConnectionManager/ControlHandler.cs | 30 ++ .../ConnectionManager/ServiceActionListBuilder.cs | 205 +++++++++ .../ContentDirectory/ContentDirectory.cs | 118 +++++ .../ContentDirectory/ContentDirectoryXmlBuilder.cs | 147 ++++++ .../ContentDirectory/ControlHandler.cs | 393 ++++++++++++++++ .../ContentDirectory/ServiceActionListBuilder.cs | 378 ++++++++++++++++ MediaBrowser.Dlna/Didl/DidlBuilder.cs | 11 +- MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 16 +- MediaBrowser.Dlna/Server/ContentDirectory.cs | 121 ----- .../Server/ContentDirectoryXmlBuilder.cs | 235 ---------- MediaBrowser.Dlna/Server/ControlHandler.cs | 501 --------------------- MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs | 9 + .../Server/ServiceActionListBuilder.cs | 378 ---------------- MediaBrowser.Dlna/Service/BaseControlHandler.cs | 112 +++++ MediaBrowser.Dlna/Service/ControlErrorHandler.cs | 41 ++ MediaBrowser.Dlna/Service/ServiceXmlBuilder.cs | 90 ++++ .../Channels/ChannelManager.cs | 2 +- .../Session/HttpSessionController.cs | 32 +- .../WebSocket/AlchemyServer.cs | 4 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 5 + 27 files changed, 1781 insertions(+), 1283 deletions(-) create mode 100644 MediaBrowser.Controller/Dlna/IConnectionManager.cs create mode 100644 MediaBrowser.Controller/Dlna/IUpnpService.cs create mode 100644 MediaBrowser.Dlna/ConnectionManager/ConnectionManager.cs create mode 100644 MediaBrowser.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs create mode 100644 MediaBrowser.Dlna/ConnectionManager/ControlHandler.cs create mode 100644 MediaBrowser.Dlna/ConnectionManager/ServiceActionListBuilder.cs create mode 100644 MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs create mode 100644 MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs create mode 100644 MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs create mode 100644 MediaBrowser.Dlna/ContentDirectory/ServiceActionListBuilder.cs delete mode 100644 MediaBrowser.Dlna/Server/ContentDirectory.cs delete mode 100644 MediaBrowser.Dlna/Server/ContentDirectoryXmlBuilder.cs delete mode 100644 MediaBrowser.Dlna/Server/ControlHandler.cs delete mode 100644 MediaBrowser.Dlna/Server/ServiceActionListBuilder.cs create mode 100644 MediaBrowser.Dlna/Service/BaseControlHandler.cs create mode 100644 MediaBrowser.Dlna/Service/ControlErrorHandler.cs create mode 100644 MediaBrowser.Dlna/Service/ServiceXmlBuilder.cs diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 05d41c18bc..8e4e559df2 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/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 PostAsync(ProcessControlRequest request) + private async Task PostAsync(Stream requestStream, IUpnpService service) { var pathInfo = PathInfo.Parse(Request.PathInfo); var id = pathInfo.GetArgumentValue(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), diff --git a/MediaBrowser.Controller/Dlna/IConnectionManager.cs b/MediaBrowser.Controller/Dlna/IConnectionManager.cs new file mode 100644 index 0000000000..942e523e88 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/IConnectionManager.cs @@ -0,0 +1,7 @@ + +namespace MediaBrowser.Controller.Dlna +{ + public interface IConnectionManager : IUpnpService + { + } +} diff --git a/MediaBrowser.Controller/Dlna/IContentDirectory.cs b/MediaBrowser.Controller/Dlna/IContentDirectory.cs index e48d498dfe..00c236813b 100644 --- a/MediaBrowser.Controller/Dlna/IContentDirectory.cs +++ b/MediaBrowser.Controller/Dlna/IContentDirectory.cs @@ -1,21 +1,7 @@ -using System.Collections.Generic; - + namespace MediaBrowser.Controller.Dlna { - public interface IContentDirectory + public interface IContentDirectory : IUpnpService { - /// - /// Gets the content directory XML. - /// - /// The headers. - /// System.String. - string GetContentDirectoryXml(IDictionary headers); - - /// - /// Processes the control request. - /// - /// The request. - /// ControlResponse. - ControlResponse ProcessControlRequest(ControlRequest request); } } diff --git a/MediaBrowser.Controller/Dlna/IUpnpService.cs b/MediaBrowser.Controller/Dlna/IUpnpService.cs new file mode 100644 index 0000000000..3511740ad2 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/IUpnpService.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Dlna +{ + public interface IUpnpService + { + /// + /// Gets the content directory XML. + /// + /// The headers. + /// System.String. + string GetServiceXml(IDictionary headers); + + /// + /// Processes the control request. + /// + /// The request. + /// ControlResponse. + ControlResponse ProcessControlRequest(ControlRequest request); + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index bab550e451..fa7842a684 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -92,9 +92,11 @@ + + diff --git a/MediaBrowser.Dlna/ConnectionManager/ConnectionManager.cs b/MediaBrowser.Dlna/ConnectionManager/ConnectionManager.cs new file mode 100644 index 0000000000..3270fca72c --- /dev/null +++ b/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 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); + } + } +} diff --git a/MediaBrowser.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/MediaBrowser.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs new file mode 100644 index 0000000000..4efa111591 --- /dev/null +++ b/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 GetStateVariables() + { + var list = new List(); + + 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 + { + "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 + { + "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; + } + } +} diff --git a/MediaBrowser.Dlna/ConnectionManager/ControlHandler.cs b/MediaBrowser.Dlna/ConnectionManager/ControlHandler.cs new file mode 100644 index 0000000000..842e9380ab --- /dev/null +++ b/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> GetResult(string methodName, Headers methodParams) + { + var deviceId = "test"; + + throw new ResourceNotFoundException("Unexpected control request name: " + methodName); + } + } +} diff --git a/MediaBrowser.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/MediaBrowser.Dlna/ConnectionManager/ServiceActionListBuilder.cs new file mode 100644 index 0000000000..9dbd4e0e23 --- /dev/null +++ b/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 GetActions() + { + var list = new List + { + 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; + } + } +} diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs new file mode 100644 index 0000000000..d1b29502d7 --- /dev/null +++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs @@ -0,0 +1,118 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Dlna.ContentDirectory +{ + public class ContentDirectory : IContentDirectory, IDisposable + { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + private readonly IUserDataManager _userDataManager; + private readonly IDlnaManager _dlna; + private readonly IServerConfigurationManager _config; + private readonly IUserManager _userManager; + + private readonly IEventManager _eventManager; + + public ContentDirectory(IDlnaManager dlna, + IUserDataManager userDataManager, + IImageProcessor imageProcessor, + IDtoService dtoService, + ILibraryManager libraryManager, + ILogManager logManager, + IServerConfigurationManager config, + IUserManager userManager, + IEventManager eventManager) + { + _dlna = dlna; + _userDataManager = userDataManager; + _imageProcessor = imageProcessor; + _dtoService = dtoService; + _libraryManager = libraryManager; + _config = config; + _userManager = userManager; + _eventManager = eventManager; + _logger = logManager.GetLogger("UpnpContentDirectory"); + } + + private int SystemUpdateId + { + get + { + var now = DateTime.UtcNow; + + return now.Year + now.DayOfYear + now.Hour; + } + } + + public string GetServiceXml(IDictionary headers) + { + return new ContentDirectoryXmlBuilder().GetXml(); + } + + public ControlResponse ProcessControlRequest(ControlRequest request) + { + var profile = _dlna.GetProfile(request.Headers) ?? + _dlna.GetDefaultProfile(); + + var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); + + var user = GetUser(profile); + + return new ControlHandler( + _logger, + _libraryManager, + profile, + serverAddress, + _dtoService, + _imageProcessor, + _userDataManager, + user, + SystemUpdateId, + _config) + .ProcessControlRequest(request); + } + + private User GetUser(DeviceProfile profile) + { + if (!string.IsNullOrEmpty(profile.UserId)) + { + var user = _userManager.GetUserById(new Guid(profile.UserId)); + + if (user != null) + { + return user; + } + } + + if (!string.IsNullOrEmpty(_config.Configuration.DlnaOptions.DefaultUserId)) + { + var user = _userManager.GetUserById(new Guid(_config.Configuration.DlnaOptions.DefaultUserId)); + + if (user != null) + { + return user; + } + } + + // No configuration so it's going to be pretty arbitrary + return _userManager.Users.First(); + } + + public void Dispose() + { + + } + } +} diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs new file mode 100644 index 0000000000..0e5a2671c9 --- /dev/null +++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs @@ -0,0 +1,147 @@ +using MediaBrowser.Dlna.Common; +using MediaBrowser.Dlna.Service; +using System.Collections.Generic; + +namespace MediaBrowser.Dlna.ContentDirectory +{ + public class ContentDirectoryXmlBuilder + { + public string GetXml() + { + return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), + GetStateVariables()); + } + + private IEnumerable GetStateVariables() + { + var list = new List(); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_Filter", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_SortCriteria", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_Index", + DataType = "ui4", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_Count", + DataType = "ui4", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_UpdateID", + DataType = "ui4", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "SearchCapabilities", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "SortCapabilities", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "SystemUpdateID", + DataType = "ui4", + SendsEvents = true + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_SearchCriteria", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_Result", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_ObjectID", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_BrowseFlag", + DataType = "string", + SendsEvents = false, + + AllowedValues = new List + { + "BrowseMetadata", + "BrowseDirectChildren" + } + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_BrowseLetter", + DataType = "string", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_CategoryType", + DataType = "ui4", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_RID", + DataType = "ui4", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_PosSec", + DataType = "ui4", + SendsEvents = false + }); + + list.Add(new StateVariable + { + Name = "A_ARG_TYPE_Featurelist", + DataType = "string", + SendsEvents = false + }); + + return list; + } + } +} diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs new file mode 100644 index 0000000000..0e4f0d8f92 --- /dev/null +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -0,0 +1,393 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +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.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Xml; + +namespace MediaBrowser.Dlna.ContentDirectory +{ + public class ControlHandler : BaseControlHandler + { + private readonly ILibraryManager _libraryManager; + private readonly IUserDataManager _userDataManager; + 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_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; + + private readonly int _systemUpdateId; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + private readonly DidlBuilder _didlBuilder; + + 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) + { + _libraryManager = libraryManager; + _userDataManager = userDataManager; + _user = user; + _systemUpdateId = systemUpdateId; + _profile = profile; + + _didlBuilder = new DidlBuilder(profile, imageProcessor, serverAddress, dtoService); + } + + protected override IEnumerable> GetResult(string methodName, Headers methodParams) + { + var deviceId = "test"; + + var user = _user; + + if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase)) + return HandleGetSearchCapabilities(); + + if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase)) + return HandleGetSortCapabilities(); + + if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase)) + return HandleGetSystemUpdateID(); + + if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase)) + return HandleBrowse(methodParams, user, deviceId); + + if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase)) + return HandleXGetFeatureList(); + + if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase)) + return HandleXSetBookmark(methodParams, user); + + if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase)) + return HandleSearch(methodParams, user, deviceId); + + throw new ResourceNotFoundException("Unexpected control request name: " + methodName); + } + + private IEnumerable> HandleXSetBookmark(IDictionary sparams, User user) + { + var id = sparams["ObjectID"]; + + var item = GetItemFromObjectId(id, user); + + var newbookmark = int.Parse(sparams["PosSecond"], _usCulture); + + var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + + userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; + + _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, + CancellationToken.None); + + return new Headers(); + } + + private IEnumerable> HandleGetSearchCapabilities() + { + return new Headers { { "SearchCaps", string.Empty } }; + } + + private IEnumerable> HandleGetSortCapabilities() + { + return new Headers { { "SortCaps", string.Empty } }; + } + + private IEnumerable> HandleGetSystemUpdateID() + { + var headers = new Headers(true); + headers.Add("Id", _systemUpdateId.ToString(_usCulture)); + return headers; + } + + private IEnumerable> HandleXGetFeatureList() + { + return new Headers { { "FeatureList", GetFeatureListXml() } }; + } + + private string GetFeatureListXml() + { + var builder = new StringBuilder(); + + builder.Append(""); + builder.Append(""); + + builder.Append(""); + builder.Append(""); + builder.Append(""); + builder.Append(""); + builder.Append(""); + + builder.Append(""); + + return builder.ToString(); + } + + private IEnumerable> HandleBrowse(Headers sparams, User user, string deviceId) + { + var id = sparams["ObjectID"]; + var flag = sparams["BrowseFlag"]; + var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); + + var provided = 0; + var requested = 0; + var start = 0; + + if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0) + { + requested = 0; + } + if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0) + { + start = 0; + } + + //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); + //didl.SetAttribute("xmlns:sec", NS_SEC); + result.AppendChild(didl); + + var folder = (Folder)GetItemFromObjectId(id, user); + + var children = GetChildrenSorted(folder, user, sortCriteria).ToList(); + + var totalCount = children.Count; + + if (string.Equals(flag, "BrowseMetadata")) + { + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, children.Count, filter)); + provided++; + } + else + { + if (start > 0) + { + children = children.Skip(start).ToList(); + } + if (requested > 0) + { + children = children.Take(requested).ToList(); + } + + provided = children.Count; + + foreach (var i in children) + { + if (i.IsFolder) + { + var f = (Folder)i; + var childCount = GetChildrenSorted(f, user, sortCriteria).Count(); + + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); + } + else + { + result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, deviceId, filter)); + } + } + } + + var resXML = result.OuterXml; + + 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)) + }; + } + + private IEnumerable> HandleSearch(Headers sparams, User user, string deviceId) + { + var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); + var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); + var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + + // sort example: dc:title, dc:date + + var requested = 0; + var start = 0; + + if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0) + { + requested = 0; + } + if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0) + { + start = 0; + } + + //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) + { + didl.SetAttribute(att.Name, att.Value); + } + + result.AppendChild(didl); + + var folder = (Folder)GetItemFromObjectId(sparams["ContainerID"], user); + + var children = GetChildrenSorted(folder, user, searchCriteria, sortCriteria).ToList(); + + var totalCount = children.Count; + + if (start > 0) + { + children = children.Skip(start).ToList(); + } + if (requested > 0) + { + children = children.Take(requested).ToList(); + } + + var provided = children.Count; + + foreach (var i in children) + { + if (i.IsFolder) + { + var f = (Folder)i; + var childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count(); + + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); + } + else + { + result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, deviceId, filter)); + } + } + + var resXML = result.OuterXml; + + 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)) + }; + } + + private IEnumerable GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort) + { + if (search.SearchType == SearchType.Unknown) + { + return GetChildrenSorted(folder, user, sort); + } + + var items = folder.GetRecursiveChildren(user); + items = FilterUnsupportedContent(items); + + if (search.SearchType == SearchType.Audio) + { + items = items.OfType