2016-11-11 19:55:12 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Reflection;
|
|
|
|
using System.Threading.Tasks;
|
2017-02-13 01:07:48 +00:00
|
|
|
using Emby.Server.Implementations.HttpServer;
|
2017-02-12 22:53:14 +00:00
|
|
|
using MediaBrowser.Model.Logging;
|
2016-11-11 19:55:12 +00:00
|
|
|
using MediaBrowser.Model.Services;
|
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
namespace Emby.Server.Implementations.Services
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
|
|
|
public delegate Task<object> InstanceExecFn(IRequest requestContext, object intance, object request);
|
|
|
|
public delegate object ActionInvokerFn(object intance, object request);
|
|
|
|
public delegate void VoidActionInvokerFn(object intance, object request);
|
|
|
|
|
|
|
|
public class ServiceController
|
|
|
|
{
|
2017-02-13 01:07:48 +00:00
|
|
|
public static ServiceController Instance;
|
2016-11-11 19:55:12 +00:00
|
|
|
private readonly Func<IEnumerable<Type>> _resolveServicesFn;
|
|
|
|
|
|
|
|
public ServiceController(Func<IEnumerable<Type>> resolveServicesFn)
|
|
|
|
{
|
2017-02-13 01:07:48 +00:00
|
|
|
Instance = this;
|
2016-11-11 19:55:12 +00:00
|
|
|
_resolveServicesFn = resolveServicesFn;
|
|
|
|
}
|
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
public void Init(HttpListenerHost appHost)
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
|
|
|
foreach (var serviceType in _resolveServicesFn())
|
|
|
|
{
|
2017-02-13 01:07:48 +00:00
|
|
|
RegisterService(appHost, serviceType);
|
2016-11-11 19:55:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Type[] GetGenericArguments(Type type)
|
|
|
|
{
|
|
|
|
return type.GetTypeInfo().IsGenericTypeDefinition
|
|
|
|
? type.GetTypeInfo().GenericTypeParameters
|
|
|
|
: type.GetTypeInfo().GenericTypeArguments;
|
|
|
|
}
|
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
public void RegisterService(HttpListenerHost appHost, Type serviceType)
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
|
|
|
var processedReqs = new HashSet<Type>();
|
|
|
|
|
|
|
|
var actions = ServiceExecGeneral.Reset(serviceType);
|
|
|
|
|
|
|
|
foreach (var mi in serviceType.GetActions())
|
|
|
|
{
|
|
|
|
var requestType = mi.GetParameters()[0].ParameterType;
|
|
|
|
if (processedReqs.Contains(requestType)) continue;
|
|
|
|
processedReqs.Add(requestType);
|
|
|
|
|
|
|
|
ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
|
|
|
|
|
2017-02-13 02:06:54 +00:00
|
|
|
var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>));
|
2016-11-11 19:55:12 +00:00
|
|
|
var responseType = returnMarker != null ?
|
|
|
|
GetGenericArguments(returnMarker)[0]
|
|
|
|
: mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
|
|
|
|
mi.ReturnType
|
|
|
|
: Type.GetType(requestType.FullName + "Response");
|
|
|
|
|
2017-02-13 02:06:54 +00:00
|
|
|
RegisterRestPaths(appHost, requestType);
|
2016-11-11 19:55:12 +00:00
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
appHost.AddServiceInfo(serviceType, requestType, responseType);
|
2016-11-11 19:55:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-13 02:06:54 +00:00
|
|
|
private static Type GetTypeWithGenericTypeDefinitionOf(Type type, Type genericTypeDefinition)
|
|
|
|
{
|
|
|
|
foreach (var t in type.GetTypeInfo().ImplementedInterfaces)
|
|
|
|
{
|
|
|
|
if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == genericTypeDefinition)
|
|
|
|
{
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var genericType = FirstGenericType(type);
|
|
|
|
if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition)
|
|
|
|
{
|
|
|
|
return genericType;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Type FirstGenericType(Type type)
|
|
|
|
{
|
|
|
|
while (type != null)
|
|
|
|
{
|
|
|
|
if (type.GetTypeInfo().IsGenericType)
|
|
|
|
return type;
|
|
|
|
|
|
|
|
type = type.GetTypeInfo().BaseType;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-12-04 21:54:45 +00:00
|
|
|
public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>(StringComparer.OrdinalIgnoreCase);
|
2016-11-11 19:55:12 +00:00
|
|
|
|
2017-02-13 02:06:54 +00:00
|
|
|
public void RegisterRestPaths(HttpListenerHost appHost, Type requestType)
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
|
|
|
var attrs = appHost.GetRouteAttributes(requestType);
|
2017-02-13 02:06:54 +00:00
|
|
|
foreach (RouteAttribute attr in attrs)
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
2017-02-13 02:06:54 +00:00
|
|
|
var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
|
2016-11-11 19:55:12 +00:00
|
|
|
|
|
|
|
if (!restPath.IsValid)
|
|
|
|
throw new NotSupportedException(string.Format(
|
2017-02-13 20:54:28 +00:00
|
|
|
"RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetMethodName()));
|
2016-11-11 19:55:12 +00:00
|
|
|
|
|
|
|
RegisterRestPath(restPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static readonly char[] InvalidRouteChars = new[] { '?', '&' };
|
|
|
|
|
|
|
|
public void RegisterRestPath(RestPath restPath)
|
|
|
|
{
|
|
|
|
if (!restPath.Path.StartsWith("/"))
|
2017-02-13 20:54:28 +00:00
|
|
|
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
|
2016-11-11 19:55:12 +00:00
|
|
|
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
|
|
|
|
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " +
|
2017-02-13 20:54:28 +00:00
|
|
|
"See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetMethodName()));
|
2016-11-11 19:55:12 +00:00
|
|
|
|
|
|
|
List<RestPath> pathsAtFirstMatch;
|
|
|
|
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
|
|
|
|
{
|
|
|
|
pathsAtFirstMatch = new List<RestPath>();
|
|
|
|
RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch;
|
|
|
|
}
|
|
|
|
pathsAtFirstMatch.Add(restPath);
|
|
|
|
}
|
|
|
|
|
2017-02-12 22:53:14 +00:00
|
|
|
public RestPath GetRestPathForRequest(string httpMethod, string pathInfo, ILogger logger)
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
|
|
|
var matchUsingPathParts = RestPath.GetPathPartsForMatching(pathInfo);
|
|
|
|
|
|
|
|
List<RestPath> firstMatches;
|
|
|
|
|
|
|
|
var yieldedHashMatches = RestPath.GetFirstMatchHashKeys(matchUsingPathParts);
|
|
|
|
foreach (var potentialHashMatch in yieldedHashMatches)
|
|
|
|
{
|
2017-02-12 22:53:14 +00:00
|
|
|
if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-11 19:55:12 +00:00
|
|
|
|
|
|
|
var bestScore = -1;
|
|
|
|
foreach (var restPath in firstMatches)
|
|
|
|
{
|
2017-02-12 22:53:14 +00:00
|
|
|
var score = restPath.MatchScore(httpMethod, matchUsingPathParts, logger);
|
2016-11-11 19:55:12 +00:00
|
|
|
if (score > bestScore) bestScore = score;
|
|
|
|
}
|
2017-02-12 22:53:14 +00:00
|
|
|
|
2016-11-11 19:55:12 +00:00
|
|
|
if (bestScore > 0)
|
|
|
|
{
|
|
|
|
foreach (var restPath in firstMatches)
|
|
|
|
{
|
2017-02-12 22:53:14 +00:00
|
|
|
if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts, logger))
|
2016-11-11 19:55:12 +00:00
|
|
|
return restPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var yieldedWildcardMatches = RestPath.GetFirstMatchWildCardHashKeys(matchUsingPathParts);
|
|
|
|
foreach (var potentialHashMatch in yieldedWildcardMatches)
|
|
|
|
{
|
|
|
|
if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue;
|
|
|
|
|
|
|
|
var bestScore = -1;
|
|
|
|
foreach (var restPath in firstMatches)
|
|
|
|
{
|
2017-02-12 22:53:14 +00:00
|
|
|
var score = restPath.MatchScore(httpMethod, matchUsingPathParts, logger);
|
2016-11-11 19:55:12 +00:00
|
|
|
if (score > bestScore) bestScore = score;
|
|
|
|
}
|
|
|
|
if (bestScore > 0)
|
|
|
|
{
|
|
|
|
foreach (var restPath in firstMatches)
|
|
|
|
{
|
2017-02-12 22:53:14 +00:00
|
|
|
if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts, logger))
|
2016-11-11 19:55:12 +00:00
|
|
|
return restPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
public async Task<object> Execute(HttpListenerHost appHost, object requestDto, IRequest req)
|
2016-11-11 19:55:12 +00:00
|
|
|
{
|
|
|
|
req.Dto = requestDto;
|
|
|
|
var requestType = requestDto.GetType();
|
|
|
|
req.OperationName = requestType.Name;
|
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
var serviceType = appHost.GetServiceTypeByRequest(requestType);
|
2016-11-11 19:55:12 +00:00
|
|
|
|
2017-02-13 01:07:48 +00:00
|
|
|
var service = appHost.CreateInstance(serviceType);
|
2016-11-11 19:55:12 +00:00
|
|
|
|
|
|
|
//var service = typeFactory.CreateInstance(serviceType);
|
|
|
|
|
|
|
|
var serviceRequiresContext = service as IRequiresRequest;
|
|
|
|
if (serviceRequiresContext != null)
|
|
|
|
{
|
|
|
|
serviceRequiresContext.Request = req;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.Dto == null) // Don't override existing batched DTO[]
|
|
|
|
req.Dto = requestDto;
|
|
|
|
|
|
|
|
//Executes the service and returns the result
|
2017-02-13 20:54:28 +00:00
|
|
|
var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()).ConfigureAwait(false);
|
2016-11-11 19:55:12 +00:00
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|