Files
se-launcher/PluginLoader/Data/LocalFolderPlugin.cs
zznty 9fb29d2011
All checks were successful
Build / Build Launcher (push) Successful in 2m31s
update logging and add pl splash as the main one
2024-05-31 17:12:08 +07:00

252 lines
8.3 KiB
C#

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.Log.Debug(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));
}
LogFile.Log.Error("An error occurred while checking git for project files. Git output: {GitOutput}", gitError);
}
catch (Exception e)
{
LogFile.Log.Error(e, "An error occurred while checking git for project files. Git output: {GitOutput}", gitError);
}
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.Log.Error(e, "Error while reading the xml file");
}
}
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);
}
}