From 135d1f4be80f821caaf9968389984693f8bc8d22 Mon Sep 17 00:00:00 2001 From: John Gross Date: Sat, 29 Apr 2017 13:28:24 -0700 Subject: [PATCH] Add GitHub plugin updater, refactor game update hook to TorchBase instead of PluginManager, fix world config not applying --- Torch.API/ITorchBase.cs | 3 +- Torch.API/ITorchConfig.cs | 15 +++ Torch.API/ServerState.cs | 16 +++ Torch.API/Torch.API.csproj | 2 + Torch.Client/Properties/AssemblyInfo.cs | 4 +- Torch.Client/TorchClient.cs | 2 +- Torch.Server/Program.cs | 29 ++++- Torch.Server/Properties/AssemblyInfo.cs | 4 +- Torch.Server/ServerStatistics.cs | 14 +++ Torch.Server/Torch.Server.csproj | 2 +- Torch.Server/TorchServer.cs | 107 +++++++----------- .../ViewModels/SessionSettingsViewModel.cs | 5 + Torch.Server/Views/ConfigControl.xaml.cs | 26 +++++ Torch.Server/Views/TorchUI.xaml | 2 +- Torch.Server/Views/TorchUI.xaml.cs | 6 +- Torch/Collections/RollingAverage.cs | 56 +++++++++ Torch/Managers/EntityManager.cs | 1 + Torch/Managers/MultiplayerManager.cs | 2 +- .../Managers/NetworkManager/NetworkManager.cs | 14 +-- Torch/Managers/PluginManager.cs | 73 ++++++------ Torch/PluginManifest.cs | 14 --- Torch/PluginOptions.cs | 21 ---- Torch/SteamService.cs | 15 ++- Torch/Torch.csproj | 5 +- Torch/TorchBase.cs | 39 ++++++- {Torch.Server => Torch}/TorchConfig.cs | 14 ++- Torch/Updater/PluginManifest.cs | 34 ++++++ Torch/Updater/PluginUpdater.cs | 83 ++++++++++++-- 28 files changed, 419 insertions(+), 189 deletions(-) create mode 100644 Torch.API/ITorchConfig.cs create mode 100644 Torch.API/ServerState.cs create mode 100644 Torch.Server/ServerStatistics.cs create mode 100644 Torch/Collections/RollingAverage.cs delete mode 100644 Torch/PluginManifest.cs delete mode 100644 Torch/PluginOptions.cs rename {Torch.Server => Torch}/TorchConfig.cs (84%) create mode 100644 Torch/Updater/PluginManifest.cs diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index f9fb84e..23a6313 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -13,6 +13,7 @@ namespace Torch.API event Action SessionLoaded; event Action SessionUnloading; event Action SessionUnloaded; + ITorchConfig Config { get; } IMultiplayer Multiplayer { get; } IPluginManager Plugins { get; } Version TorchVersion { get; } @@ -28,9 +29,7 @@ namespace Torch.API public interface ITorchServer : ITorchBase { - bool IsRunning { get; } string InstancePath { get; } - void Start(IMyConfigDedicated config); } public interface ITorchClient : ITorchBase diff --git a/Torch.API/ITorchConfig.cs b/Torch.API/ITorchConfig.cs new file mode 100644 index 0000000..52f4db8 --- /dev/null +++ b/Torch.API/ITorchConfig.cs @@ -0,0 +1,15 @@ +namespace Torch +{ + public interface ITorchConfig + { + //bool AutoRestart { get; set; } + //int Autosave { get; set; } + string InstanceName { get; set; } + string InstancePath { get; set; } + //bool LogChat { get; set; } + bool RedownloadPlugins { get; set; } + bool EnableAutomaticUpdates { get; set; } + + bool Save(string path = null); + } +} \ No newline at end of file diff --git a/Torch.API/ServerState.cs b/Torch.API/ServerState.cs new file mode 100644 index 0000000..b80e8e2 --- /dev/null +++ b/Torch.API/ServerState.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.API +{ + public enum ServerState + { + Stopped, + Starting, + Running, + Error + } +} diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj index d66de92..1438964 100644 --- a/Torch.API/Torch.API.csproj +++ b/Torch.API/Torch.API.csproj @@ -141,6 +141,7 @@ + @@ -149,6 +150,7 @@ + diff --git a/Torch.Client/Properties/AssemblyInfo.cs b/Torch.Client/Properties/AssemblyInfo.cs index 7aa4672..490fc9c 100644 --- a/Torch.Client/Properties/AssemblyInfo.cs +++ b/Torch.Client/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("1.0.105.498")] -[assembly: AssemblyFileVersion("1.0.105.498")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.119.399")] +[assembly: AssemblyFileVersion("1.0.119.399")] \ No newline at end of file diff --git a/Torch.Client/TorchClient.cs b/Torch.Client/TorchClient.cs index d164f31..92b26b6 100644 --- a/Torch.Client/TorchClient.cs +++ b/Torch.Client/TorchClient.cs @@ -43,7 +43,7 @@ namespace Torch.Client return; var appDataPath = _startup.GetAppDataPath(); - MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath, false); + MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath); MyInitializer.InitCheckSum(); if (!_startup.Check64Bit()) return; diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs index f2e62e4..da85f5e 100644 --- a/Torch.Server/Program.cs +++ b/Torch.Server/Program.cs @@ -40,7 +40,7 @@ namespace Torch.Server return; } - var configName = args.FirstOrDefault() ?? "TorchConfig.xml"; + var configName = /*args.FirstOrDefault() ??*/ "TorchConfig.xml"; var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName); TorchConfig options; if (File.Exists(configName)) @@ -55,6 +55,20 @@ namespace Torch.Server options.Save(configPath); } + bool gui = true; + foreach (var arg in args) + { + switch (arg) + { + case "-noupdate": + options.EnableAutomaticUpdates = false; + break; + case "-nogui": + gui = false; + break; + } + } + /* if (!parser.ParseArguments(args, options)) { @@ -118,9 +132,16 @@ namespace Torch.Server _server = new TorchServer(options); _server.Init(); - var ui = new TorchUI((TorchServer)_server); - ui.LoadConfig(options); - ui.ShowDialog(); + if (gui) + { + var ui = new TorchUI((TorchServer)_server); + ui.LoadConfig(options); + ui.ShowDialog(); + } + else + { + _server.Start(); + } } } } diff --git a/Torch.Server/Properties/AssemblyInfo.cs b/Torch.Server/Properties/AssemblyInfo.cs index 7aa4672..490fc9c 100644 --- a/Torch.Server/Properties/AssemblyInfo.cs +++ b/Torch.Server/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("1.0.105.498")] -[assembly: AssemblyFileVersion("1.0.105.498")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.119.399")] +[assembly: AssemblyFileVersion("1.0.119.399")] \ No newline at end of file diff --git a/Torch.Server/ServerStatistics.cs b/Torch.Server/ServerStatistics.cs new file mode 100644 index 0000000..c3cd6a2 --- /dev/null +++ b/Torch.Server/ServerStatistics.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Torch.Collections; + +namespace Torch.Server +{ + public class ServerStatistics + { + public RollingAverage SimSpeed { get; } = new RollingAverage(30); + } +} diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index 571a865..3cceeb1 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -167,7 +167,7 @@ AssemblyInfo.tt - + Component diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 7df9b2d..03bef53 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -1,40 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using NLog; -using Torch; -using Sandbox; -using Sandbox.Engine.Analytics; -using Sandbox.Engine.Multiplayer; +using Sandbox; using Sandbox.Engine.Utils; using Sandbox.Game; -using Sandbox.Game.Gui; using Sandbox.Game.World; -using SpaceEngineers.Game; +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Xml.Serialization; using SteamSDK; using Torch.API; using VRage.Dedicated; using VRage.FileSystem; using VRage.Game; -using VRage.Game.ModAPI; using VRage.Game.ObjectBuilder; using VRage.Game.SessionComponents; -using VRage.Profiler; +using VRage.Plugins; namespace Torch.Server { public class TorchServer : TorchBase, ITorchServer { public Thread GameThread { get; private set; } - public bool IsRunning { get; private set; } - public TorchConfig Config { get; } + public ServerState State { get; private set; } public string InstanceName => Config?.InstanceName; public string InstancePath => Config?.InstancePath; @@ -49,7 +36,7 @@ namespace Torch.Server { base.Init(); - Log.Info($"Init server instance '{Config.InstanceName}' at path '{Config.InstancePath}'"); + Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'"); MyFakes.ENABLE_INFINARIO = false; MyPerGameSettings.SendLogToKeen = false; @@ -60,21 +47,19 @@ namespace Torch.Server MySessionComponentExtDebug.ForceDisable = true; MyPerServerSettings.AppId = 244850; MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion; - //MyGlobalTypeMetadata.Static.Init(); - //TODO: Allows players to filter servers for Torch in the server browser. Need to init Steam before this - //SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch"); - } + MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly); + MyPlugins.RegisterGameObjectBuildersAssemblyFile(MyPerGameSettings.GameModObjBuildersAssembly); + MyPlugins.RegisterSandboxAssemblyFile(MyPerGameSettings.SandboxAssembly); + MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly); + MyPlugins.Load(); - public void SetConfig(IMyConfigDedicated config) - { - MySandboxGame.ConfigDedicated = config; - } + MyGlobalTypeMetadata.Static.Init(); + MyInitializer.InvokeBeforeRun( + MyPerServerSettings.AppId, + MyPerServerSettings.GameDSName, + InstancePath, DedicatedServer.AddDateToLog); - public void Start(IMyConfigDedicated config) - { - SetConfig(config); - Start(); } /// @@ -82,26 +67,35 @@ namespace Torch.Server /// public override void Start() { - if (IsRunning) + if (State > 0) throw new InvalidOperationException("Server is already running."); Config.Save(); - IsRunning = true; + State = ServerState.Starting; Log.Info("Starting server."); + var runInternal = typeof(DedicatedServer).GetMethod("RunInternal", BindingFlags.Static | BindingFlags.NonPublic); + MySandboxGame.IsDedicated = true; Environment.SetEnvironmentVariable("SteamAppId", MyPerServerSettings.AppId.ToString()); - Log.Trace("Invoking RunMain"); - try { Reflection.InvokeStaticMethod(typeof(DedicatedServer), "RunMain", Config.InstanceName, Config.InstancePath, false, true); } - catch (Exception e) + VRage.Service.ExitListenerSTA.OnExit += delegate { MySandboxGame.Static?.Exit(); }; + + do { - Log.Error("Error running server."); - Log.Error(e); - throw; - } - Log.Trace("RunMain completed"); - IsRunning = false; + runInternal.Invoke(null, null); + } while (MySandboxGame.IsReloading); + + MyInitializer.InvokeAfterRun(); + State = ServerState.Stopped; + } + + /// + public override void Init(object gameInstance) + { + base.Init(gameInstance); + State = ServerState.Running; + SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch"); } /// @@ -109,7 +103,7 @@ namespace Torch.Server /// public override void Stop() { - if (!IsRunning) + if (State == ServerState.Stopped) Log.Error("Server is already stopped"); if (Thread.CurrentThread.ManagedThreadId != GameThread?.ManagedThreadId && MySandboxGame.Static.IsRunning) @@ -125,31 +119,14 @@ namespace Torch.Server //Unload all the static junk. //TODO: Finish unloading all server data so it's in a completely clean state. - VRage.FileSystem.MyFileSystem.Reset(); + MyFileSystem.Reset(); VRage.Input.MyGuiGameControlsHelpers.Reset(); VRage.Input.MyInput.UnloadData(); //CleanupProfilers(); Log.Info("Server stopped."); _stopHandle.Set(); - IsRunning = false; + State = ServerState.Stopped; } - - /* - private string GetInstancePath(bool isService = false, string instanceName = "Torch") - { - if (isService) - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), MyPerServerSettings.GameDSName, instanceName); - - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), MyPerServerSettings.GameDSName); - }*/ - - /* - private void CleanupProfilers() - { - typeof(MyRenderProfiler).GetField("m_threadProfiler", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, null); - typeof(MyRenderProfiler).GetField("m_gpuProfiler", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, null); - (typeof(MyRenderProfiler).GetField("m_threadProfilers", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as List).Clear(); - }*/ } } diff --git a/Torch.Server/ViewModels/SessionSettingsViewModel.cs b/Torch.Server/ViewModels/SessionSettingsViewModel.cs index 60e2da4..a4ee779 100644 --- a/Torch.Server/ViewModels/SessionSettingsViewModel.cs +++ b/Torch.Server/ViewModels/SessionSettingsViewModel.cs @@ -331,5 +331,10 @@ namespace Torch.Server.ViewModels get { return _settings.ViewDistance; } set { _settings.WorldSizeKm = value; OnPropertyChanged(); } } + + public static implicit operator MyObjectBuilder_SessionSettings(SessionSettingsViewModel viewModel) + { + return viewModel._settings; + } } } diff --git a/Torch.Server/Views/ConfigControl.xaml.cs b/Torch.Server/Views/ConfigControl.xaml.cs index 482528d..56ee760 100644 --- a/Torch.Server/Views/ConfigControl.xaml.cs +++ b/Torch.Server/Views/ConfigControl.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -14,8 +15,12 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using NLog; +using Sandbox; +using Sandbox.Engine.Networking; using Sandbox.Engine.Utils; using Torch.Server.ViewModels; +using VRage.Dedicated; using VRage.Game; using Path = System.IO.Path; @@ -38,10 +43,31 @@ namespace Torch.Server.Views public void SaveConfig() { Config.Save(_configPath); + //TODO: make this work + try + { + var checkpoint = MyLocalCache.LoadCheckpoint(_viewModel.LoadWorld, out ulong size); + checkpoint.SessionName = _viewModel.WorldName; + checkpoint.Settings = _viewModel.SessionSettings; + checkpoint.Mods.Clear(); + foreach (var modId in _viewModel.Mods) + checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId)); + + Debug.Assert(checkpoint != null); + Debug.Assert(_viewModel.LoadWorld != null); + MyLocalCache.SaveCheckpoint(checkpoint, _viewModel.LoadWorld); + } + catch (Exception e) + { + var log = LogManager.GetLogger("Torch"); + log.Error("Failed to overwrite sandbox config, changes will not appear on server"); + log.Error(e); + } } public void LoadDedicatedConfig(TorchConfig torchConfig) { + MySandboxGame.Config = new MyConfig(MyPerServerSettings.GameNameSafe + ".cfg"); var path = Path.Combine(torchConfig.InstancePath, "SpaceEngineers-Dedicated.cfg"); if (!File.Exists(path)) diff --git a/Torch.Server/Views/TorchUI.xaml b/Torch.Server/Views/TorchUI.xaml index c8dd984..80a53fa 100644 --- a/Torch.Server/Views/TorchUI.xaml +++ b/Torch.Server/Views/TorchUI.xaml @@ -20,7 +20,7 @@ diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs index f21f6f5..548e02f 100644 --- a/Torch.Server/Views/TorchUI.xaml.cs +++ b/Torch.Server/Views/TorchUI.xaml.cs @@ -38,7 +38,7 @@ namespace Torch.Server public TorchUI(TorchServer server) { - _config = server.Config; + _config = (TorchConfig)server.Config; _server = server; InitializeComponent(); _startTime = DateTime.Now; @@ -84,7 +84,7 @@ namespace Torch.Server BtnStop.IsEnabled = true; _uiUpdate.Start(); ConfigControl.SaveConfig(); - new Thread(() => _server.Start(ConfigControl.Config)).Start(); + new Thread(_server.Start).Start(); } private void BtnStop_Click(object sender, RoutedEventArgs e) @@ -100,7 +100,7 @@ namespace Torch.Server protected override void OnClosing(CancelEventArgs e) { - if (_server?.IsRunning ?? false) + if (_server?.State == ServerState.Running) _server.Stop(); } diff --git a/Torch/Collections/RollingAverage.cs b/Torch/Collections/RollingAverage.cs new file mode 100644 index 0000000..3254657 --- /dev/null +++ b/Torch/Collections/RollingAverage.cs @@ -0,0 +1,56 @@ +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Collections +{ + public class RollingAverage + { + private readonly double[] _array; + private int _idx; + private bool _full; + + public RollingAverage(int size) + { + _array = new double[size]; + } + + /// + /// Adds a new value and removes the oldest if necessary. + /// + /// + public void Add(double value) + { + if (_idx >= _array.Length - 1) + { + _full = true; + _idx = 0; + } + + _array[_idx] = value; + _idx++; + } + + public double GetAverage() + { + return _array.Sum() / (_full ? _array.Length : (_idx + 1)); + } + + /// + /// Resets the rolling average. + /// + public void Clear() + { + _idx = 0; + _full = false; + } + + public static implicit operator double(RollingAverage avg) + { + return avg.GetAverage(); + } + } +} diff --git a/Torch/Managers/EntityManager.cs b/Torch/Managers/EntityManager.cs index 672c503..05478ac 100644 --- a/Torch/Managers/EntityManager.cs +++ b/Torch/Managers/EntityManager.cs @@ -17,6 +17,7 @@ using Sandbox.ModAPI; using SpaceEngineers.Game.Entities.Blocks; using SpaceEngineers.Game.ModAPI; using Torch.API; +using Torch.API.Plugins; using VRage; using VRage.Collections; using VRage.Game; diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index f7be1d2..4a50620 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -177,7 +177,7 @@ namespace Torch.Managers { _log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}..."); UserRejected(steamID, JoinResult.BannedByAdmins); - BanPlayer(steamID, true); + BanPlayer(steamID); } } diff --git a/Torch/Managers/NetworkManager/NetworkManager.cs b/Torch/Managers/NetworkManager/NetworkManager.cs index ab81614..41b157d 100644 --- a/Torch/Managers/NetworkManager/NetworkManager.cs +++ b/Torch/Managers/NetworkManager/NetworkManager.cs @@ -120,17 +120,15 @@ namespace Torch.Managers CallSite site; - IMyNetObject sendAs; object obj; if (networkId.IsInvalid) // Static event { site = m_typeTable.StaticEventTable.Get(eventId); - sendAs = null; obj = null; } else // Instance event { - sendAs = ((MyReplicationLayer)MyMultiplayer.ReplicationLayer).GetObjectByNetworkId(networkId); + var sendAs = ((MyReplicationLayer)MyMultiplayer.ReplicationLayer).GetObjectByNetworkId(networkId); if (sendAs == null) { return; @@ -165,7 +163,7 @@ namespace Torch.Managers //ApplicationLog.Error(ex.ToString()); _log.Error(ex); } - }; + } //one of the handlers wants us to discard this packet if (discard) @@ -278,7 +276,7 @@ namespace Torch.Managers for (var j = args.Length + 4; j < 10; j++) arguments[j] = e; - arguments[10] = (IMyEventOwner)null; + arguments[10] = null; //create an array of Types so we can create a generic method var argTypes = new Type[8]; @@ -336,8 +334,7 @@ namespace Torch.Managers private CallSite TryGetStaticCallSite(MethodInfo method) { var methodLookup = (Dictionary)typeof(MyEventTable).GetField("m_methodInfoLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(m_typeTable.StaticEventTable); - CallSite result; - if(!methodLookup.TryGetValue(method, out result)) + if (!methodLookup.TryGetValue(method, out CallSite result)) throw new MissingMemberException("Provided event target not found!"); return result; } @@ -346,8 +343,7 @@ namespace Torch.Managers { var typeInfo = m_typeTable.Get(arg.GetType()); var methodLookup = (Dictionary)typeof(MyEventTable).GetField("m_methodInfoLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(typeInfo.EventTable); - CallSite result; - if (!methodLookup.TryGetValue(method, out result)) + if (!methodLookup.TryGetValue(method, out CallSite result)) throw new MissingMemberException("Provided event target not found!"); return result; } diff --git a/Torch/Managers/PluginManager.cs b/Torch/Managers/PluginManager.cs index c7b2ac6..995e15c 100644 --- a/Torch/Managers/PluginManager.cs +++ b/Torch/Managers/PluginManager.cs @@ -16,17 +16,18 @@ using Torch.API; using Torch.API.Plugins; using Torch.Commands; using Torch.Managers; +using Torch.Updater; using VRage.Plugins; using VRage.Collections; using VRage.Library.Collections; namespace Torch.Managers { - public class PluginManager : IPluginManager, IPlugin + public class PluginManager : IPluginManager { private readonly ITorchBase _torch; private static Logger _log = LogManager.GetLogger(nameof(PluginManager)); - public const string PluginDir = "Plugins"; + public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"); public List Plugins { get; } = new List(); public CommandManager Commands { get; private set; } @@ -42,21 +43,6 @@ namespace Torch.Managers if (!Directory.Exists(PluginDir)) Directory.CreateDirectory(PluginDir); - - InitUpdater(); - } - - /// - /// Adds the plugin manager "plugin" to VRage's plugin system. - /// - private void InitUpdater() - { - var fieldName = "m_plugins"; - var pluginList = typeof(MyPlugins).GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as List; - if (pluginList == null) - throw new TypeLoadException($"{fieldName} field not found in {nameof(MyPlugins)}"); - - pluginList.Add(this); } /// @@ -81,18 +67,48 @@ namespace Torch.Managers Plugins.Clear(); } + private void DownloadPlugins() + { + _log.Info("Downloading plugins"); + var updater = new PluginUpdater(this); + + var folders = Directory.GetDirectories(PluginDir); + var taskList = new List(); + if (_torch.Config.RedownloadPlugins) + _log.Warn("Force downloading all plugins because the RedownloadPlugins flag is set in the config"); + + foreach (var folder in folders) + { + var manifestPath = Path.Combine(folder, "manifest.xml"); + if (!File.Exists(manifestPath)) + { + _log.Info($"No manifest in {folder}, skipping"); + continue; + } + + _log.Info($"Checking for updates for {folder}"); + var manifest = PluginManifest.Load(manifestPath); + taskList.Add(updater.CheckAndUpdate(manifest, _torch.Config.RedownloadPlugins)); + } + + Task.WaitAll(taskList.ToArray()); + _torch.Config.RedownloadPlugins = false; + } + /// /// Loads and creates instances of all plugins in the folder. /// public void Init() { - ((TorchBase)_torch).Network.Init(); - ChatManager.Instance.Init(); Commands = new CommandManager(_torch); + if (_torch.Config.EnableAutomaticUpdates) + DownloadPlugins(); + else + _log.Warn("Automatic plugin updates are disabled."); + _log.Info("Loading plugins"); - var pluginsPath = Path.Combine(Directory.GetCurrentDirectory(), PluginDir); - var dlls = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories); + var dlls = Directory.GetFiles(PluginDir, "*.dll", SearchOption.AllDirectories); foreach (var dllPath in dlls) { var asm = Assembly.UnsafeLoadFrom(dllPath); @@ -135,20 +151,5 @@ namespace Torch.Managers { return GetEnumerator(); } - - void IPlugin.Init(object obj) - { - Init(); - } - - void IPlugin.Update() - { - UpdatePlugins(); - } - - public void Dispose() - { - DisposePlugins(); - } } } diff --git a/Torch/PluginManifest.cs b/Torch/PluginManifest.cs deleted file mode 100644 index ea739dc..0000000 --- a/Torch/PluginManifest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Torch -{ - public class PluginManifest - { - public string Repository { get; set; } - public string Version { get; set; } - } -} diff --git a/Torch/PluginOptions.cs b/Torch/PluginOptions.cs deleted file mode 100644 index 16d9a44..0000000 --- a/Torch/PluginOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Torch -{ - public class PluginOptions - { - public virtual string Save() - { - return null; - } - - public virtual void Load(string data) - { - - } - } -} diff --git a/Torch/SteamService.cs b/Torch/SteamService.cs index 63830d8..7dff154 100644 --- a/Torch/SteamService.cs +++ b/Torch/SteamService.cs @@ -16,24 +16,23 @@ namespace Torch /// public class SteamService : MySteamService { - public SteamService(bool isDedicated, uint appId) - : base(true, appId) + public SteamService(bool isDedicated, uint appId) : base(true, appId) { // TODO: Add protection for this mess... somewhere - SteamSDK.SteamServerAPI.Instance.Dispose(); - var steam = typeof(Sandbox.MySteamService); + SteamServerAPI.Instance.Dispose(); + var steam = typeof(MySteamService); steam.GetField("SteamServerAPI").SetValue(this, null); steam.GetProperty("AppId").GetSetMethod(true).Invoke(this, new object[] { appId }); if (isDedicated) { - steam.GetField("SteamServerAPI").SetValue(this, SteamSDK.SteamServerAPI.Instance); + steam.GetField("SteamServerAPI").SetValue(this, SteamServerAPI.Instance); } else { - var steamApi = SteamSDK.SteamAPI.Instance; - steam.GetField("SteamAPI").SetValue(this, SteamSDK.SteamAPI.Instance); - steam.GetProperty("IsActive").GetSetMethod(true).Invoke(this, new object[] { SteamSDK.SteamAPI.Instance != null }); + var steamApi = SteamAPI.Instance; + steam.GetField("SteamAPI").SetValue(this, SteamAPI.Instance); + steam.GetProperty("IsActive").GetSetMethod(true).Invoke(this, new object[] { SteamAPI.Instance != null }); if (steamApi != null) { diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index de99fba..6feda09 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -116,6 +116,7 @@ + @@ -131,8 +132,8 @@ - - + + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index dea5825..7897658 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -18,18 +18,20 @@ using SpaceEngineers.Game; using Torch.API; using Torch.Managers; using VRage.FileSystem; +using VRage.Plugins; using VRage.Scripting; using VRage.Utils; namespace Torch { - public abstract class TorchBase : ITorchBase + public abstract class TorchBase : ViewModel, ITorchBase, IPlugin { /// /// Hack because *keen*. /// Use only if necessary, prefer dependency injection. /// public static ITorchBase Instance { get; private set; } + public ITorchConfig Config { get; protected set; } protected static Logger Log { get; } = LogManager.GetLogger("Torch"); public Version TorchVersion { get; protected set; } public Version GameVersion { get; private set; } @@ -186,10 +188,25 @@ namespace Torch MySession.AfterLoading += () => SessionLoaded?.Invoke(); MySession.OnUnloading += () => SessionUnloading?.Invoke(); MySession.OnUnloaded += () => SessionUnloaded?.Invoke(); + InitUpdater(); _init = true; } + /// + /// Hook into the VRage plugin system for updates. + /// + private void InitUpdater() + { + var fieldName = "m_plugins"; + var pluginList = typeof(MyPlugins).GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as List; + if (pluginList == null) + throw new TypeLoadException($"{fieldName} field not found in {nameof(MyPlugins)}"); + + pluginList.Add(this); + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { var ex = (Exception)e.ExceptionObject; @@ -205,5 +222,25 @@ namespace Torch public abstract void Start(); public abstract void Stop(); + + /// + public virtual void Dispose() + { + Plugins.DisposePlugins(); + } + + /// + public virtual void Init(object gameInstance) + { + Network.Init(); + ChatManager.Instance.Init(); + Plugins.Init(); + } + + /// + public virtual void Update() + { + Plugins.UpdatePlugins(); + } } } diff --git a/Torch.Server/TorchConfig.cs b/Torch/TorchConfig.cs similarity index 84% rename from Torch.Server/TorchConfig.cs rename to Torch/TorchConfig.cs index 4a09a1d..4ca5fdf 100644 --- a/Torch.Server/TorchConfig.cs +++ b/Torch/TorchConfig.cs @@ -10,15 +10,17 @@ using Sandbox.ModAPI.Ingame; namespace Torch { - public class TorchConfig + public class TorchConfig : ITorchConfig { private static Logger _log = LogManager.GetLogger("Config"); public string InstancePath { get; set; } public string InstanceName { get; set; } - public int Autosave { get; set; } - public bool AutoRestart { get; set; } - public bool LogChat { get; set; } + //public int Autosave { get; set; } + //public bool AutoRestart { get; set; } + //public bool LogChat { get; set; } + public bool EnableAutomaticUpdates { get; set; } = true; + public bool RedownloadPlugins { get; set; } [NonSerialized] private string _path; @@ -28,8 +30,8 @@ namespace Torch { InstanceName = instanceName; InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Torch", InstanceName); - Autosave = autosaveInterval; - AutoRestart = autoRestart; + //Autosave = autosaveInterval; + //AutoRestart = autoRestart; } public static TorchConfig LoadFrom(string path) diff --git a/Torch/Updater/PluginManifest.cs b/Torch/Updater/PluginManifest.cs new file mode 100644 index 0000000..6f13cb4 --- /dev/null +++ b/Torch/Updater/PluginManifest.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace Torch +{ + public class PluginManifest + { + public string Repository { get; set; } = "Jimmacle/notarealrepo"; + public string Version { get; set; } = "1.0"; + + public void Save(string path) + { + using (var f = File.OpenWrite(path)) + { + var ser = new XmlSerializer(typeof(PluginManifest)); + ser.Serialize(f, this); + } + } + + public static PluginManifest Load(string path) + { + using (var f = File.OpenRead(path)) + { + var ser = new XmlSerializer(typeof(PluginManifest)); + return (PluginManifest)ser.Deserialize(f); + } + } + } +} diff --git a/Torch/Updater/PluginUpdater.cs b/Torch/Updater/PluginUpdater.cs index 5a86593..a5f4985 100644 --- a/Torch/Updater/PluginUpdater.cs +++ b/Torch/Updater/PluginUpdater.cs @@ -1,30 +1,93 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; using System.Linq; +using System.Net; using System.Text; using System.Threading.Tasks; +using NLog; using Octokit; +using Torch.API; +using Torch.Managers; +using VRage.Compression; namespace Torch.Updater { public class PluginUpdater { - public async Task CheckForUpdate(PluginManifest manifest) + private readonly PluginManager _pluginManager; + private static readonly Logger Log = LogManager.GetLogger("PluginUpdater"); + + public PluginUpdater(PluginManager pm) { + _pluginManager = pm; + } + + public async Task CheckAndUpdate(PluginManifest manifest, bool force = false) + { + Log.Info($"Checking for update at {manifest.Repository}"); var split = manifest.Repository.Split('/'); if (split.Length != 2) + { + Log.Warn($"Manifest has an invalid repository name: {manifest.Repository}"); + return; + } + + var gitClient = new GitHubClient(new ProductHeaderValue("Torch")); + var releases = await gitClient.Repository.Release.GetAll(split[0], split[1]); + + if (releases.Count == 0) + { + Log.Debug("No releases in repo"); + return; + } + + Version currentVersion; + Version latestVersion; + + try + { + currentVersion = new Version(manifest.Version); + latestVersion = new Version(releases[0].TagName); + } + catch (Exception e) + { + Log.Warn("Invalid version number on manifest or GitHub release"); + return; + } + + if (force || latestVersion > currentVersion) + { + var webClient = new WebClient(); + var assets = await gitClient.Repository.Release.GetAllAssets(split[0], split[1], releases[0].Id); + foreach (var asset in assets) + { + if (asset.Name.EndsWith(".zip")) + { + Log.Debug(asset.BrowserDownloadUrl); + var localPath = Path.Combine(Path.GetTempPath(), asset.Name); + await webClient.DownloadFileTaskAsync(new Uri(asset.BrowserDownloadUrl), localPath); + UnzipPlugin(localPath); + Log.Info($"Downloaded update for {manifest.Repository}"); + return; + } + } + } + else + { + Log.Info($"{manifest.Repository} is up to date."); + } + } + + public void UnzipPlugin(string zipName) + { + if (!File.Exists(zipName)) return; - var client = new GitHubClient(new ProductHeaderValue("Torch")); - var releases = await client.Repository.Release.GetAll(split[0], split[1]); - var currentVersion = new Version(manifest.Version); - var latestVersion = new Version(releases[0].TagName); - - if (latestVersion > currentVersion) - { - //update - } + MyZipArchive.ExtractToDirectory(zipName, _pluginManager.PluginDir); } } }