aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Services
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/Services')
-rw-r--r--Emby.Server.Implementations/Services/HttpResult.cs9
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs31
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs9
-rw-r--r--Emby.Server.Implementations/Services/ServiceExec.cs33
-rw-r--r--Emby.Server.Implementations/Services/ServiceHandler.cs56
-rw-r--r--Emby.Server.Implementations/Services/ServiceMethod.cs7
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs128
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs10
-rw-r--r--Emby.Server.Implementations/Services/SwaggerService.cs260
9 files changed, 414 insertions, 129 deletions
diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs
index dfad09f7b..91314c15a 100644
--- a/Emby.Server.Implementations/Services/HttpResult.cs
+++ b/Emby.Server.Implementations/Services/HttpResult.cs
@@ -45,10 +45,15 @@ namespace Emby.Server.Implementations.Services
var bytesResponse = this.Response as byte[];
if (bytesResponse != null)
{
+ var contentLength = bytesResponse.Length;
+
if (response != null)
- response.SetContentLength(bytesResponse.Length);
+ response.SetContentLength(contentLength);
- await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false);
+ if (contentLength > 0)
+ {
+ await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false);
+ }
return;
}
diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs
index 84dc343c3..22e1bc4aa 100644
--- a/Emby.Server.Implementations/Services/ResponseHelper.cs
+++ b/Emby.Server.Implementations/Services/ResponseHelper.cs
@@ -41,11 +41,11 @@ namespace Emby.Server.Implementations.Services
response.StatusCode = httpResult.Status;
response.StatusDescription = httpResult.StatusCode.ToString();
- if (string.IsNullOrEmpty(httpResult.ContentType))
- {
- httpResult.ContentType = defaultContentType;
- }
- response.ContentType = httpResult.ContentType;
+ //if (string.IsNullOrEmpty(httpResult.ContentType))
+ //{
+ // httpResult.ContentType = defaultContentType;
+ //}
+ //response.ContentType = httpResult.ContentType;
if (httpResult.Cookies != null)
{
@@ -124,7 +124,10 @@ namespace Emby.Server.Implementations.Services
response.ContentType = "application/octet-stream";
response.SetContentLength(bytes.Length);
- await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ if (bytes.Length > 0)
+ {
+ await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ }
return;
}
@@ -133,7 +136,10 @@ namespace Emby.Server.Implementations.Services
{
bytes = Encoding.UTF8.GetBytes(responseText);
response.SetContentLength(bytes.Length);
- await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ if (bytes.Length > 0)
+ {
+ await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ }
return;
}
@@ -150,8 +156,15 @@ namespace Emby.Server.Implementations.Services
serializer(result, ms);
ms.Position = 0;
- response.SetContentLength(ms.Length);
- await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
+
+ var contentLength = ms.Length;
+
+ response.SetContentLength(contentLength);
+
+ if (contentLength > 0)
+ {
+ await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
+ }
}
//serializer(result, outputStream);
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index c3970b22f..3fd6d88f8 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -75,11 +75,7 @@ namespace Emby.Server.Implementations.Services
var attrs = appHost.GetRouteAttributes(requestType);
foreach (RouteAttribute attr in attrs)
{
- var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
-
- if (!restPath.IsValid)
- throw new NotSupportedException(string.Format(
- "RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetMethodName()));
+ var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.IsHidden, attr.Summary, attr.Description);
RegisterRestPath(restPath);
}
@@ -92,8 +88,7 @@ namespace Emby.Server.Implementations.Services
if (!restPath.Path.StartsWith("/"))
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
- throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " +
- "See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetMethodName()));
+ throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName()));
List<RestPath> pathsAtFirstMatch;
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs
index 4a2199c89..5709d3e0a 100644
--- a/Emby.Server.Implementations/Services/ServiceExec.cs
+++ b/Emby.Server.Implementations/Services/ServiceExec.cs
@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Services
{
public static class ServiceExecExtensions
{
- public static HashSet<string> AllVerbs = new HashSet<string>(new[] {
+ public static string[] AllVerbs = new[] {
"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616
"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518
"VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT",
@@ -22,27 +22,43 @@ namespace Emby.Server.Implementations.Services
"SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/
"BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY",
"POLL", "SUBSCRIBE", "UNSUBSCRIBE"
- });
+ };
- public static IEnumerable<MethodInfo> GetActions(this Type serviceType)
+ public static HashSet<string> AllVerbsSet = new HashSet<string>(AllVerbs);
+
+ public static List<MethodInfo> GetActions(this Type serviceType)
{
- foreach (var mi in serviceType.GetRuntimeMethods().Where(i => i.IsPublic && !i.IsStatic))
+ var list = new List<MethodInfo>();
+
+ foreach (var mi in serviceType.GetRuntimeMethods())
{
+ if (!mi.IsPublic)
+ {
+ continue;
+ }
+
+ if (mi.IsStatic)
+ {
+ continue;
+ }
+
if (mi.GetParameters().Length != 1)
continue;
var actionName = mi.Name;
- if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase) && !string.Equals(actionName, ServiceMethod.AnyAction, StringComparison.OrdinalIgnoreCase))
+ if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase))
continue;
- yield return mi;
+ list.Add(mi);
}
+
+ return list;
}
}
internal static class ServiceExecGeneral
{
- public static Dictionary<string, ServiceMethod> execMap = new Dictionary<string, ServiceMethod>();
+ private static Dictionary<string, ServiceMethod> execMap = new Dictionary<string, ServiceMethod>();
public static void CreateServiceRunnersFor(Type requestType, List<ServiceMethod> actions)
{
@@ -59,8 +75,7 @@ namespace Emby.Server.Implementations.Services
var actionName = request.Verb ?? "POST";
ServiceMethod actionContext;
- if (ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out actionContext)
- || ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.AnyKey(serviceType, requestName), out actionContext))
+ if (ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out actionContext))
{
if (actionContext.RequestFilters != null)
{
diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs
index f9fcfdbab..d500595ce 100644
--- a/Emby.Server.Implementations/Services/ServiceHandler.cs
+++ b/Emby.Server.Implementations/Services/ServiceHandler.cs
@@ -162,7 +162,11 @@ namespace Emby.Server.Implementations.Services
if (RequireqRequestStream(requestType))
{
// Used by IRequiresRequestStream
- return CreateRequiresRequestStreamRequest(host, httpReq, requestType);
+ var request = ServiceHandler.CreateRequest(httpReq, restPath, GetRequestParams(httpReq), host.CreateInstance(requestType));
+
+ var rawReq = (IRequiresRequestStream)request;
+ rawReq.RequestStream = httpReq.InputStream;
+ return rawReq;
}
var requestParams = GetFlattenedRequestParams(httpReq);
@@ -176,16 +180,6 @@ namespace Emby.Server.Implementations.Services
return requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo());
}
- private static IRequiresRequestStream CreateRequiresRequestStreamRequest(HttpListenerHost host, IRequest req, Type requestType)
- {
- var restPath = GetRoute(req);
- var request = ServiceHandler.CreateRequest(req, restPath, GetRequestParams(req), host.CreateInstance(requestType));
-
- var rawReq = (IRequiresRequestStream)request;
- rawReq.RequestStream = req.InputStream;
- return rawReq;
- }
-
public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams)
{
var requestDto = CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType);
@@ -228,22 +222,26 @@ namespace Emby.Server.Implementations.Services
}
}
- if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null)
+ if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")))
{
- foreach (var name in request.FormData.Keys)
+ var formData = request.FormData;
+ if (formData != null)
{
- if (name == null) continue; //thank you ASP.NET
-
- var values = request.FormData.GetValues(name);
- if (values.Count == 1)
+ foreach (var name in formData.Keys)
{
- map[name] = values[0];
- }
- else
- {
- for (var i = 0; i < values.Count; i++)
+ if (name == null) continue; //thank you ASP.NET
+
+ var values = formData.GetValues(name);
+ if (values.Count == 1)
+ {
+ map[name] = values[0];
+ }
+ else
{
- map[name + (i == 0 ? "" : "#" + i)] = values[i];
+ for (var i = 0; i < values.Count; i++)
+ {
+ map[name + (i == 0 ? "" : "#" + i)] = values[i];
+ }
}
}
}
@@ -270,12 +268,16 @@ namespace Emby.Server.Implementations.Services
map[name] = request.QueryString[name];
}
- if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null)
+ if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")))
{
- foreach (var name in request.FormData.Keys)
+ var formData = request.FormData;
+ if (formData != null)
{
- if (name == null) continue; //thank you ASP.NET
- map[name] = request.FormData[name];
+ foreach (var name in formData.Keys)
+ {
+ if (name == null) continue; //thank you ASP.NET
+ map[name] = formData[name];
+ }
}
}
diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs
index bcbc6fb57..fa2dd43d0 100644
--- a/Emby.Server.Implementations/Services/ServiceMethod.cs
+++ b/Emby.Server.Implementations/Services/ServiceMethod.cs
@@ -4,8 +4,6 @@ namespace Emby.Server.Implementations.Services
{
public class ServiceMethod
{
- public const string AnyAction = "ANY";
-
public string Id { get; set; }
public ActionInvokerFn ServiceAction { get; set; }
@@ -15,10 +13,5 @@ namespace Emby.Server.Implementations.Services
{
return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName;
}
-
- public static string AnyKey(Type serviceType, string requestDtoName)
- {
- return Key(serviceType, AnyAction, requestDtoName);
- }
}
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
index da5e74f1f..0ca36df19 100644
--- a/Emby.Server.Implementations/Services/ServicePath.cs
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -21,8 +21,6 @@ namespace Emby.Server.Implementations.Services
readonly bool[] componentsWithSeparators;
private readonly string restPath;
- private readonly string allowedVerbs;
- private readonly bool allowsAllVerbs;
public bool IsWildCardPath { get; private set; }
private readonly string[] literalsToMatch;
@@ -46,35 +44,21 @@ namespace Emby.Server.Implementations.Services
/// </summary>
public int TotalComponentsCount { get; set; }
- public string[] Verbs
- {
- get
- {
- return allowsAllVerbs
- ? new[] { "ANY" }
- : AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
- }
- }
+ public string[] Verbs { get; private set; }
public Type RequestType { get; private set; }
public string Path { get { return this.restPath; } }
public string Summary { get; private set; }
-
- public string Notes { get; private set; }
-
- public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } }
-
- public string AllowedVerbs { get { return this.allowedVerbs; } }
+ public string Description { get; private set; }
+ public bool IsHidden { get; private set; }
public int Priority { get; set; } //passed back to RouteAttribute
public static string[] GetPathPartsForMatching(string pathInfo)
{
- var parts = pathInfo.ToLower().Split(PathSeperatorChar)
- .Where(x => !String.IsNullOrEmpty(x)).ToArray();
- return parts;
+ return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
}
public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
@@ -109,18 +93,15 @@ namespace Emby.Server.Implementations.Services
return list;
}
- public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, string summary = null, string notes = null)
+ public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, bool isHidden = false, string summary = null, string description = null)
{
this.RequestType = requestType;
this.Summary = summary;
- this.Notes = notes;
+ this.IsHidden = isHidden;
+ this.Description = description;
this.restPath = path;
- this.allowsAllVerbs = verbs == null || String.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase);
- if (!this.allowsAllVerbs)
- {
- this.allowedVerbs = verbs.ToUpper();
- }
+ this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpper().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
var componentsList = new List<string>();
@@ -153,7 +134,6 @@ namespace Emby.Server.Implementations.Services
this.PathComponentsCount = this.componentsWithSeparators.Length;
string firstLiteralMatch = null;
- var sbHashKey = new StringBuilder();
for (var i = 0; i < components.Length; i++)
{
var component = components[i];
@@ -172,7 +152,6 @@ namespace Emby.Server.Implementations.Services
else
{
this.literalsToMatch[i] = component.ToLower();
- sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch);
if (firstLiteralMatch == null)
{
@@ -198,9 +177,6 @@ namespace Emby.Server.Implementations.Services
? this.PathComponentsCount + PathSeperator + firstLiteralMatch
: WildCardChar + PathSeperator + firstLiteralMatch;
- this.IsValid = sbHashKey.Length > 0;
- this.UniqueMatchHashKey = sbHashKey.ToString();
-
this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
RegisterCaseInsenstivePropertyNameMappings();
}
@@ -220,26 +196,46 @@ namespace Emby.Server.Implementations.Services
};
- private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) };
+ private static Type excludeType = typeof(Stream);
- internal static PropertyInfo[] GetSerializableProperties(Type type)
+ internal static List<PropertyInfo> GetSerializableProperties(Type type)
{
- var properties = GetPublicProperties(type);
- var readableProperties = properties.Where(x => x.GetMethod != null);
+ var list = new List<PropertyInfo>();
+ var props = GetPublicProperties(type);
- // else return those properties that are not decorated with IgnoreDataMember
- return readableProperties
- .Where(prop => prop.GetCustomAttributes(true)
- .All(attr =>
+ foreach (var prop in props)
+ {
+ if (prop.GetMethod == null)
+ {
+ continue;
+ }
+
+ if (excludeType == prop.PropertyType)
+ {
+ continue;
+ }
+
+ var ignored = false;
+ foreach (var attr in prop.GetCustomAttributes(true))
+ {
+ if (IgnoreAttributesNamed.Contains(attr.GetType().Name))
{
- var name = attr.GetType().Name;
- return !IgnoreAttributesNamed.Contains(name);
- }))
- .Where(prop => !_excludeTypes.Contains(prop.PropertyType))
- .ToArray();
+ ignored = true;
+ break;
+ }
+ }
+
+ if (!ignored)
+ {
+ list.Add(prop);
+ }
+ }
+
+ // else return those properties that are not decorated with IgnoreDataMember
+ return list;
}
- private static PropertyInfo[] GetPublicProperties(Type type)
+ private static List<PropertyInfo> GetPublicProperties(Type type)
{
if (type.GetTypeInfo().IsInterface)
{
@@ -269,12 +265,19 @@ namespace Emby.Server.Implementations.Services
propertyInfos.InsertRange(0, newPropertyInfos);
}
- return propertyInfos.ToArray(propertyInfos.Count);
+ return propertyInfos;
}
- return GetTypesPublicProperties(type)
- .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
- .ToArray();
+ var list = new List<PropertyInfo>();
+
+ foreach (var t in GetTypesPublicProperties(type))
+ {
+ if (t.GetIndexParameters().Length == 0)
+ {
+ list.Add(t);
+ }
+ }
+ return list;
}
private static PropertyInfo[] GetTypesPublicProperties(Type subType)
@@ -289,16 +292,11 @@ namespace Emby.Server.Implementations.Services
return pis.ToArray(pis.Count);
}
-
- public bool IsValid { get; set; }
-
/// <summary>
/// Provide for quick lookups based on hashes that can be determined from a request url
/// </summary>
public string FirstMatchHashKey { get; private set; }
- public string UniqueMatchHashKey { get; private set; }
-
private readonly StringMapTypeDeserializer typeDeserializer;
private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
@@ -321,8 +319,14 @@ namespace Emby.Server.Implementations.Services
score += Math.Max((10 - VariableArgsCount), 1) * 100;
//Exact verb match is better than ANY
- var exactVerb = String.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase);
- score += exactVerb ? 10 : 1;
+ if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase))
+ {
+ score += 10;
+ }
+ else
+ {
+ score += 1;
+ }
return score;
}
@@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Services
return false;
}
- if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod))
+ if (!Verbs.Contains(httpMethod, StringComparer.OrdinalIgnoreCase))
{
//logger.Info("allowsAllVerbs mismatch for {0} for {1} allowedverbs {2}", httpMethod, string.Join("/", withPathInfoParts), this.allowedVerbs);
return false;
@@ -457,8 +461,7 @@ namespace Emby.Server.Implementations.Services
public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
{
- var requestComponents = pathInfo.Split(PathSeperatorChar)
- .Where(x => !String.IsNullOrEmpty(x)).ToArray();
+ var requestComponents = pathInfo.Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
ExplodeComponents(ref requestComponents);
@@ -555,10 +558,5 @@ namespace Emby.Server.Implementations.Services
return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
}
-
- public override int GetHashCode()
- {
- return UniqueMatchHashKey.GetHashCode();
- }
}
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index fc1cf4ed9..2233bf918 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
namespace Emby.Server.Implementations.Services
@@ -64,11 +63,16 @@ namespace Emby.Server.Implementations.Services
if (instance == null)
instance = _CreateInstanceFn(type);
- foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value)))
+ foreach (var pair in keyValuePairs)
{
propertyName = pair.Key;
propertyTextValue = pair.Value;
+ if (string.IsNullOrEmpty(propertyTextValue))
+ {
+ continue;
+ }
+
if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
{
if (propertyName == "v")
@@ -115,7 +119,7 @@ namespace Emby.Server.Implementations.Services
{
public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
{
- if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null;
+ if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null;
var setMethodInfo = propertyInfo.SetMethod;
return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs
new file mode 100644
index 000000000..fc2bdbd55
--- /dev/null
+++ b/Emby.Server.Implementations/Services/SwaggerService.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Services;
+
+namespace Emby.Server.Implementations.Services
+{
+ [Route("/swagger", "GET", Summary = "Gets the swagger specifications")]
+ [Route("/swagger.json", "GET", Summary = "Gets the swagger specifications")]
+ public class GetSwaggerSpec : IReturn<SwaggerSpec>
+ {
+ }
+
+ public class SwaggerSpec
+ {
+ public string swagger { get; set; }
+ public string[] schemes { get; set; }
+ public SwaggerInfo info { get; set; }
+ public string host { get; set; }
+ public string basePath { get; set; }
+ public SwaggerTag[] tags { get; set; }
+ public IDictionary<string, Dictionary<string, SwaggerMethod>> paths { get; set; }
+ public Dictionary<string, SwaggerDefinition> definitions { get; set; }
+ public SwaggerComponents components { get; set; }
+ }
+
+ public class SwaggerComponents
+ {
+ public Dictionary<string, SwaggerSecurityScheme> securitySchemes { get; set; }
+ }
+
+ public class SwaggerSecurityScheme
+ {
+ public string name { get; set; }
+ public string type { get; set; }
+ public string @in { get; set; }
+ }
+
+ public class SwaggerInfo
+ {
+ public string description { get; set; }
+ public string version { get; set; }
+ public string title { get; set; }
+ public string termsOfService { get; set; }
+
+ public SwaggerConcactInfo contact { get; set; }
+ }
+
+ public class SwaggerConcactInfo
+ {
+ public string email { get; set; }
+ public string name { get; set; }
+ public string url { get; set; }
+ }
+
+ public class SwaggerTag
+ {
+ public string description { get; set; }
+ public string name { get; set; }
+ }
+
+ public class SwaggerMethod
+ {
+ public string summary { get; set; }
+ public string description { get; set; }
+ public string[] tags { get; set; }
+ public string operationId { get; set; }
+ public string[] consumes { get; set; }
+ public string[] produces { get; set; }
+ public SwaggerParam[] parameters { get; set; }
+ public Dictionary<string, SwaggerResponse> responses { get; set; }
+ public Dictionary<string, string[]>[] security { get; set; }
+ }
+
+ public class SwaggerParam
+ {
+ public string @in { get; set; }
+ public string name { get; set; }
+ public string description { get; set; }
+ public bool required { get; set; }
+ public string type { get; set; }
+ public string collectionFormat { get; set; }
+ }
+
+ public class SwaggerResponse
+ {
+ public string description { get; set; }
+
+ // ex. "$ref":"#/definitions/Pet"
+ public Dictionary<string, string> schema { get; set; }
+ }
+
+ public class SwaggerDefinition
+ {
+ public string type { get; set; }
+ public Dictionary<string, SwaggerProperty> properties { get; set; }
+ }
+
+ public class SwaggerProperty
+ {
+ public string type { get; set; }
+ public string format { get; set; }
+ public string description { get; set; }
+ public string[] @enum { get; set; }
+ public string @default { get; set; }
+ }
+
+ public class SwaggerService : IService, IRequiresRequest
+ {
+ private SwaggerSpec _spec;
+
+ public IRequest Request { get; set; }
+
+ public object Get(GetSwaggerSpec request)
+ {
+ return _spec ?? (_spec = GetSpec());
+ }
+
+ private SwaggerSpec GetSpec()
+ {
+ string host = null;
+ Uri uri;
+ if (Uri.TryCreate(Request.RawUrl, UriKind.Absolute, out uri))
+ {
+ host = uri.Host;
+ }
+
+ var securitySchemes = new Dictionary<string, SwaggerSecurityScheme>();
+
+ securitySchemes["api_key"] = new SwaggerSecurityScheme
+ {
+ name = "api_key",
+ type = "apiKey",
+ @in = "query"
+ };
+
+ var spec = new SwaggerSpec
+ {
+ schemes = new[] { "http" },
+ tags = GetTags(),
+ swagger = "2.0",
+ info = new SwaggerInfo
+ {
+ title = "Emby Server API",
+ version = "1.0.0",
+ description = "Explore the Emby Server API",
+ contact = new SwaggerConcactInfo
+ {
+ name = "Emby Developer Community",
+ url = "https://emby.media/community/index.php?/forum/47-developer-api"
+ },
+ termsOfService = "https://emby.media/terms"
+ },
+ paths = GetPaths(),
+ definitions = GetDefinitions(),
+ basePath = "/emby",
+ host = host,
+
+ components = new SwaggerComponents
+ {
+ securitySchemes = securitySchemes
+ }
+ };
+
+ return spec;
+ }
+
+
+ private SwaggerTag[] GetTags()
+ {
+ return new SwaggerTag[] { };
+ }
+
+ private Dictionary<string, SwaggerDefinition> GetDefinitions()
+ {
+ return new Dictionary<string, SwaggerDefinition>();
+ }
+
+ private IDictionary<string, Dictionary<string, SwaggerMethod>> GetPaths()
+ {
+ var paths = new SortedDictionary<string, Dictionary<string, SwaggerMethod>>();
+
+ var all = ServiceController.Instance.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList();
+
+ foreach (var current in all)
+ {
+ foreach (var info in current.Value)
+ {
+ if (info.IsHidden)
+ {
+ continue;
+ }
+
+ if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+ if (info.Path.StartsWith("/emby", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ paths[info.Path] = GetPathInfo(info);
+ }
+ }
+
+ return paths;
+ }
+
+ private Dictionary<string, SwaggerMethod> GetPathInfo(RestPath info)
+ {
+ var result = new Dictionary<string, SwaggerMethod>();
+
+ foreach (var verb in info.Verbs)
+ {
+ var responses = new Dictionary<string, SwaggerResponse>
+ {
+ };
+
+ responses["200"] = new SwaggerResponse
+ {
+ description = "OK"
+ };
+
+ var security = new List<Dictionary<string, string[]>>();
+
+ var apiKeySecurity = new Dictionary<string, string[]>();
+ apiKeySecurity["api_key"] = new string[] { };
+
+ security.Add(apiKeySecurity);
+
+ result[verb.ToLower()] = new SwaggerMethod
+ {
+ summary = info.Summary,
+ description = info.Description,
+ produces = new[]
+ {
+ "application/json"
+ },
+ consumes = new[]
+ {
+ "application/json"
+ },
+ operationId = info.RequestType.Name,
+ tags = new string[] { },
+
+ parameters = new SwaggerParam[] { },
+
+ responses = responses,
+
+ security = security.ToArray()
+ };
+ }
+
+ return result;
+ }
+ }
+}