mirror of
https://github.com/9ParsonsB/Pulsar.git
synced 2025-04-04 17:19:39 -04:00
Add project files.
This commit is contained in:
parent
7099cf23c6
commit
a5154996ee
365
.gitignore
vendored
Normal file
365
.gitignore
vendored
Normal file
@ -0,0 +1,365 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
*.snk
|
25
ObservatoryCore.sln
Normal file
25
ObservatoryCore.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30128.74
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryCore", "ObservatoryCore\ObservatoryCore.csproj", "{0E1C4F16-858E-4E53-948A-77D81A8F3395}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F41B8681-A5D9-4167-9938-56DE88024000}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
97
ObservatoryCore/JournalReader.cs
Normal file
97
ObservatoryCore/JournalReader.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Observatory
|
||||
{
|
||||
public class JournalReader
|
||||
{
|
||||
public static TJournal ObservatoryDeserializer<TJournal>(string json) where TJournal : JournalBase
|
||||
{
|
||||
TJournal deserialized;
|
||||
|
||||
if (typeof(TJournal) == typeof(InvalidJson))
|
||||
{
|
||||
InvalidJson invalidJson;
|
||||
try
|
||||
{
|
||||
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
|
||||
string eventType = string.Empty;
|
||||
string timestamp = string.Empty;
|
||||
|
||||
while ((eventType == string.Empty || timestamp == string.Empty) && reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
if (reader.GetString() == "event")
|
||||
{
|
||||
reader.Read();
|
||||
eventType = reader.GetString();
|
||||
}
|
||||
else if (reader.GetString() == "timestamp")
|
||||
{
|
||||
reader.Read();
|
||||
timestamp = reader.GetString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invalidJson = new InvalidJson()
|
||||
{
|
||||
Event = "InvalidJson",
|
||||
Timestamp = timestamp,
|
||||
OriginalEvent = eventType
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
invalidJson = new InvalidJson()
|
||||
{
|
||||
Event = "InvalidJson",
|
||||
Timestamp = string.Empty,
|
||||
OriginalEvent = "Invalid"
|
||||
};
|
||||
}
|
||||
|
||||
deserialized = (TJournal)Convert.ChangeType(invalidJson, typeof(TJournal));
|
||||
|
||||
}
|
||||
//Journal potentially had invalid JSON for a brief period in 2017, check for it and remove.
|
||||
//TODO: Check if this gets handled by InvalidJson now.
|
||||
else if (typeof(TJournal) == typeof(Scan) && json.Contains("\"RotationPeriod\":inf"))
|
||||
{
|
||||
deserialized = JsonSerializer.Deserialize<TJournal>(json.Replace("\"RotationPeriod\":inf,", ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
deserialized = JsonSerializer.Deserialize<TJournal>(json);
|
||||
}
|
||||
deserialized.Json = json;
|
||||
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
|
||||
public static Dictionary<string, Type> PopulateEventClasses()
|
||||
{
|
||||
var eventClasses = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
var allTypes = Assembly.GetAssembly(typeof(JournalBase)).GetTypes();
|
||||
|
||||
var journalTypes = allTypes.Where(a => a.IsSubclassOf(typeof(JournalBase)));
|
||||
|
||||
foreach (var journalType in journalTypes)
|
||||
{
|
||||
eventClasses.Add(journalType.Name, journalType);
|
||||
}
|
||||
|
||||
eventClasses.Add("JournalBase", typeof(JournalBase));
|
||||
|
||||
return eventClasses;
|
||||
}
|
||||
}
|
||||
}
|
265
ObservatoryCore/LogMonitor.cs
Normal file
265
ObservatoryCore/LogMonitor.cs
Normal file
@ -0,0 +1,265 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files;
|
||||
|
||||
namespace Observatory
|
||||
{
|
||||
class LogMonitor
|
||||
{
|
||||
#region Singleton Instantiation
|
||||
|
||||
public static LogMonitor GetInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Lazy<LogMonitor> _instance = new Lazy<LogMonitor>(NewLogMonitor);
|
||||
|
||||
private static LogMonitor NewLogMonitor()
|
||||
{
|
||||
return new LogMonitor();
|
||||
}
|
||||
|
||||
private LogMonitor()
|
||||
{
|
||||
currentLine = new();
|
||||
journalTypes = JournalReader.PopulateEventClasses();
|
||||
InitializeWatchers(string.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Start()
|
||||
{
|
||||
journalWatcher.EnableRaisingEvents = true;
|
||||
statusWatcher.EnableRaisingEvents = true;
|
||||
monitoring = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
journalWatcher.EnableRaisingEvents = false;
|
||||
statusWatcher.EnableRaisingEvents = false;
|
||||
monitoring = false;
|
||||
}
|
||||
|
||||
public void ChangeWatchedDirectory(string path)
|
||||
{
|
||||
journalWatcher.Dispose();
|
||||
statusWatcher.Dispose();
|
||||
InitializeWatchers(path);
|
||||
}
|
||||
|
||||
public bool IsMonitoring()
|
||||
{
|
||||
return monitoring;
|
||||
}
|
||||
|
||||
public bool ReadAllInProgress()
|
||||
{
|
||||
return readall;
|
||||
}
|
||||
|
||||
public void ReadAllJournals()
|
||||
{
|
||||
ReadAllJournals(string.Empty);
|
||||
}
|
||||
|
||||
public void ReadAllJournals(string path)
|
||||
{
|
||||
readall = true;
|
||||
DirectoryInfo logDirectory = GetJournalFolder(path);
|
||||
var files = logDirectory.GetFiles("Journal.????????????.??.log");
|
||||
foreach (var file in files)
|
||||
{
|
||||
var lines = ReadAllLines(file.FullName);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
DeserializeAndInvoke(line);
|
||||
}
|
||||
}
|
||||
readall = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Events
|
||||
|
||||
public event EventHandler<JournalEventArgs> JournalEntry;
|
||||
|
||||
public event EventHandler<JournalEventArgs> StatusUpdate;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private FileSystemWatcher journalWatcher;
|
||||
private FileSystemWatcher statusWatcher;
|
||||
private Dictionary<string, Type> journalTypes;
|
||||
private Dictionary<string, int> currentLine;
|
||||
private bool monitoring = false;
|
||||
private bool readall = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void InitializeWatchers(string path)
|
||||
{
|
||||
DirectoryInfo logDirectory = GetJournalFolder(path);
|
||||
|
||||
journalWatcher = new FileSystemWatcher(logDirectory.FullName, "Journal.????????????.??.log")
|
||||
{
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.LastAccess |
|
||||
NotifyFilters.FileName | NotifyFilters.CreationTime | NotifyFilters.DirectoryName
|
||||
};
|
||||
journalWatcher.Changed += LogChangedEvent;
|
||||
journalWatcher.Created += LogCreatedEvent;
|
||||
|
||||
statusWatcher = new FileSystemWatcher(logDirectory.FullName, "Status.json")
|
||||
{
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.LastAccess
|
||||
};
|
||||
statusWatcher.Changed += StatusUpdateEvent;
|
||||
}
|
||||
|
||||
private DirectoryInfo GetJournalFolder(string path)
|
||||
{
|
||||
DirectoryInfo logDirectory;
|
||||
|
||||
if (path.Length == 0 && Properties.Core.Default.JournalFolder.Trim().Length > 0)
|
||||
{
|
||||
path = Properties.Core.Default.JournalFolder;
|
||||
}
|
||||
|
||||
if (path.Length > 0)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
logDirectory = new DirectoryInfo(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Directory '{path}' does not exist.");
|
||||
}
|
||||
}
|
||||
else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
logDirectory = new DirectoryInfo(GetSavedGamesPath() + @"\Frontier Developments\Elite Dangerous");
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
logDirectory = new DirectoryInfo(".");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Current OS Platform Not Supported.");
|
||||
}
|
||||
|
||||
Properties.Core.Default.JournalFolder = path;
|
||||
Properties.Core.Default.Save();
|
||||
|
||||
return logDirectory;
|
||||
}
|
||||
|
||||
private void DeserializeAndInvoke(string line)
|
||||
{
|
||||
var eventType = JournalUtilities.GetEventType(line);
|
||||
if (!journalTypes.ContainsKey(eventType))
|
||||
{
|
||||
eventType = "JournalBase";
|
||||
}
|
||||
|
||||
var eventClass = journalTypes[eventType];
|
||||
MethodInfo journalRead = typeof(JournalReader).GetMethod(nameof(JournalReader.ObservatoryDeserializer));
|
||||
MethodInfo journalGeneric = journalRead.MakeGenericMethod(eventClass);
|
||||
object entry = journalGeneric.Invoke(null, new object[] { line });
|
||||
var journalEvent = new JournalEventArgs() { journalType = eventClass, journalEvent = entry };
|
||||
var handler = JournalEntry;
|
||||
|
||||
handler?.Invoke(this, journalEvent);
|
||||
|
||||
}
|
||||
|
||||
private void LogChangedEvent(object source, FileSystemEventArgs eventArgs)
|
||||
{
|
||||
var fileContent = ReadAllLines(eventArgs.FullPath);
|
||||
|
||||
if (currentLine[eventArgs.FullPath] == -1)
|
||||
{
|
||||
currentLine[eventArgs.FullPath] = fileContent.Count - 1;
|
||||
}
|
||||
|
||||
foreach(string line in fileContent.Skip(currentLine[eventArgs.FullPath]))
|
||||
{
|
||||
DeserializeAndInvoke(line);
|
||||
}
|
||||
|
||||
currentLine[eventArgs.FullPath] = fileContent.Count;
|
||||
}
|
||||
|
||||
private List<string> ReadAllLines(string path)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
using (StreamReader file = new StreamReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||||
{
|
||||
while (!file.EndOfStream)
|
||||
{
|
||||
lines.Add(file.ReadLine());
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private void LogCreatedEvent(object source, FileSystemEventArgs eventArgs)
|
||||
{
|
||||
currentLine[eventArgs.FullPath] = 0;
|
||||
LogChangedEvent(source, eventArgs);
|
||||
}
|
||||
|
||||
private void StatusUpdateEvent(object source, FileSystemEventArgs eventArgs)
|
||||
{
|
||||
var handler = StatusUpdate;
|
||||
var statusLines = ReadAllLines(eventArgs.FullPath);
|
||||
if (statusLines.Count > 0)
|
||||
{
|
||||
var status = JournalReader.ObservatoryDeserializer<Status>(statusLines[0]);
|
||||
handler?.Invoke(this, new JournalEventArgs() { journalType = typeof(Status), journalEvent = status });
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSavedGamesPath()
|
||||
{
|
||||
if (Environment.OSVersion.Version.Major < 6) throw new NotSupportedException();
|
||||
IntPtr pathPtr = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
Guid FolderSavedGames = new Guid("4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4");
|
||||
SHGetKnownFolderPath(ref FolderSavedGames, 0, IntPtr.Zero, out pathPtr);
|
||||
return Marshal.PtrToStringUni(pathPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeCoTaskMem(pathPtr);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern int SHGetKnownFolderPath(ref Guid id, int flags, IntPtr token, out IntPtr path);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
23
ObservatoryCore/ObservatoryCore.cs
Normal file
23
ObservatoryCore/ObservatoryCore.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Observatory
|
||||
{
|
||||
class ObservatoryCore
|
||||
{
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
return AppBuilder.Configure<UI.MainApplication>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace()
|
||||
.UseReactiveUI();
|
||||
}
|
||||
}
|
||||
}
|
59
ObservatoryCore/ObservatoryCore.csproj
Normal file
59
ObservatoryCore/ObservatoryCore.csproj
Normal file
@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>Observatory</RootNamespace>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<AssemblyOriginatorKeyFile>ObservatoryKey.snk</AssemblyOriginatorKeyFile>
|
||||
<!--<PublishTrimmed>true</PublishTrimmed>-->
|
||||
<TrimMode>Link</TrimMode>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.3" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.3" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.3" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.3" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="UI\Assets\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ObservatoryFramework">
|
||||
<HintPath>..\..\ObservatoryFramework\ObservatoryFramework\bin\Release\net5.0\ObservatoryFramework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Core.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Core.settings</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UI\Views\CoreView.axaml.cs">
|
||||
<DependentUpon>CoreView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UI\Views\NotificationView.axaml.cs">
|
||||
<DependentUpon>NotificationView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Core.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Core.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
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
|
||||
}
|
||||
}
|
||||
}
|
62
ObservatoryCore/Properties/Core.Designer.cs
generated
Normal file
62
ObservatoryCore/Properties/Core.Designer.cs
generated
Normal file
@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Observatory.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
|
||||
internal sealed partial class Core : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Core defaultInstance = ((Core)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Core())));
|
||||
|
||||
public static Core Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string JournalFolder {
|
||||
get {
|
||||
return ((string)(this["JournalFolder"]));
|
||||
}
|
||||
set {
|
||||
this["JournalFolder"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool AllowUnsigned {
|
||||
get {
|
||||
return ((bool)(this["AllowUnsigned"]));
|
||||
}
|
||||
set {
|
||||
this["AllowUnsigned"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool NativeNotify {
|
||||
get {
|
||||
return ((bool)(this["NativeNotify"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotify"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
ObservatoryCore/Properties/Core.settings
Normal file
15
ObservatoryCore/Properties/Core.settings
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Observatory.Properties" GeneratedClassName="Core">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="JournalFolder" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="AllowUnsigned" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotify" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
13
ObservatoryCore/UI/MainApplication.axaml
Normal file
13
ObservatoryCore/UI/MainApplication.axaml
Normal file
@ -0,0 +1,13 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Observatory.UI"
|
||||
x:Class="Observatory.UI.MainApplication">
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark"/>
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
|
||||
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
29
ObservatoryCore/UI/MainApplication.axaml.cs
Normal file
29
ObservatoryCore/UI/MainApplication.axaml.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Observatory.UI.ViewModels;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public class MainApplication : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var pluginManager = PluginManagement.PluginManager.GetInstance;
|
||||
desktop.MainWindow = new Views.MainWindow()
|
||||
{
|
||||
DataContext = new MainWindowViewModel(pluginManager)
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
14
ObservatoryCore/UI/Models/BasicUIModel.cs
Normal file
14
ObservatoryCore/UI/Models/BasicUIModel.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.UI.Models
|
||||
{
|
||||
public class BasicUIModel
|
||||
{
|
||||
public string Time { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
17
ObservatoryCore/UI/Models/CoreModel.cs
Normal file
17
ObservatoryCore/UI/Models/CoreModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Observatory.UI.ViewModels;
|
||||
|
||||
namespace Observatory.UI.Models
|
||||
{
|
||||
public class CoreModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public ViewModelBase UI { get; set; }
|
||||
|
||||
}
|
||||
}
|
14
ObservatoryCore/UI/Models/NotificationModel.cs
Normal file
14
ObservatoryCore/UI/Models/NotificationModel.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.UI.Models
|
||||
{
|
||||
public class NotificationModel
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Detail { get; set; }
|
||||
}
|
||||
}
|
31
ObservatoryCore/UI/TabTemplateSelector.cs
Normal file
31
ObservatoryCore/UI/TabTemplateSelector.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Metadata;
|
||||
using Observatory.UI.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public class TabTemplateSelector : IDataTemplate
|
||||
{
|
||||
public bool SupportsRecycling => false;
|
||||
|
||||
[Content]
|
||||
public Dictionary<string, IDataTemplate> Templates { get; } = new Dictionary<string, IDataTemplate>();
|
||||
|
||||
|
||||
public IControl Build(object param)
|
||||
{
|
||||
return new BasicUIView(); //Templates[param].Build(param);
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
return data is BasicUIView;
|
||||
}
|
||||
}
|
||||
}
|
32
ObservatoryCore/UI/ViewLocator.cs
Normal file
32
ObservatoryCore/UI/ViewLocator.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Observatory.UI.ViewModels;
|
||||
using System;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public bool SupportsRecycling => false;
|
||||
|
||||
public IControl Build(object data)
|
||||
{
|
||||
var name = data.GetType().FullName!.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
}
|
67
ObservatoryCore/UI/ViewModels/BasicUIViewModel.cs
Normal file
67
ObservatoryCore/UI/ViewModels/BasicUIViewModel.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Data;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.ObjectModel;
|
||||
using Observatory.UI.Models;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Linq;
|
||||
using Observatory.Framework;
|
||||
|
||||
namespace Observatory.UI.ViewModels
|
||||
{
|
||||
public class BasicUIViewModel : ViewModelBase
|
||||
{
|
||||
private ObservableCollection<object> basicUIGrid;
|
||||
|
||||
|
||||
public ObservableCollection<object> BasicUIGrid
|
||||
{
|
||||
get => basicUIGrid;
|
||||
set
|
||||
{
|
||||
basicUIGrid = value;
|
||||
this.RaisePropertyChanged(nameof(BasicUIGrid));
|
||||
}
|
||||
}
|
||||
|
||||
public BasicUIViewModel(ObservableCollection<object> BasicUIGrid)
|
||||
{
|
||||
|
||||
this.BasicUIGrid = new();
|
||||
this.BasicUIGrid = BasicUIGrid;
|
||||
|
||||
//// Create a timer and set a two second interval.
|
||||
//var aTimer = new System.Timers.Timer();
|
||||
//aTimer.Interval = 2000;
|
||||
|
||||
//// Hook up the Elapsed event for the timer.
|
||||
//aTimer.Elapsed += OnTimedEvent;
|
||||
|
||||
//// Have the timer fire repeated events (true is the default)
|
||||
//aTimer.AutoReset = true;
|
||||
|
||||
//// Start the timer
|
||||
//aTimer.Enabled = true;
|
||||
}
|
||||
|
||||
private PluginUI.UIType uiType;
|
||||
|
||||
public PluginUI.UIType UIType
|
||||
{
|
||||
get => uiType;
|
||||
set
|
||||
{
|
||||
uiType = value;
|
||||
this.RaisePropertyChanged(nameof(UIType));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
basicUIGrid.Count();
|
||||
}
|
||||
}
|
||||
}
|
110
ObservatoryCore/UI/ViewModels/CoreViewModel.cs
Normal file
110
ObservatoryCore/UI/ViewModels/CoreViewModel.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using Observatory.UI.Models;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Observatory.UI.ViewModels
|
||||
{
|
||||
public class CoreViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ObservableCollection<IObservatoryNotifier> notifiers;
|
||||
private readonly ObservableCollection<IObservatoryWorker> workers;
|
||||
private readonly ObservableCollection<CoreModel> tabs;
|
||||
private string toggleButtonText;
|
||||
|
||||
public CoreViewModel(IEnumerable<(IObservatoryWorker plugin, PluginManagement.PluginManager.PluginStatus signed)> workers, IEnumerable<(IObservatoryNotifier plugin, PluginManagement.PluginManager.PluginStatus signed)> notifiers)
|
||||
{
|
||||
this.notifiers = new ObservableCollection<IObservatoryNotifier>(notifiers.Select(p => p.plugin));
|
||||
this.workers = new ObservableCollection<IObservatoryWorker>(workers.Select(p => p.plugin));
|
||||
ToggleButtonText = "Start Monitor";
|
||||
tabs = new ObservableCollection<CoreModel>();
|
||||
|
||||
foreach(var worker in workers.Select(p => p.plugin))
|
||||
{
|
||||
if (worker.PluginUI.PluginUIType == Framework.PluginUI.UIType.Basic)
|
||||
{
|
||||
CoreModel coreModel = new();
|
||||
coreModel.Name = worker.ShortName;
|
||||
coreModel.UI = new();
|
||||
var uiViewModel = new BasicUIViewModel(worker.PluginUI.DataGrid)
|
||||
{
|
||||
UIType = worker.PluginUI.PluginUIType
|
||||
};
|
||||
coreModel.UI = uiViewModel;
|
||||
|
||||
tabs.Add(coreModel);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(var notifier in notifiers.Select(p => p.plugin))
|
||||
{
|
||||
Panel notifierPanel = new Panel();
|
||||
TextBlock notifierTextBlock = new TextBlock();
|
||||
notifierTextBlock.Text = notifier.Name;
|
||||
notifierPanel.Children.Add(notifierTextBlock);
|
||||
//tabs.Add(new CoreModel() { Name = notifier.ShortName, UI = (ViewModelBase)notifier.UI });
|
||||
}
|
||||
|
||||
|
||||
tabs.Add(new CoreModel() { Name = "Core", UI = new BasicUIViewModel(new ObservableCollection<object>()) { UIType = Framework.PluginUI.UIType.Core } });
|
||||
|
||||
}
|
||||
|
||||
public void ReadAll()
|
||||
{
|
||||
foreach (var worker in workers)
|
||||
{
|
||||
worker.ReadAllStarted();
|
||||
}
|
||||
LogMonitor.GetInstance.ReadAllJournals();
|
||||
}
|
||||
|
||||
public void ToggleMonitor()
|
||||
{
|
||||
var logMonitor = LogMonitor.GetInstance;
|
||||
|
||||
if (logMonitor.IsMonitoring())
|
||||
{
|
||||
logMonitor.Stop();
|
||||
ToggleButtonText = "Start Monitor";
|
||||
}
|
||||
else
|
||||
{
|
||||
logMonitor.Start();
|
||||
ToggleButtonText = "Stop Monitor";
|
||||
}
|
||||
}
|
||||
|
||||
public string ToggleButtonText
|
||||
{
|
||||
get => toggleButtonText;
|
||||
set
|
||||
{
|
||||
if (toggleButtonText != value)
|
||||
{
|
||||
toggleButtonText = value;
|
||||
this.RaisePropertyChanged(nameof(ToggleButtonText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<IObservatoryWorker> Workers
|
||||
{
|
||||
get { return workers; }
|
||||
}
|
||||
|
||||
public ObservableCollection<IObservatoryNotifier> Notifiers
|
||||
{
|
||||
get { return notifiers; }
|
||||
}
|
||||
|
||||
public ObservableCollection<CoreModel> Tabs
|
||||
{
|
||||
get { return tabs; }
|
||||
}
|
||||
}
|
||||
}
|
16
ObservatoryCore/UI/ViewModels/MainWindowViewModel.cs
Normal file
16
ObservatoryCore/UI/ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Observatory.UI.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
public MainWindowViewModel(PluginManagement.PluginManager pluginManager)
|
||||
{
|
||||
core = new CoreViewModel(pluginManager.workerPlugins, pluginManager.notifyPlugins);
|
||||
}
|
||||
|
||||
public CoreViewModel core { get; }
|
||||
}
|
||||
}
|
18
ObservatoryCore/UI/ViewModels/NotificationViewModel.cs
Normal file
18
ObservatoryCore/UI/ViewModels/NotificationViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.UI.ViewModels
|
||||
{
|
||||
public class NotificationViewModel : ViewModelBase
|
||||
{
|
||||
public NotificationViewModel(string title, string detail)
|
||||
{
|
||||
Notification = new() { Title = title, Detail = detail };
|
||||
}
|
||||
|
||||
public Models.NotificationModel Notification;
|
||||
}
|
||||
}
|
11
ObservatoryCore/UI/ViewModels/ViewModelBase.cs
Normal file
11
ObservatoryCore/UI/ViewModels/ViewModelBase.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Observatory.UI.ViewModels
|
||||
{
|
||||
public class ViewModelBase : ReactiveObject
|
||||
{
|
||||
}
|
||||
}
|
10
ObservatoryCore/UI/Views/BasicUIView.axaml
Normal file
10
ObservatoryCore/UI/Views/BasicUIView.axaml
Normal file
@ -0,0 +1,10 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Observatory.UI.Views.BasicUIView">
|
||||
<Panel Name="UIPanel">
|
||||
|
||||
</Panel>
|
||||
</UserControl>
|
404
ObservatoryCore/UI/Views/BasicUIView.axaml.cs
Normal file
404
ObservatoryCore/UI/Views/BasicUIView.axaml.cs
Normal file
@ -0,0 +1,404 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Text.RegularExpressions;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System.Linq;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Observatory.UI.Views
|
||||
{
|
||||
public class BasicUIView : UserControl
|
||||
{
|
||||
public BasicUIView()
|
||||
{
|
||||
Initialized += OnInitialized;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<BasicUIView, PluginUI.UIType> UITypeProperty =
|
||||
AvaloniaProperty.RegisterDirect<BasicUIView, PluginUI.UIType>(
|
||||
nameof(UIType),
|
||||
o => o.UIType,
|
||||
(o, v) => o.UIType = v,
|
||||
PluginUI.UIType.None,
|
||||
BindingMode.OneWay
|
||||
);
|
||||
|
||||
public PluginUI.UIType UIType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _uitype;
|
||||
}
|
||||
set
|
||||
{
|
||||
_uitype = value;
|
||||
UITypeChange();
|
||||
}
|
||||
}
|
||||
|
||||
private PluginUI.UIType _uitype;
|
||||
|
||||
|
||||
private void ColumnGeneration(object sender, DataGridAutoGeneratingColumnEventArgs e)
|
||||
{
|
||||
e.Column.Header = SplitCamelCase(e.PropertyName);
|
||||
e.Column.CanUserReorder = true;
|
||||
e.Column.CanUserResize = true;
|
||||
e.Column.CanUserSort = true;
|
||||
}
|
||||
|
||||
private void OnInitialized(object sender, System.EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void UITypeChange()
|
||||
{
|
||||
var uiPanel = this.Find<Panel>("UIPanel");
|
||||
|
||||
switch (UIType)
|
||||
{
|
||||
case PluginUI.UIType.None:
|
||||
break;
|
||||
case PluginUI.UIType.Basic:
|
||||
DataGrid dataGrid = new()
|
||||
{
|
||||
[!DataGrid.ItemsProperty] = new Binding("BasicUIGrid"),
|
||||
SelectionMode = DataGridSelectionMode.Extended,
|
||||
GridLinesVisibility = DataGridGridLinesVisibility.Vertical,
|
||||
AutoGenerateColumns = true
|
||||
};
|
||||
dataGrid.AutoGeneratingColumn += ColumnGeneration;
|
||||
uiPanel.Children.Clear();
|
||||
uiPanel.Children.Add(dataGrid);
|
||||
break;
|
||||
case PluginUI.UIType.Avalonia:
|
||||
break;
|
||||
case PluginUI.UIType.Core:
|
||||
uiPanel.Children.Clear();
|
||||
ScrollViewer scrollViewer = new();
|
||||
scrollViewer.Content = GenerateCoreUI();
|
||||
uiPanel.Children.Add(scrollViewer);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Grid GenerateCoreUI()
|
||||
{
|
||||
|
||||
Grid corePanel = new();
|
||||
|
||||
ColumnDefinitions columns = new()
|
||||
{
|
||||
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) },
|
||||
new ColumnDefinition() { Width = new GridLength(300) },
|
||||
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) }
|
||||
};
|
||||
corePanel.ColumnDefinitions = columns;
|
||||
|
||||
RowDefinitions rows = new()
|
||||
{
|
||||
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) },
|
||||
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) }
|
||||
};
|
||||
corePanel.RowDefinitions = rows;
|
||||
|
||||
var pluginManager = PluginManagement.PluginManager.GetInstance;
|
||||
|
||||
#region Journal Location
|
||||
TextBlock journalPathLabel = new()
|
||||
{
|
||||
Text = "Journal Path: ",
|
||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
|
||||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
||||
};
|
||||
|
||||
TextBox journalPath = new()
|
||||
{
|
||||
Text = Properties.Core.Default.JournalFolder
|
||||
};
|
||||
|
||||
Button journalBrowse = new()
|
||||
{
|
||||
Content = "Browse",
|
||||
Height = 30,
|
||||
Width = 100,
|
||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
|
||||
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center
|
||||
};
|
||||
|
||||
journalBrowse.Click += (object source, RoutedEventArgs e) =>
|
||||
{
|
||||
OpenFolderDialog openFolderDialog = new()
|
||||
{
|
||||
Directory = journalPath.Text
|
||||
};
|
||||
var browseTask = openFolderDialog.ShowAsync((Window)((Button)source).GetVisualRoot());
|
||||
string path = browseTask.Result;
|
||||
if (path != string.Empty)
|
||||
{
|
||||
journalPath.Text = path;
|
||||
Properties.Core.Default.JournalFolder = path;
|
||||
Properties.Core.Default.Save();
|
||||
}
|
||||
};
|
||||
|
||||
corePanel.AddControl(journalPathLabel, 1, 0);
|
||||
corePanel.AddControl(journalPath, 1, 1);
|
||||
corePanel.AddControl(journalBrowse, 1, 2);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plugin List
|
||||
DataGrid pluginList = new() { Margin = new Thickness(0, 20) };
|
||||
|
||||
pluginList.Columns.Add(new DataGridTextColumn()
|
||||
{
|
||||
Header = "Plugin",
|
||||
Binding = new Binding("Name")
|
||||
});
|
||||
|
||||
pluginList.Columns.Add(new DataGridTextColumn()
|
||||
{
|
||||
Header = "Version",
|
||||
Binding = new Binding("Version")
|
||||
});
|
||||
|
||||
pluginList.Columns.Add(new DataGridTextColumn()
|
||||
{
|
||||
Header = "Status",
|
||||
Binding = new Binding("Status")
|
||||
});
|
||||
|
||||
System.Collections.Generic.List<PluginView> allPlugins = new();
|
||||
|
||||
foreach(var (plugin, signed) in pluginManager.workerPlugins)
|
||||
{
|
||||
allPlugins.Add(new PluginView() { Name = plugin.Name, Version = plugin.Version, Status = GetStatusText(signed) });
|
||||
}
|
||||
|
||||
foreach (var (plugin, signed) in pluginManager.notifyPlugins)
|
||||
{
|
||||
allPlugins.Add(new PluginView() { Name = plugin.Name, Version = plugin.Version, Status = GetStatusText(signed) });
|
||||
}
|
||||
|
||||
pluginList.Items = allPlugins;
|
||||
corePanel.AddControl(pluginList, 0, 0, 2);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plugin Settings
|
||||
|
||||
foreach(var plugin in pluginManager.workerPlugins.Select(p => p.plugin))
|
||||
{
|
||||
GeneratePluginSettingUI(corePanel, plugin);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return corePanel;
|
||||
}
|
||||
|
||||
private void GeneratePluginSettingUI(Grid gridPanel, IObservatoryPlugin plugin)
|
||||
{
|
||||
//var plugin = pluginSettings.Key;
|
||||
|
||||
var displayedSettings = PluginManagement.PluginManager.GetSettingDisplayNames(plugin.Settings);
|
||||
|
||||
if (displayedSettings.Count > 0)
|
||||
{
|
||||
Expander expander = new()
|
||||
{
|
||||
Header = $"{plugin.Name} - {plugin.Version}",
|
||||
DataContext = plugin.Settings,
|
||||
Margin = new Thickness(0, 20)
|
||||
};
|
||||
|
||||
Grid settingsGrid = new();
|
||||
ColumnDefinitions settingColumns = new()
|
||||
{
|
||||
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
|
||||
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
|
||||
new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }
|
||||
};
|
||||
settingsGrid.ColumnDefinitions = settingColumns;
|
||||
expander.Content = settingsGrid;
|
||||
|
||||
int nextRow = gridPanel.RowDefinitions.Count;
|
||||
gridPanel.RowDefinitions.Add(new RowDefinition());
|
||||
gridPanel.AddControl(expander, nextRow, 0, 3);
|
||||
|
||||
foreach (var setting in displayedSettings.Where(s => !System.Attribute.IsDefined(s.Key, typeof(SettingIgnore))))
|
||||
{
|
||||
if (setting.Key.PropertyType != typeof(bool) || settingsGrid.Children.Count % 2 == 0)
|
||||
{
|
||||
settingsGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(21) });
|
||||
}
|
||||
|
||||
TextBlock label = new() { Text = setting.Value };
|
||||
|
||||
switch (setting.Key.GetValue(plugin.Settings))
|
||||
{
|
||||
case bool boolSetting:
|
||||
CheckBox checkBox = new() { IsChecked = boolSetting, Content = label };
|
||||
|
||||
checkBox.Checked += (object sender, RoutedEventArgs e) =>
|
||||
{
|
||||
setting.Key.SetValue(plugin.Settings, true);
|
||||
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
|
||||
};
|
||||
|
||||
checkBox.Unchecked += (object sender, RoutedEventArgs e) =>
|
||||
{
|
||||
setting.Key.SetValue(plugin.Settings, false);
|
||||
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
|
||||
};
|
||||
|
||||
//settingsGrid.Children.Add(checkBox);
|
||||
settingsGrid.AddControl(checkBox, settingsGrid.RowDefinitions.Count - 1, settingsGrid.Children.Count % 2 == 0 ? 0 : 1);
|
||||
|
||||
break;
|
||||
case string stringSetting:
|
||||
TextBox textBox = new() { Text = stringSetting };
|
||||
settingsGrid.Children.Add(label);
|
||||
settingsGrid.Children.Add(textBox);
|
||||
break;
|
||||
case int intSetting:
|
||||
NumericUpDown numericUpDown = new() { Text = intSetting.ToString(), AllowSpin = true };
|
||||
settingsGrid.Children.Add(label);
|
||||
settingsGrid.Children.Add(numericUpDown);
|
||||
break;
|
||||
case System.IO.FileInfo fileSetting:
|
||||
label.Text += ": ";
|
||||
|
||||
TextBox settingPath = new()
|
||||
{
|
||||
Text = fileSetting.FullName,
|
||||
Width = 250,
|
||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right
|
||||
};
|
||||
|
||||
Button settingBrowse = new()
|
||||
{
|
||||
Content = "Browse",
|
||||
Height = 30,
|
||||
Width = 100,
|
||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left,
|
||||
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center
|
||||
};
|
||||
|
||||
settingBrowse.Click += (object source, RoutedEventArgs e) =>
|
||||
{
|
||||
OpenFileDialog openFileDialog = new()
|
||||
{
|
||||
Directory = fileSetting.DirectoryName,
|
||||
AllowMultiple = false
|
||||
};
|
||||
var browseTask = openFileDialog.ShowAsync((Window)((Button)source).GetVisualRoot());
|
||||
|
||||
if (browseTask.Result.Count() > 0)
|
||||
{
|
||||
string path = browseTask.Result[0];
|
||||
settingPath.Text = path;
|
||||
|
||||
setting.Key.SetValue(plugin.Settings, new System.IO.FileInfo(path));
|
||||
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
StackPanel stackPanel = new() { Orientation = Avalonia.Layout.Orientation.Horizontal };
|
||||
stackPanel.Children.Add(label);
|
||||
stackPanel.Children.Add(settingPath);
|
||||
|
||||
settingsGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(21) });
|
||||
//settingsGrid.AddControl(label, settingsGrid.RowDefinitions.Count - 1, 0, 2);
|
||||
settingsGrid.AddControl(stackPanel, settingsGrid.RowDefinitions.Count - 1, 0, 2);
|
||||
settingsGrid.AddControl(settingBrowse, settingsGrid.RowDefinitions.Count - 1, 2);
|
||||
|
||||
break;
|
||||
}
|
||||
//wrapPanel.Children.Add(panel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStatusText(PluginManagement.PluginManager.PluginStatus status)
|
||||
{
|
||||
string statusText;
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case PluginManagement.PluginManager.PluginStatus.Signed:
|
||||
statusText = "Signed";
|
||||
break;
|
||||
case PluginManagement.PluginManager.PluginStatus.Unsigned:
|
||||
statusText = "Unsigned";
|
||||
break;
|
||||
case PluginManagement.PluginManager.PluginStatus.InvalidSignature:
|
||||
statusText = "Signature Invalid";
|
||||
break;
|
||||
case PluginManagement.PluginManager.PluginStatus.InvalidPlugin:
|
||||
statusText = "No Interface";
|
||||
break;
|
||||
case PluginManagement.PluginManager.PluginStatus.InvalidLibrary:
|
||||
statusText = "Invalid Library";
|
||||
break;
|
||||
default:
|
||||
statusText = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
return statusText;
|
||||
}
|
||||
|
||||
//From https://stackoverflow.com/questions/5796383/insert-spaces-between-words-on-a-camel-cased-token
|
||||
private static string SplitCamelCase(string str)
|
||||
{
|
||||
return Regex.Replace(
|
||||
Regex.Replace(
|
||||
str,
|
||||
@"(\P{Ll})(\P{Ll}\p{Ll})",
|
||||
"$1 $2"
|
||||
),
|
||||
@"(\p{Ll})(\P{Ll})",
|
||||
"$1 $2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal class PluginView
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
internal static class GridExtention
|
||||
{
|
||||
public static void AddControl(this Grid grid, Control control, int row, int column, int span = 1)
|
||||
{
|
||||
grid.Children.Add(control);
|
||||
Grid.SetColumnSpan(control, span);
|
||||
Grid.SetColumn(control, column);
|
||||
Grid.SetRow(control, row);
|
||||
}
|
||||
}
|
||||
}
|
43
ObservatoryCore/UI/Views/CoreView.axaml
Normal file
43
ObservatoryCore/UI/Views/CoreView.axaml
Normal file
@ -0,0 +1,43 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vw="clr-namespace:Observatory.UI.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Observatory.UI.Views.CoreView">
|
||||
<Grid RowDefinitions="30,*,Auto">
|
||||
<TextBlock Grid.Row="0" VerticalAlignment="Center" Padding="10,0,0,0">
|
||||
Elite Observatory - v1.0core
|
||||
</TextBlock>
|
||||
<TabControl Name="CoreTabs"
|
||||
Grid.Row="1" Grid.Column="1"
|
||||
VerticalAlignment="Stretch"
|
||||
TabStripPlacement="Left"
|
||||
Items="{Binding Tabs}"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1">
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
</TabItem.Header>
|
||||
</TabItem>
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<vw:BasicUIView DataContext="{Binding UI}" UIType="{Binding UIType}"/>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
<WrapPanel Height="50" Grid.Row="2" Grid.Column="0" VerticalAlignment="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Name="ToggleMonitor" Margin="10" Command="{Binding ToggleMonitor}" Content="{Binding ToggleButtonText}">
|
||||
Start Monitor
|
||||
</Button>
|
||||
<Button Name="ReadAll" Margin="10" Command="{Binding ReadAll}">
|
||||
Read All
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
21
ObservatoryCore/UI/Views/CoreView.axaml.cs
Normal file
21
ObservatoryCore/UI/Views/CoreView.axaml.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Linq;
|
||||
|
||||
namespace Observatory.UI.Views
|
||||
{
|
||||
public class CoreView : UserControl
|
||||
{
|
||||
public CoreView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
11
ObservatoryCore/UI/Views/MainWindow.axaml
Normal file
11
ObservatoryCore/UI/Views/MainWindow.axaml
Normal file
@ -0,0 +1,11 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:views="clr-namespace:Observatory.UI.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Observatory.UI.Views.MainWindow"
|
||||
Title="Elite Observatory"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
Content="{Binding core}">
|
||||
</Window>
|
26
ObservatoryCore/UI/Views/MainWindow.axaml.cs
Normal file
26
ObservatoryCore/UI/Views/MainWindow.axaml.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
|
||||
|
||||
namespace Observatory.UI.Views
|
||||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
13
ObservatoryCore/UI/Views/NotificationView.axaml
Normal file
13
ObservatoryCore/UI/Views/NotificationView.axaml
Normal file
@ -0,0 +1,13 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Observatory.UI.Views.NotificationView"
|
||||
|
||||
Title="Notification">
|
||||
<StackPanel DataContext="{Binding Notification}">
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
<TextBlock Text="{Binding Detail}" />
|
||||
</StackPanel>
|
||||
</Window>
|
22
ObservatoryCore/UI/Views/NotificationView.axaml.cs
Normal file
22
ObservatoryCore/UI/Views/NotificationView.axaml.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Observatory.UI.Views
|
||||
{
|
||||
public partial class NotificationView : Window
|
||||
{
|
||||
public NotificationView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user