Merge branch 'master' into RGB.NET_update

This commit is contained in:
Robert committed 2021-03-05 15:40:15 +01:00
commit da90c0d3a1
94 files changed
+1525 -1007

No files matched your search

@@ -70,6 +70,12 @@ namespace Artemis.Core.Services
/// <returns>The resulting plugin</returns>
Plugin ImportPlugin(string fileName);
/// <summary>
/// Unloads and permanently removes the provided plugin
/// </summary>
/// <param name="plugin">The plugin to remove</param>
void RemovePlugin(Plugin plugin);
/// <summary>
/// Enables the provided plugin feature
/// </summary>
@@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Ninject;
using Artemis.Storage.Entities.Plugins;
@@ -275,6 +276,7 @@ namespace Artemis.Core.Services
plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure =>
{
configure.IsUnloadable = true;
configure.LoadInMemory = true;
configure.PreferSharedTypes = true;
});
@@ -430,7 +432,6 @@ namespace Artemis.Core.Services
OnPluginDisabled(new PluginEventArgs(plugin));
}
/// <inheritdoc />
public Plugin ImportPlugin(string fileName)
{
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins"));
@@ -449,7 +450,16 @@ namespace Artemis.Core.Services
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
if (existing != null)
throw new ArtemisPluginException($"A plugin with the same GUID is already loaded: {existing.Info}");
{
try
{
RemovePlugin(existing);
}
catch (Exception e)
{
throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e);
}
}
string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", "");
string uniqueTargetDirectory = targetDirectory;
@@ -464,19 +474,38 @@ namespace Artemis.Core.Services
// Extract everything in the same archive directory to the unique plugin directory
DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory));
Directory.CreateDirectory(directoryInfo.FullName);
Utilities.CreateAccessibleDirectory(directoryInfo.FullName);
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
{
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory))
{
string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
zipArchiveEntry.ExtractToFile(target);
// Create folders
if (zipArchiveEntry.FullName.EndsWith("/"))
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
// Extract files
else
zipArchiveEntry.ExtractToFile(target);
}
}
// Load the newly extracted plugin and return the result
return LoadPlugin(directoryInfo);
}
public void RemovePlugin(Plugin plugin)
{
DirectoryInfo directory = plugin.Directory;
lock (_plugins)
{
if (_plugins.Contains(plugin))
UnloadPlugin(plugin);
}
directory.Delete(true);
}
#endregion
#region Features
+2 -2
View File
@@ -71,7 +71,7 @@ namespace Artemis.Core.Services
_modifyingProviders = true;
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
Surface.Detach(deviceProvider.Devices);
Surface.Detach(toRemove.Select(d => d.RgbDevice));
foreach (ArtemisDevice device in toRemove)
RemoveDevice(device);
@@ -115,7 +115,7 @@ namespace Artemis.Core.Services
_modifyingProviders = true;
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
Surface.Detach(deviceProvider.Devices);
Surface.Detach(toRemove.Select(d => d.RgbDevice));
foreach (ArtemisDevice device in toRemove)
RemoveDevice(device);
@@ -79,6 +79,11 @@ namespace Artemis.Core.Services.Models
public void Arrange(List<ArtemisDevice> devices)
{
ArrangedDevices.Clear();
// Not much to do here
if (!devices.Any())
return;
foreach (ArtemisDevice surfaceDevice in devices)
{
surfaceDevice.X = 0;
@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using EmbedIO;
using Newtonsoft.Json;
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
{
private readonly Module<T>? _module;
private readonly DataModelExpansion<T>? _dataModelExpansion;
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
_module = module ?? throw new ArgumentNullException(nameof(module));
ThrowOnFail = true;
Accepts = MimeType.Json;
}
internal DataModelJsonPluginEndPoint(DataModelExpansion<T> dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule)
{
_dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion));
ThrowOnFail = true;
Accepts = MimeType.Json;
}
/// <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; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.Json;
using TextReader reader = context.OpenRequestText();
try
{
if (_module != null)
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
else
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel);
}
catch (JsonException)
{
if (ThrowOnFail)
throw;
}
}
#endregion
}
}
@@ -0,0 +1,20 @@
using System;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides data about endpoint exception related events
/// </summary>
public class EndpointExceptionEventArgs : EventArgs
{
internal EndpointExceptionEventArgs(Exception exception)
{
Exception = exception;
}
/// <summary>
/// Gets the exception that occurred
/// </summary>
public Exception Exception { get; }
}
}
@@ -0,0 +1,21 @@
using System;
using EmbedIO;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides data about endpoint request related events
/// </summary>
public class EndpointRequestEventArgs : EventArgs
{
internal EndpointRequestEventArgs(IHttpContext context)
{
Context = context;
}
/// <summary>
/// Gets the HTTP context of the request
/// </summary>
public IHttpContext Context { get; }
}
}
@@ -52,15 +52,65 @@ namespace Artemis.Core.Services
/// </summary>
public string? Returns { get; protected set; }
/// <summary>
/// Occurs whenever a request threw an unhandled exception
/// </summary>
public event EventHandler<EndpointExceptionEventArgs>? RequestException;
/// <summary>
/// Occurs whenever a request is about to be processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessingRequest;
/// <summary>
/// Occurs whenever a request was processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessedRequest;
/// <summary>
/// Called whenever the end point has to process a request
/// </summary>
/// <param name="context">The HTTP context of the request</param>
protected abstract Task ProcessRequest(IHttpContext context);
/// <summary>
/// Invokes the <see cref="RequestException" /> event
/// </summary>
/// <param name="e">The exception that occurred during the request</param>
protected virtual void OnRequestException(Exception e)
{
RequestException?.Invoke(this, new EndpointExceptionEventArgs(e));
}
/// <summary>
/// Invokes the <see cref="ProcessingRequest" /> event
/// </summary>
protected virtual void OnProcessingRequest(IHttpContext context)
{
ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
/// <summary>
/// Invokes the <see cref="ProcessedRequest" /> event
/// </summary>
protected virtual void OnProcessedRequest(IHttpContext context)
{
ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
internal async Task InternalProcessRequest(IHttpContext context)
{
await ProcessRequest(context);
try
{
OnProcessingRequest(context);
await ProcessRequest(context);
OnProcessedRequest(context);
}
catch (Exception e)
{
OnRequestException(e);
throw;
}
}
private void OnDisabled(object? sender, EventArgs e)
@@ -1,5 +1,7 @@
using System;
using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using EmbedIO;
using EmbedIO.WebApi;
@@ -43,6 +45,24 @@ namespace Artemis.Core.Services
/// <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>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="dataModelExpansion" />.
/// </summary>
/// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="dataModelExpansion">The data model expansion 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>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel;
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary>
@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services
{
@@ -67,6 +64,12 @@ namespace Artemis.Core.Services
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}.");
// If Accept-Charset contains a wildcard, remove the header so we default to UTF8
// This is a workaround for an EmbedIO ehh issue
string? acceptCharset = context.Request.Headers["Accept-Charset"];
if (acceptCharset != null && acceptCharset.Contains("*"))
context.Request.Headers.Remove("Accept-Charset");
// It is up to the registration how the request is eventually handled, it might even set a response here
await endPoint.InternalProcessRequest(context);
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using EmbedIO;
using EmbedIO.WebApi;
using Newtonsoft.Json;
@@ -28,7 +30,6 @@ namespace Artemis.Core.Services
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
PluginsModule = new PluginsModule("/plugins");
StartWebServer();
}
@@ -42,7 +43,7 @@ namespace Artemis.Core.Services
Server?.Dispose();
Server = null;
string url = $"http://localhost:{_webServerPortSetting.Value}/";
string url = $"http://*:{_webServerPortSetting.Value}/";
WebApiModule apiModule = new("/api/", JsonNetSerializer);
PluginsModule.ServerUrl = url;
WebServer server = new WebServer(o => o.WithUrlPrefix(url).WithMode(HttpListenerMode.EmbedIO))
@@ -126,6 +127,29 @@ namespace Artemis.Core.Services
return endPoint;
}
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel
{
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);
return endPoint;
}
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel
{
if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
DataModelJsonPluginEndPoint<T> endPoint = new(dataModelExpansion, endPointName, PluginsModule);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel
{
}
public void RemovePluginEndPoint(PluginEndPoint endPoint)
{
PluginsModule.RemovePluginEndPoint(endPoint);