1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Finish implementing GenHTTP

This commit is contained in:
Robert 2025-02-13 21:35:35 +01:00
parent e09389c6db
commit eb4a6ceafb
12 changed files with 122 additions and 184 deletions

View File

@ -38,7 +38,7 @@
<ItemGroup>
<PackageReference Include="DryIoc.dll" />
<PackageReference Include="GenHTTP.Core" />
<PackageReference Include="GenHTTP.Modules.Controllers" />
<PackageReference Include="GenHTTP.Modules.Webservices" />
<PackageReference Include="HidSharp" />
<PackageReference Include="HPPH.SkiaSharp" />
<PackageReference Include="Humanizer.Core" />

View File

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using GenHTTP.Api.Protocol;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
/// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel, new()
{
private readonly Module<T> _module;
private readonly Action<T, T> _update;
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
_module = module ?? throw new ArgumentNullException(nameof(module));
_update = CreateUpdateAction();
ThrowOnFail = true;
Accepts = ContentType.ApplicationJson;
}
/// <summary>
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
/// JSON throws a <see cref="JsonException" />.
/// </summary>
public bool ThrowOnFail { get; set; }
/// <inheritdoc />
protected override async Task<IResponse> ProcessRequest(IRequest request)
{
if (request.Method != RequestMethod.Post && request.Method != RequestMethod.Put)
return request.Respond().Status(ResponseStatus.MethodNotAllowed).Build();
if (request.Content == null)
return request.Respond().Status(ResponseStatus.BadRequest).Build();
try
{
T? dataModel = await JsonSerializer.DeserializeAsync<T>(request.Content, WebServerService.JsonOptions);
if (dataModel != null)
_update(dataModel, _module.DataModel);
}
catch (JsonException)
{
if (ThrowOnFail)
throw;
}
return request.Respond().Status(ResponseStatus.NoContent).Build();
}
private Action<T, T> CreateUpdateAction()
{
ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
ParameterExpression targetParameter = Expression.Parameter(typeof(T), "target");
IEnumerable<BinaryExpression> assignments = typeof(T)
.GetProperties()
.Where(prop => prop.CanWrite && prop.GetSetMethod() != null &&
prop.GetSetMethod()!.IsPublic &&
!prop.IsDefined(typeof(JsonIgnoreAttribute), false) &&
!prop.PropertyType.IsAssignableTo(typeof(IDataModelEvent)))
.Select(prop =>
{
MemberExpression sourceProperty = Expression.Property(sourceParameter, prop);
MemberExpression targetProperty = Expression.Property(targetParameter, prop);
return Expression.Assign(targetProperty, sourceProperty);
});
BlockExpression body = Expression.Block(assignments);
return Expression.Lambda<Action<T, T>>(body, sourceParameter, targetParameter).Compile();
}
}

View File

@ -17,19 +17,19 @@ public class JsonPluginEndPoint<T> : PluginEndPoint
private readonly Action<T>? _requestHandler;
private readonly Func<T, object?>? _responseRequestHandler;
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<T> requestHandler) : base(pluginFeature, name, pluginsModule)
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsHandler pluginsHandler, Action<T> requestHandler) : base(pluginFeature, name, pluginsHandler)
{
_requestHandler = requestHandler;
ThrowOnFail = true;
Accepts = ContentType.ApplicationJson;
Accepts = FlexibleContentType.Get(ContentType.ApplicationJson);
}
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<T, object?> responseRequestHandler) : base(pluginFeature, name, pluginsModule)
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsHandler pluginsHandler, Func<T, object?> responseRequestHandler) : base(pluginFeature, name, pluginsHandler)
{
_responseRequestHandler = responseRequestHandler;
ThrowOnFail = true;
Accepts = ContentType.ApplicationJson;
Returns = ContentType.ApplicationJson;
Accepts = FlexibleContentType.Get(ContentType.ApplicationJson);
Returns = FlexibleContentType.Get(ContentType.ApplicationJson);
}
/// <summary>

View File

@ -10,15 +10,15 @@ using StringContent = GenHTTP.Modules.IO.Strings.StringContent;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a base type for plugin end points to be targeted by the <see cref="PluginsModule" />
/// Represents a base type for plugin end points to be targeted by the <see cref="PluginsHandler" />
/// </summary>
public abstract class PluginEndPoint
{
private readonly PluginsModule _pluginsModule;
private readonly PluginsHandler _pluginsHandler;
internal PluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule)
internal PluginEndPoint(PluginFeature pluginFeature, string name, PluginsHandler pluginsHandler)
{
_pluginsModule = pluginsModule;
_pluginsHandler = pluginsHandler;
PluginFeature = pluginFeature;
Name = name;
@ -33,7 +33,7 @@ public abstract class PluginEndPoint
/// <summary>
/// Gets the full URL of the end point
/// </summary>
public string Url => $"{_pluginsModule.ServerUrl?.TrimEnd('/')}{_pluginsModule.BaseRoute}{PluginFeature.Plugin.Guid}/{Name}";
public string Url => $"{_pluginsHandler.ServerUrl}{_pluginsHandler.BaseRoute}/{PluginFeature.Plugin.Guid}/{Name}";
/// <summary>
/// Gets the plugin the end point is associated with
@ -46,15 +46,15 @@ public abstract class PluginEndPoint
/// </summary>
public PluginInfo PluginInfo => PluginFeature.Plugin.Info;
/// <summary>
/// <summary><summary>
/// Gets the mime type of the input this end point accepts
/// </summary>
public ContentType Accepts { get; protected set; }
public FlexibleContentType Accepts { get; protected set; }
/// <summary>
/// Gets the mime type of the output this end point returns
/// </summary>
public ContentType Returns { get; protected set; }
public FlexibleContentType Returns { get; protected set; }
/// <summary>
/// Occurs whenever a request threw an unhandled exception
@ -107,6 +107,13 @@ public abstract class PluginEndPoint
try
{
OnProcessingRequest(context);
if (!Equals(context.ContentType, Accepts))
{
OnRequestException(new Exception("Unsupported media type"));
return context.Respond().Status(ResponseStatus.UnsupportedMediaType).Build();
}
IResponse response = await ProcessRequest(context);
OnProcessedRequest(context);
return response;
@ -125,6 +132,6 @@ public abstract class PluginEndPoint
private void OnDisabled(object? sender, EventArgs e)
{
PluginFeature.Disabled -= OnDisabled;
_pluginsModule.RemovePluginEndPoint(this);
_pluginsHandler.RemovePluginEndPoint(this);
}
}

View File

@ -14,7 +14,7 @@ namespace Artemis.Core.Services;
public class RawPluginEndPoint : PluginEndPoint
{
/// <inheritdoc />
internal RawPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<IRequest, Task<IResponse>> requestHandler) : base(pluginFeature, name, pluginsModule)
internal RawPluginEndPoint(PluginFeature pluginFeature, string name, PluginsHandler pluginsHandler, Func<IRequest, Task<IResponse>> requestHandler) : base(pluginFeature, name, pluginsHandler)
{
RequestHandler = requestHandler;
}
@ -29,7 +29,7 @@ public class RawPluginEndPoint : PluginEndPoint
/// </summary>
public void SetAcceptType(ContentType type)
{
Accepts = type;
Accepts = FlexibleContentType.Get(type);
}
/// <summary>
@ -37,7 +37,7 @@ public class RawPluginEndPoint : PluginEndPoint
/// </summary>
public void SetReturnType(ContentType type)
{
Returns = type;
Returns = FlexibleContentType.Get(type);
}
#region Overrides of PluginEndPoint

View File

@ -16,17 +16,17 @@ public class StringPluginEndPoint : PluginEndPoint
private readonly Action<string>? _requestHandler;
private readonly Func<string, string?>? _responseRequestHandler;
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<string> requestHandler) : base(pluginFeature, name, pluginsModule)
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsHandler pluginsHandler, Action<string> requestHandler) : base(pluginFeature, name, pluginsHandler)
{
_requestHandler = requestHandler;
Accepts = ContentType.TextPlain;
Accepts = FlexibleContentType.Get(ContentType.TextPlain);
}
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<string, string?> requestHandler) : base(pluginFeature, name, pluginsModule)
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsHandler pluginsHandler, Func<string, string?> requestHandler) : base(pluginFeature, name, pluginsHandler)
{
_responseRequestHandler = requestHandler;
Accepts = ContentType.TextPlain;
Returns = ContentType.TextPlain;
Accepts = FlexibleContentType.Get(ContentType.TextPlain);
Returns = FlexibleContentType.Get(ContentType.TextPlain);
}
#region Overrides of PluginEndPoint

View File

@ -19,7 +19,7 @@ public interface IWebServerService : IArtemisService
/// <summary>
/// Gets the plugins module containing all plugin end points
/// </summary>
PluginsModule PluginsModule { get; }
PluginsHandler PluginsHandler { get; }
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" />
@ -44,16 +44,6 @@ public interface IWebServerService : IArtemisService
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
/// </summary>
/// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
[Obsolete("This way of updating is too unpredictable in combination with nested events, use AddJsonEndPoint<T> to update manually instead")]
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new();
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary>
@ -95,7 +85,7 @@ public interface IWebServerService : IArtemisService
/// <summary>
/// Adds a new Web API controller and restarts the web server
/// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
/// <typeparam name="T">The type of Web API controller to add</typeparam>
WebApiControllerRegistration AddController<T>(PluginFeature feature, string path) where T : class;
/// <summary>

View File

@ -12,14 +12,14 @@ using GenHTTP.Modules.IO.Strings;
namespace Artemis.Core.Services;
/// <summary>
/// Represents an EmbedIO web module used to process web requests and forward them to the right
/// Represents an GenHTTP handler used to process web requests and forward them to the right
/// <see cref="PluginEndPoint" />.
/// </summary>
public class PluginsModule : IHandler
public class PluginsHandler : IHandler
{
private readonly Dictionary<string, Dictionary<string, PluginEndPoint>> _pluginEndPoints;
internal PluginsModule(string baseRoute)
internal PluginsHandler(string baseRoute)
{
BaseRoute = baseRoute;
_pluginEndPoints = new Dictionary<string, Dictionary<string, PluginEndPoint>>(comparer: StringComparer.InvariantCultureIgnoreCase);
@ -63,20 +63,30 @@ public class PluginsModule : IHandler
/// <inheritdoc />
public async ValueTask<IResponse?> HandleAsync(IRequest request)
{
// Expect a plugin ID and an endpoint
if (request.Target.Path.Parts.Count != 2)
// Used to be part of the RemoteController but moved here to avoid the /remote/ prefix enforced by GenHTTP
if (request.Target.Current?.Value != "plugins")
return null;
request.Target.Advance();
string? pluginId = request.Target.Current?.Value;
if (pluginId == null)
return null;
// Find a matching plugin, if none found let another handler have a go :)
if (!_pluginEndPoints.TryGetValue(request.Target.Path.Parts[0].Value, out Dictionary<string, PluginEndPoint>? endPoints))
if (!_pluginEndPoints.TryGetValue(pluginId, out Dictionary<string, PluginEndPoint>? endPoints))
return null;
request.Target.Advance();
string? endPointName = request.Target.Current?.Value;
if (endPointName == null)
return null;
// Find a matching endpoint
if (!endPoints.TryGetValue(request.Target.Path.Parts[1].Value, out PluginEndPoint? endPoint))
if (!endPoints.TryGetValue(endPointName, out PluginEndPoint? endPoint))
{
return request.Respond()
.Status(ResponseStatus.NotFound)
.Content(new StringContent($"Found no endpoint called {request.Target.Path.Parts[1].Value} for plugin with ID {request.Target.Path.Parts[0].Value}."))
.Content(new StringContent($"Found no endpoint called {endPointName} for plugin with ID {pluginId}."))
.Type(ContentType.TextPlain)
.Build();
}

View File

@ -0,0 +1,27 @@
using System.Threading.Tasks;
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
namespace Artemis.Core.Services;
/// <summary>
/// Represents an GenHTTP handler used to process web requests and forward them to the right
/// <see cref="PluginEndPoint" />.
/// </summary>
public class StatusHandler : IHandler
{
/// <inheritdoc />
public ValueTask PrepareAsync()
{
return ValueTask.CompletedTask;
}
/// <inheritdoc />
public ValueTask<IResponse?> HandleAsync(IRequest request)
{
// Used to be part of the RemoteController but moved here to avoid the /remote/ prefix enforced by GenHTTP
return request.Target.Current?.Value == "status"
? ValueTask.FromResult<IResponse?>(request.Respond().Status(ResponseStatus.NoContent).Build())
: ValueTask.FromResult<IResponse?>(null);
}
}

View File

@ -3,20 +3,21 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Protocol;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Controllers;
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Conversion.Serializers;
using GenHTTP.Modules.ErrorHandling;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Layouting.Provider;
using GenHTTP.Modules.Practices;
using GenHTTP.Modules.Security;
using GenHTTP.Modules.Webservices;
using Serilog;
namespace Artemis.Core.Services;
@ -29,7 +30,13 @@ internal class WebServerService : IWebServerService, IDisposable
private readonly PluginSetting<bool> _webServerEnabledSetting;
private readonly PluginSetting<int> _webServerPortSetting;
private readonly SemaphoreSlim _webserverSemaphore = new(1, 1);
internal static readonly JsonSerializerOptions JsonOptions = new(CoreJson.GetJsonSerializerOptions()) {ReferenceHandler = ReferenceHandler.IgnoreCycles, WriteIndented = true};
internal static readonly JsonSerializerOptions JsonOptions = new(CoreJson.GetJsonSerializerOptions())
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true,
Converters = {new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)}
};
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{
@ -43,7 +50,7 @@ internal class WebServerService : IWebServerService, IDisposable
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
PluginsModule = new PluginsModule("/plugins");
PluginsHandler = new PluginsHandler("plugins");
if (coreService.IsInitialized)
AutoStartWebServer();
else
@ -86,7 +93,7 @@ internal class WebServerService : IWebServerService, IDisposable
mustRestart = true;
_controllers.RemoveAll(c => c.Feature == e.PluginFeature);
}
if (mustRestart)
_ = StartWebServer();
}
@ -99,13 +106,13 @@ internal class WebServerService : IWebServerService, IDisposable
}
public IServer? Server { get; private set; }
public PluginsModule PluginsModule { get; }
public PluginsHandler PluginsHandler { get; }
public event EventHandler? WebServerStarting;
#region Web server managament
private async Task <IServer> CreateWebServer()
private async Task<IServer> CreateWebServer()
{
if (Server != null)
{
@ -114,27 +121,29 @@ internal class WebServerService : IWebServerService, IDisposable
Server = null;
}
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
PluginsHandler.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
LayoutBuilder serverLayout = Layout.Create()
.Add(PluginsModule)
.Add(PluginsHandler)
.Add(ErrorHandler.Structured())
.Add(CorsPolicy.Permissive());
// Add registered controllers to the API module
// Add registered controllers to the API module as services.
// GenHTTP also has controllers but services are more flexible and match EmbedIO's approach more closely.
SerializationBuilder serialization = Serialization.Default(JsonOptions);
foreach (WebApiControllerRegistration registration in _controllers)
{
serverLayout = serverLayout.Add(registration.Path, Controller.From(registration.Factory()));
serverLayout = serverLayout.AddService(registration.Path, registration.Factory(), serializers: serialization);
}
IServer server = Host.Create()
.Handler(serverLayout.Build())
.Bind(IPAddress.Loopback, (ushort) _webServerPortSetting.Value)
.Development()
.Defaults()
.Build();
// Store the URL in a webserver.txt file so that remote applications can find it
await File.WriteAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt"), PluginsModule.ServerUrl);
await File.WriteAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt"), PluginsHandler.ServerUrl);
return server;
}
@ -169,6 +178,7 @@ internal class WebServerService : IWebServerService, IDisposable
_logger.Warning(e, "Failed to start webserver");
throw;
}
OnWebServerStarted();
}
finally
@ -198,8 +208,8 @@ internal class WebServerService : IWebServerService, IDisposable
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsHandler, requestHandler);
PluginsHandler.AddPluginEndPoint(endPoint);
return endPoint;
}
@ -208,8 +218,8 @@ internal class WebServerService : IWebServerService, IDisposable
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsHandler, requestHandler);
PluginsHandler.AddPluginEndPoint(endPoint);
return endPoint;
}
@ -218,8 +228,8 @@ internal class WebServerService : IWebServerService, IDisposable
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsHandler, requestHandler);
PluginsHandler.AddPluginEndPoint(endPoint);
return endPoint;
}
@ -228,8 +238,8 @@ internal class WebServerService : IWebServerService, IDisposable
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsHandler, requestHandler);
PluginsHandler.AddPluginEndPoint(endPoint);
return endPoint;
}
@ -238,26 +248,16 @@ internal class WebServerService : IWebServerService, IDisposable
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
RawPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
[Obsolete("Use AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler) instead")]
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new()
{
if (module == null) throw new ArgumentNullException(nameof(module));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
DataModelJsonPluginEndPoint<T> endPoint = new(module, endPointName, PluginsModule);
PluginsModule.AddPluginEndPoint(endPoint);
RawPluginEndPoint endPoint = new(feature, endPointName, PluginsHandler, requestHandler);
PluginsHandler.AddPluginEndPoint(endPoint);
return endPoint;
}
public void RemovePluginEndPoint(PluginEndPoint endPoint)
{
PluginsModule.RemovePluginEndPoint(endPoint);
PluginsHandler.RemovePluginEndPoint(endPoint);
}
#endregion
#region Controller management

View File

@ -7,8 +7,7 @@ using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading;
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.Controllers;
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Webservices;
namespace Artemis.UI.Controllers;
@ -25,18 +24,7 @@ public class RemoteController
_router = router;
}
public void Index()
{
// HTTP 204 No Content
}
[ControllerAction(RequestMethod.Get)]
public void Status()
{
// HTTP 204 No Content
}
[ControllerAction(RequestMethod.Post)]
[ResourceMethod(RequestMethod.Post, "bring-to-foreground")]
public void BringToForeground(IRequest request)
{
// Get the route from the request content stream
@ -55,13 +43,13 @@ public class RemoteController
});
}
[ControllerAction(RequestMethod.Post)]
[ResourceMethod(RequestMethod.Post, "restart")]
public void Restart(List<string> args)
{
Utilities.Restart(_coreService.IsElevated, TimeSpan.FromMilliseconds(500), args.ToArray());
}
[ControllerAction(RequestMethod.Post)]
[ResourceMethod(RequestMethod.Post, "shutdown")]
public void Shutdown()
{
Utilities.Shutdown();

View File

@ -15,7 +15,7 @@
<PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageVersion Include="Avalonia.Win32" Version="11.2.3" />
<PackageVersion Include="GenHTTP.Core" Version="9.6.2" />
<PackageVersion Include="GenHTTP.Modules.Controllers" Version="9.6.2" />
<PackageVersion Include="GenHTTP.Modules.Webservices" Version="9.6.2" />
<PackageVersion Include="HPPH.SkiaSharp" Version="1.0.0" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.1" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.2.0.8" />