2
0
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:
Xjph
2021-06-03 22:25:32 -02:30
parent 7099cf23c6
commit a5154996ee
32 changed files with 2287 additions and 0 deletions

View 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)
{ }
}
}

View 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;
}
}

View 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);
}
}
}
}

View 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
}
}
}