embed plugin loader directly into the launcher
This commit is contained in:
268
PluginLoader/Data/LocalFolderPlugin.cs
Normal file
268
PluginLoader/Data/LocalFolderPlugin.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using PluginLoader.Compiler;
|
||||
using PluginLoader.GUI;
|
||||
using Sandbox.Graphics.GUI;
|
||||
using VRage;
|
||||
|
||||
namespace PluginLoader.Data;
|
||||
|
||||
public class LocalFolderPlugin : PluginData
|
||||
{
|
||||
private const string XmlDataType = "Xml files (*.xml)|*.xml|All files (*.*)|*.*";
|
||||
private const int GitTimeout = 10000;
|
||||
private string[] sourceDirectories;
|
||||
|
||||
public LocalFolderPlugin(Config settings)
|
||||
{
|
||||
Id = settings.Folder;
|
||||
FriendlyName = Path.GetFileName(Id);
|
||||
Status = PluginStatus.None;
|
||||
FolderSettings = settings;
|
||||
DeserializeFile(settings.DataFile);
|
||||
}
|
||||
|
||||
private LocalFolderPlugin(string folder)
|
||||
{
|
||||
Id = folder;
|
||||
Status = PluginStatus.None;
|
||||
FolderSettings = new()
|
||||
{
|
||||
Folder = folder
|
||||
};
|
||||
}
|
||||
|
||||
public override string Source => MyTexts.GetString(MyCommonTexts.Local);
|
||||
|
||||
public Config FolderSettings { get; }
|
||||
|
||||
public override Assembly GetAssembly()
|
||||
{
|
||||
if (Directory.Exists(Id))
|
||||
{
|
||||
var compiler = new RoslynCompiler(FolderSettings.DebugBuild);
|
||||
var hasFile = false;
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("Compiling files from ").Append(Id).Append(":").AppendLine();
|
||||
foreach (var file in GetProjectFiles(Id))
|
||||
using (var fileStream = File.OpenRead(file))
|
||||
{
|
||||
hasFile = true;
|
||||
var name = file.Substring(Id.Length + 1, file.Length - (Id.Length + 1));
|
||||
sb.Append(name).Append(", ");
|
||||
compiler.Load(fileStream, file);
|
||||
}
|
||||
|
||||
if (hasFile)
|
||||
{
|
||||
sb.Length -= 2;
|
||||
LogFile.WriteLine(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("No files were found in the directory specified.");
|
||||
}
|
||||
|
||||
var data = compiler.Compile(FriendlyName + '_' + Path.GetRandomFileName(), out var symbols);
|
||||
var a = Assembly.Load(data, symbols);
|
||||
Version = a.GetName().Version;
|
||||
return a;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Unable to find directory '" + Id + "'");
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetProjectFiles(string folder)
|
||||
{
|
||||
string gitError = null;
|
||||
try
|
||||
{
|
||||
var p = new Process();
|
||||
|
||||
// Redirect the output stream of the child process.
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.RedirectStandardError = true;
|
||||
p.StartInfo.FileName = "git";
|
||||
p.StartInfo.Arguments = "ls-files --cached --others --exclude-standard";
|
||||
p.StartInfo.WorkingDirectory = folder;
|
||||
p.Start();
|
||||
|
||||
// Do not wait for the child process to exit before
|
||||
// reading to the end of its redirected stream.
|
||||
// Read the output stream first and then wait.
|
||||
var gitOutput = p.StandardOutput.ReadToEnd();
|
||||
gitError = p.StandardError.ReadToEnd();
|
||||
if (!p.WaitForExit(GitTimeout))
|
||||
{
|
||||
p.Kill();
|
||||
throw new TimeoutException("Git operation timed out.");
|
||||
}
|
||||
|
||||
if (p.ExitCode == 0)
|
||||
{
|
||||
var files = gitOutput.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return files.Where(x => x.EndsWith(".cs") && IsValidProjectFile(x))
|
||||
.Select(x => Path.Combine(folder, x.Trim().Replace('/', Path.DirectorySeparatorChar)))
|
||||
.Where(x => File.Exists(x));
|
||||
}
|
||||
|
||||
var sb = new StringBuilder("An error occurred while checking git for project files.").AppendLine();
|
||||
if (!string.IsNullOrWhiteSpace(gitError))
|
||||
{
|
||||
sb.AppendLine("Git output: ");
|
||||
sb.Append(gitError).AppendLine();
|
||||
}
|
||||
|
||||
LogFile.WriteLine(sb.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var sb = new StringBuilder("An error occurred while checking git for project files.").AppendLine();
|
||||
if (!string.IsNullOrWhiteSpace(gitError))
|
||||
{
|
||||
sb.AppendLine(" Git output: ");
|
||||
sb.Append(gitError).AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("Exception: ");
|
||||
sb.Append(e).AppendLine();
|
||||
LogFile.WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
|
||||
var sep = Path.DirectorySeparatorChar;
|
||||
return Directory.EnumerateFiles(folder, "*.cs", SearchOption.AllDirectories)
|
||||
.Where(x => !x.Contains(sep + "bin" + sep) && !x.Contains(sep + "obj" + sep) &&
|
||||
IsValidProjectFile(x));
|
||||
}
|
||||
|
||||
private bool IsValidProjectFile(string file)
|
||||
{
|
||||
if (sourceDirectories == null || sourceDirectories.Length == 0)
|
||||
return true;
|
||||
file = file.Replace('\\', '/');
|
||||
foreach (var dir in sourceDirectories)
|
||||
if (file.StartsWith(dir))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
var folder = Path.GetFullPath(Id);
|
||||
if (Directory.Exists(folder))
|
||||
Process.Start("explorer.exe", $"\"{folder}\"");
|
||||
}
|
||||
|
||||
public override bool OpenContextMenu(MyGuiControlContextMenu menu)
|
||||
{
|
||||
menu.Clear();
|
||||
menu.AddItem(new("Remove"));
|
||||
menu.AddItem(new("Load data file"));
|
||||
if (FolderSettings.DebugBuild)
|
||||
menu.AddItem(new("Switch to release build"));
|
||||
else
|
||||
menu.AddItem(new("Switch to debug build"));
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void ContextMenuClicked(MyGuiScreenPluginConfig screen, MyGuiControlContextMenu.EventArgs args)
|
||||
{
|
||||
switch (args.ItemIndex)
|
||||
{
|
||||
case 0:
|
||||
Main.Instance.Config.PluginFolders.Remove(Id);
|
||||
screen.RemovePlugin(this);
|
||||
screen.RequireRestart();
|
||||
break;
|
||||
case 1:
|
||||
LoaderTools.OpenFileDialog("Open an xml data file", Path.GetDirectoryName(FolderSettings.DataFile),
|
||||
XmlDataType, file => DeserializeFile(file, screen));
|
||||
break;
|
||||
case 2:
|
||||
FolderSettings.DebugBuild = !FolderSettings.DebugBuild;
|
||||
screen.RequireRestart();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Deserializes a file and refreshes the plugin screen
|
||||
private void DeserializeFile(string file, MyGuiScreenPluginConfig screen = null)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var xml = new XmlSerializer(typeof(PluginData));
|
||||
|
||||
using (var reader = File.OpenText(file))
|
||||
{
|
||||
var resultObj = xml.Deserialize(reader);
|
||||
if (resultObj.GetType() != typeof(GitHubPlugin)) throw new("Xml file is not of type GitHubPlugin!");
|
||||
|
||||
var github = (GitHubPlugin)resultObj;
|
||||
github.Init(LoaderTools.PluginsDir);
|
||||
FriendlyName = github.FriendlyName;
|
||||
Tooltip = github.Tooltip;
|
||||
Author = github.Author;
|
||||
Description = github.Description;
|
||||
sourceDirectories = github.SourceDirectories;
|
||||
FolderSettings.DataFile = file;
|
||||
if (screen != null && screen.Visible && screen.IsOpened)
|
||||
screen.RefreshSidePanel();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogFile.WriteLine("Error while reading the xml file: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateNew(Action<LocalFolderPlugin> onComplete)
|
||||
{
|
||||
LoaderTools.OpenFolderDialog("Open the root of your project", LoaderTools.PluginsDir, folder =>
|
||||
{
|
||||
if (Main.Instance.List.Contains(folder))
|
||||
{
|
||||
MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error,
|
||||
messageText: new("That folder already exists in the list!"));
|
||||
return;
|
||||
}
|
||||
|
||||
var plugin = new LocalFolderPlugin(folder);
|
||||
LoaderTools.OpenFileDialog("Open the xml data file", folder, XmlDataType, file =>
|
||||
{
|
||||
plugin.DeserializeFile(file);
|
||||
onComplete(plugin);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public class Config
|
||||
{
|
||||
public Config()
|
||||
{
|
||||
}
|
||||
|
||||
public Config(string folder, string dataFile)
|
||||
{
|
||||
Folder = folder;
|
||||
DataFile = dataFile;
|
||||
}
|
||||
|
||||
public string Folder { get; set; }
|
||||
public string DataFile { get; set; }
|
||||
public bool DebugBuild { get; set; } = true;
|
||||
public bool Valid => Directory.Exists(Folder) && File.Exists(DataFile);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user