added upnp ConnectionManager.cs

This commit is contained in:
Luke Pulverenti 2014-05-20 20:56:24 -04:00
parent ad3c30c145
commit 1774e5b1ac
23 changed files with 779 additions and 281 deletions

View File

@ -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 response = PostAsync(request).Result;
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");
}
private async Task<ControlResponse> PostAsync(ProcessControlRequest request)
public object Post(ProcessConnectionManagerControlRequest request)
{
var response = PostAsync(request.RequestStream, _connectionManager).Result;
return ResultFactory.GetResult(response.Xml, "text/xml");
}
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),

View File

@ -0,0 +1,7 @@

namespace MediaBrowser.Controller.Dlna
{
public interface IConnectionManager : IUpnpService
{
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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" />

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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)
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
{
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)
{
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);
}
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
return HandleXGetFeatureList();
var controlResponse = new ControlResponse
{
Xml = env.OuterXml,
IsSuccessful = true
};
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
return HandleXSetBookmark(methodParams, user);
controlResponse.Headers.Add("EXT", string.Empty);
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
return HandleSearch(methodParams, user, deviceId);
return controlResponse;
}
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()

View File

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

View File

@ -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)

View File

@ -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.

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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
};
}
}
}

View File

@ -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>");
}
}
}

View File

@ -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));
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);