diff options
Diffstat (limited to 'Emby.Server.Implementations/Services')
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; + } + } +} |
