mirror of
https://github.com/9ParsonsB/Pulsar.git
synced 2025-07-01 08:23:42 -04:00
Add project files.
This commit is contained in:
38
ObservatoryCore/PluginManagement/PlaceholderPlugin.cs
Normal file
38
ObservatoryCore/PluginManagement/PlaceholderPlugin.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
public class PlaceholderPlugin : IObservatoryNotifier
|
||||
{
|
||||
public PlaceholderPlugin(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public string Name => name;
|
||||
|
||||
private string name;
|
||||
|
||||
public string ShortName => name;
|
||||
|
||||
public string Version => string.Empty;
|
||||
|
||||
public PluginUI PluginUI => new PluginUI(PluginUI.UIType.None, null);
|
||||
|
||||
public object Settings { get => null; set { } }
|
||||
|
||||
public void Load(IObservatoryCore observatoryCore)
|
||||
{ }
|
||||
|
||||
public void OnNotificationEvent(string title, string text)
|
||||
{ }
|
||||
}
|
||||
}
|
86
ObservatoryCore/PluginManagement/PluginCore.cs
Normal file
86
ObservatoryCore/PluginManagement/PluginCore.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System;
|
||||
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
public class PluginCore : IObservatoryCore
|
||||
{
|
||||
|
||||
public string Version => "1.0a";
|
||||
|
||||
public Status GetStatus()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RequestAllJournals()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RequestJournalRange(DateTime start, DateTime end)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RequestJournalRange(int startIndex, int number, bool newestFirst)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SendNotification(string title, string text)
|
||||
{
|
||||
if (!LogMonitor.GetInstance.ReadAllInProgress())
|
||||
{
|
||||
var handler = Notification;
|
||||
handler?.Invoke(this, new NotificationEventArgs() { Title = title, Detail = text });
|
||||
|
||||
if (Properties.Core.Default.NativeNotify)
|
||||
{
|
||||
InvokeNativeNotification(title, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeNativeNotification(string title, string text)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
var notifyWindow = new UI.Views.NotificationView() { DataContext = new UI.ViewModels.NotificationViewModel(title, text) };
|
||||
notifyWindow.Show();
|
||||
});
|
||||
}
|
||||
|
||||
public void AddGridItem(IObservatoryWorker worker, object item)
|
||||
{
|
||||
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
worker.PluginUI.DataGrid.Add(item);
|
||||
|
||||
//Hacky removal of original empty object if one was used to populate columns
|
||||
if (worker.PluginUI.DataGrid.Count == 2)
|
||||
{
|
||||
bool allNull = true;
|
||||
Type itemType = worker.PluginUI.DataGrid[0].GetType();
|
||||
foreach(var property in itemType.GetProperties())
|
||||
{
|
||||
if (property.GetValue(worker.PluginUI.DataGrid[0], null) != null)
|
||||
{
|
||||
allNull = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allNull)
|
||||
worker.PluginUI.DataGrid.RemoveAt(0);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public event EventHandler<NotificationEventArgs> Notification;
|
||||
}
|
||||
}
|
48
ObservatoryCore/PluginManagement/PluginEventHandler.cs
Normal file
48
ObservatoryCore/PluginManagement/PluginEventHandler.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using Observatory.Framework.Files;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
class PluginEventHandler
|
||||
{
|
||||
private IEnumerable<IObservatoryWorker> observatoryWorkers;
|
||||
private IEnumerable<IObservatoryNotifier> observatoryNotifiers;
|
||||
|
||||
public PluginEventHandler(IEnumerable<IObservatoryWorker> observatoryWorkers, IEnumerable<IObservatoryNotifier> observatoryNotifiers)
|
||||
{
|
||||
this.observatoryWorkers = observatoryWorkers;
|
||||
this.observatoryNotifiers = observatoryNotifiers;
|
||||
}
|
||||
|
||||
public void OnJournalEvent(object source, JournalEventArgs journalEventArgs)
|
||||
{
|
||||
foreach (var worker in observatoryWorkers)
|
||||
{
|
||||
worker.JournalEvent((JournalBase)journalEventArgs.journalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStatusUpdate(object sourece, JournalEventArgs journalEventArgs)
|
||||
{
|
||||
foreach (var worker in observatoryWorkers)
|
||||
{
|
||||
worker.StatusChange((Status)journalEventArgs.journalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnNotificationEvent(object source, NotificationEventArgs notificationEventArgs)
|
||||
{
|
||||
foreach (var notifier in observatoryNotifiers)
|
||||
{
|
||||
notifier.OnNotificationEvent(notificationEventArgs.Title, notificationEventArgs.Detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
282
ObservatoryCore/PluginManagement/PluginManager.cs
Normal file
282
ObservatoryCore/PluginManagement/PluginManager.cs
Normal file
@ -0,0 +1,282 @@
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Data;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System.IO;
|
||||
using System.Configuration;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
public class PluginManager
|
||||
{
|
||||
public static PluginManager GetInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Lazy<PluginManager> _instance = new Lazy<PluginManager>(NewPluginManager);
|
||||
|
||||
private static PluginManager NewPluginManager()
|
||||
{
|
||||
return new PluginManager();
|
||||
}
|
||||
|
||||
|
||||
public readonly List<string> errorList;
|
||||
public readonly List<Panel> pluginPanels;
|
||||
public readonly List<DataTable> pluginTables;
|
||||
public readonly List<(IObservatoryWorker plugin, PluginStatus signed)> workerPlugins;
|
||||
public readonly List<(IObservatoryNotifier plugin, PluginStatus signed)> notifyPlugins;
|
||||
|
||||
private PluginManager()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
errorList = LoadPlugins(out workerPlugins, out notifyPlugins);
|
||||
|
||||
foreach (var error in errorList)
|
||||
{
|
||||
Console.WriteLine(error);
|
||||
}
|
||||
|
||||
var pluginHandler = new PluginEventHandler(workerPlugins.Select(p => p.plugin), notifyPlugins.Select(p => p.plugin));
|
||||
var logMonitor = LogMonitor.GetInstance;
|
||||
pluginPanels = new();
|
||||
pluginTables = new();
|
||||
|
||||
logMonitor.JournalEntry += pluginHandler.OnJournalEvent;
|
||||
logMonitor.StatusUpdate += pluginHandler.OnStatusUpdate;
|
||||
|
||||
var core = new PluginCore();
|
||||
|
||||
foreach (var plugin in workerPlugins.Select(p => p.plugin))
|
||||
{
|
||||
LoadSettings(plugin);
|
||||
plugin.Load(core);
|
||||
}
|
||||
|
||||
core.Notification += pluginHandler.OnNotificationEvent;
|
||||
}
|
||||
|
||||
private void LoadSettings(IObservatoryPlugin plugin)
|
||||
{
|
||||
string settingsFile = GetSettingsFile(plugin);
|
||||
bool createFile = !File.Exists(settingsFile);
|
||||
|
||||
if (!createFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
string settingsJson = File.ReadAllText(settingsFile);
|
||||
if (settingsJson != "null")
|
||||
plugin.Settings = JsonSerializer.Deserialize(settingsJson, plugin.Settings.GetType());
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Invalid settings file, remove and recreate
|
||||
File.Delete(settingsFile);
|
||||
createFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (createFile)
|
||||
{
|
||||
string settingsJson = JsonSerializer.Serialize(plugin.Settings);
|
||||
string settingsDirectory = new FileInfo(settingsFile).DirectoryName;
|
||||
if (!Directory.Exists(settingsDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(settingsDirectory);
|
||||
}
|
||||
File.WriteAllText(settingsFile, settingsJson);
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<PropertyInfo, string> GetSettingDisplayNames(object settings)
|
||||
{
|
||||
var settingNames = new Dictionary<PropertyInfo, string>();
|
||||
|
||||
var properties = settings.GetType().GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var attrib = property.GetCustomAttribute<Framework.SettingDisplayName>();
|
||||
if (attrib == null)
|
||||
{
|
||||
settingNames.Add(property, property.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
settingNames.Add(property, attrib.DisplayName);
|
||||
}
|
||||
}
|
||||
return settingNames;
|
||||
}
|
||||
|
||||
public void SaveSettings(IObservatoryPlugin plugin, object settings)
|
||||
{
|
||||
string settingsFile = GetSettingsFile(plugin);
|
||||
|
||||
string settingsJson = JsonSerializer.Serialize(settings, new JsonSerializerOptions()
|
||||
{
|
||||
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve
|
||||
});
|
||||
string settingsDirectory = new FileInfo(settingsFile).DirectoryName;
|
||||
if (!Directory.Exists(settingsDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(settingsDirectory);
|
||||
}
|
||||
File.WriteAllText(settingsFile, settingsJson);
|
||||
|
||||
}
|
||||
|
||||
private static string GetSettingsFile(IObservatoryPlugin plugin)
|
||||
{
|
||||
var configDirectory = new FileInfo(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath).Directory;
|
||||
return configDirectory.FullName + "\\" + plugin.Name + ".json";
|
||||
}
|
||||
|
||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
AppDomain appDomain = (AppDomain)sender;
|
||||
var assemblies = appDomain.GetAssemblies();
|
||||
|
||||
if (args.Name.ToLower().Contains(".resources"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (assemblies.FirstOrDefault(x => x.FullName == args.Name) == null)
|
||||
{
|
||||
AssemblyName assemblyName = new AssemblyName(args.Name);
|
||||
System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
|
||||
}
|
||||
|
||||
var assembly = assemblies.FirstOrDefault(x => x.FullName == args.Name);
|
||||
return assembly;
|
||||
}
|
||||
|
||||
private static List<string> LoadPlugins(out List<(IObservatoryWorker plugin, PluginStatus signed)> observatoryWorkers, out List<(IObservatoryNotifier plugin, PluginStatus signed)> observatoryNotifiers)
|
||||
{
|
||||
observatoryWorkers = new();
|
||||
observatoryNotifiers = new();
|
||||
var errorList = new List<string>();
|
||||
|
||||
string pluginPath = $".{Path.DirectorySeparatorChar}plugins";
|
||||
|
||||
if (Directory.Exists(pluginPath))
|
||||
{
|
||||
|
||||
var pluginLibraries = Directory.GetFiles($".{Path.DirectorySeparatorChar}plugins", "*.dll");
|
||||
var coreToken = Assembly.GetExecutingAssembly().GetName().GetPublicKeyToken();
|
||||
foreach (var dll in pluginLibraries)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pluginToken = AssemblyName.GetAssemblyName(dll).GetPublicKeyToken();
|
||||
PluginStatus signed;
|
||||
|
||||
if (pluginToken.Length == 0)
|
||||
{
|
||||
errorList.Add($"Warning: {dll} not signed.");
|
||||
signed = PluginStatus.Unsigned;
|
||||
}
|
||||
else if (!coreToken.SequenceEqual(pluginToken))
|
||||
{
|
||||
errorList.Add($"Warning: {dll} signature does not match.");
|
||||
signed = PluginStatus.InvalidSignature;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorList.Add($"OK: {dll} signed.");
|
||||
signed = PluginStatus.Signed;
|
||||
}
|
||||
|
||||
if (signed == PluginStatus.Signed || Properties.Core.Default.AllowUnsigned)
|
||||
{
|
||||
string error = LoadPluginAssembly(dll, observatoryWorkers, observatoryNotifiers);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
errorList.Add(error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadPlaceholderPlugin(dll, signed, observatoryNotifiers);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorList.Add($"ERROR: {new FileInfo(dll).Name}, {ex.Message}");
|
||||
LoadPlaceholderPlugin(dll, PluginStatus.InvalidLibrary, observatoryNotifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
private static string LoadPluginAssembly(string dllPath, List<(IObservatoryWorker plugin, PluginStatus signed)> workers, List<(IObservatoryNotifier plugin, PluginStatus signed)> notifiers)
|
||||
{
|
||||
var pluginAssembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(new FileInfo(dllPath).FullName);
|
||||
Type[] types;
|
||||
string err = string.Empty;
|
||||
try
|
||||
{
|
||||
types = pluginAssembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
types = ex.Types.Where(t => t != null).ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
types = Array.Empty<Type>();
|
||||
}
|
||||
|
||||
var workerTypes = types.Where(t => t.IsAssignableTo(typeof(IObservatoryWorker)));
|
||||
foreach (var worker in workerTypes)
|
||||
{
|
||||
ConstructorInfo constructor = worker.GetConstructor(Array.Empty<Type>());
|
||||
object instance = constructor.Invoke(Array.Empty<object>());
|
||||
workers.Add((instance as IObservatoryWorker, PluginStatus.Signed));
|
||||
}
|
||||
|
||||
var notifyTypes = types.Where(t => t.IsAssignableTo(typeof(IObservatoryNotifier)));
|
||||
foreach (var notifier in notifyTypes)
|
||||
{
|
||||
ConstructorInfo constructor = notifier.GetConstructor(Array.Empty<Type>());
|
||||
object instance = constructor.Invoke(Array.Empty<object>());
|
||||
notifiers.Add((instance as IObservatoryNotifier, PluginStatus.Signed));
|
||||
}
|
||||
|
||||
if (workerTypes.Count() + notifyTypes.Count() == 0)
|
||||
{
|
||||
err += $"ERROR: Library '{dllPath}' contains no suitable interfaces.";
|
||||
LoadPlaceholderPlugin(dllPath, PluginStatus.InvalidPlugin, notifiers);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
private static void LoadPlaceholderPlugin(string dllPath, PluginStatus pluginStatus, List<(IObservatoryNotifier plugin, PluginStatus signed)> notifiers)
|
||||
{
|
||||
PlaceholderPlugin placeholder = new(new FileInfo(dllPath).Name);
|
||||
notifiers.Add((placeholder, pluginStatus));
|
||||
}
|
||||
|
||||
public enum PluginStatus
|
||||
{
|
||||
Signed,
|
||||
Unsigned,
|
||||
InvalidSignature,
|
||||
InvalidPlugin,
|
||||
InvalidLibrary
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user