diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 3b3804baa..d28401e73 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -327,6 +327,46 @@ internal class PluginInstallerWindow : Window, IDisposable }); } + /// + /// A continuation task that displays any errors received into the error modal. + /// + /// The previous task. + /// An error message to be displayed. + /// A value indicating whether to continue with the next task. + public bool DisplayErrorContinuation(Task task, object state) + { + if (task.IsFaulted) + { + var errorModalMessage = state as string; + + foreach (var ex in task.Exception.InnerExceptions) + { + if (ex is PluginException) + { + Log.Error(ex, "Plugin installer threw an error"); +#if DEBUG + if (!string.IsNullOrEmpty(ex.Message)) + errorModalMessage += $"\n\n{ex.Message}"; +#endif + } + else + { + Log.Error(ex, "Plugin installer threw an unexpected error"); +#if DEBUG + if (!string.IsNullOrEmpty(ex.Message)) + errorModalMessage += $"\n\n{ex.Message}"; +#endif + } + } + + this.ShowErrorModal(errorModalMessage); + + return false; + } + + return true; + } + private void DrawProgressOverlay() { var pluginManager = Service.Get(); @@ -2155,7 +2195,7 @@ internal class PluginInstallerWindow : Window, IDisposable this.DrawSendFeedbackButton(plugin.Manifest, plugin.IsTesting); } - if (availablePluginUpdate != default && ! plugin.IsDev) + if (availablePluginUpdate != default && !plugin.IsDev) this.DrawUpdateSinglePluginButton(availablePluginUpdate); ImGui.SameLine(); @@ -2914,46 +2954,6 @@ internal class PluginInstallerWindow : Window, IDisposable private bool WasPluginSeen(string internalName) => Service.Get().SeenPluginInternalName.Contains(internalName); - /// - /// A continuation task that displays any errors received into the error modal. - /// - /// The previous task. - /// An error message to be displayed. - /// A value indicating whether to continue with the next task. - public bool DisplayErrorContinuation(Task task, object state) - { - if (task.IsFaulted) - { - var errorModalMessage = state as string; - - foreach (var ex in task.Exception.InnerExceptions) - { - if (ex is PluginException) - { - Log.Error(ex, "Plugin installer threw an error"); -#if DEBUG - if (!string.IsNullOrEmpty(ex.Message)) - errorModalMessage += $"\n\n{ex.Message}"; -#endif - } - else - { - Log.Error(ex, "Plugin installer threw an unexpected error"); -#if DEBUG - if (!string.IsNullOrEmpty(ex.Message)) - errorModalMessage += $"\n\n{ex.Message}"; -#endif - } - } - - this.ShowErrorModal(errorModalMessage); - - return false; - } - - return true; - } - private Task ShowErrorModal(string message) { this.errorModalMessage = message; diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs index 1448074f3..60f605f8e 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs @@ -16,6 +16,9 @@ using Serilog; namespace Dalamud.Interface.Internal.Windows.PluginInstaller; +/// +/// ImGui widget used to manage profiles. +/// internal class ProfileManagerWidget { private readonly PluginInstallerWindow installer; @@ -25,11 +28,24 @@ internal class ProfileManagerWidget private string pickerSearch = string.Empty; private string profileNameEdit = string.Empty; + /// + /// Initializes a new instance of the class. + /// + /// The plugin installer. public ProfileManagerWidget(PluginInstallerWindow installer) { this.installer = installer; } + private enum Mode + { + Overview, + EditSingleProfile, + } + + /// + /// Draw this widget's contents. + /// public void Draw() { switch (this.mode) @@ -44,6 +60,9 @@ internal class ProfileManagerWidget } } + /// + /// Reset the widget. + /// public void Reset() { this.mode = Mode.Overview; @@ -428,12 +447,6 @@ internal class ProfileManagerWidget } } - private enum Mode - { - Overview, - EditSingleProfile, - } - private static class Locs { public static string TooltipEnableDisable => diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index a0283ca67..162b6c90b 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -816,137 +816,6 @@ internal partial class PluginManager : IDisposable, IServiceType return plugin; } - /// - /// Load a plugin. - /// - /// The associated with the main assembly of this plugin. - /// The already loaded definition, if available. - /// The reason this plugin was loaded. - /// If this plugin should support development features. - /// If this plugin is being loaded at boot. - /// Don't load the plugin, just don't do it. - /// The loaded plugin. - private async Task LoadPluginAsync(FileInfo dllFile, LocalPluginManifest? manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) - { - var name = manifest?.Name ?? dllFile.Name; - var loadPlugin = !doNotLoad; - - LocalPlugin plugin; - - if (manifest != null && manifest.InternalName == null) - { - Log.Error("{FileName}: Your manifest has no internal name set! Can't load this.", dllFile.FullName); - throw new Exception("No internal name"); - } - - if (isDev) - { - Log.Information($"Loading dev plugin {name}"); - var devPlugin = new LocalDevPlugin(dllFile, manifest); - loadPlugin &= !isBoot || devPlugin.StartOnBoot; - - var probablyInternalNameForThisPurpose = manifest?.InternalName ?? dllFile.Name; - var wantsInDefaultProfile = - this.profileManager.DefaultProfile.WantsPlugin(probablyInternalNameForThisPurpose); - if (wantsInDefaultProfile == false && devPlugin.StartOnBoot) - { - this.profileManager.DefaultProfile.AddOrUpdate(probablyInternalNameForThisPurpose, true, false); - } - else if (wantsInDefaultProfile == true && !devPlugin.StartOnBoot) - { - this.profileManager.DefaultProfile.AddOrUpdate(probablyInternalNameForThisPurpose, false, false); - } - - plugin = devPlugin; - } - else - { - Log.Information($"Loading plugin {name}"); - plugin = new LocalPlugin(dllFile, manifest); - } - -#pragma warning disable CS0618 - var defaultState = manifest?.Disabled != true && loadPlugin; -#pragma warning restore CS0618 - - // Need to do this here, so plugins that don't load are still added to the default profile - var wantToLoad = this.profileManager.GetWantState(plugin.Manifest.InternalName, defaultState); - - if (loadPlugin) - { - try - { - if (wantToLoad && !plugin.IsOrphaned) - { - await plugin.LoadAsync(reason); - } - else - { - Log.Verbose($"{name} not loaded, wantToLoad:{wantToLoad} orphaned:{plugin.IsOrphaned}"); - } - } - catch (InvalidPluginException) - { - PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); - throw; - } - catch (BannedPluginException) - { - // Out of date plugins get added so they can be updated. - Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}"); - } - catch (Exception ex) - { - if (plugin.IsDev) - { - // Dev plugins always get added to the list so they can be fiddled with in the UI - Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}"); - - // NOTE(goat): This can't work - plugins don't "unload" if they fail to load. - // plugin.Disable(); // Disable here, otherwise you can't enable+load later - } - else if (plugin.IsOutdated) - { - // Out of date plugins get added, so they can be updated. - Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}"); - } - else if (plugin.IsOrphaned) - { - // Orphaned plugins get added, so that users aren't confused. - Log.Information(ex, $"Plugin was orphaned, adding anyways: {dllFile.Name}"); - } - else if (isBoot) - { - // During boot load, plugins always get added to the list so they can be fiddled with in the UI - Log.Information(ex, $"Regular plugin failed to load, adding anyways: {dllFile.Name}"); - - // NOTE(goat): This can't work - plugins don't "unload" if they fail to load. - // plugin.Disable(); // Disable here, otherwise you can't enable+load later - } - else if (!plugin.CheckPolicy()) - { - // During boot load, plugins always get added to the list so they can be fiddled with in the UI - Log.Information(ex, $"Plugin not loaded due to policy, adding anyways: {dllFile.Name}"); - - // NOTE(goat): This can't work - plugins don't "unload" if they fail to load. - // plugin.Disable(); // Disable here, otherwise you can't enable+load later - } - else - { - PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); - throw; - } - } - } - - lock (this.pluginListLock) - { - this.InstalledPlugins = this.InstalledPlugins.Add(plugin); - } - - return plugin; - } - /// /// Remove a plugin. /// @@ -1330,6 +1199,137 @@ internal partial class PluginManager : IDisposable, IServiceType /// The calling plugin, or null. public LocalPlugin? FindCallingPlugin() => this.FindCallingPlugin(new StackTrace()); + /// + /// Load a plugin. + /// + /// The associated with the main assembly of this plugin. + /// The already loaded definition, if available. + /// The reason this plugin was loaded. + /// If this plugin should support development features. + /// If this plugin is being loaded at boot. + /// Don't load the plugin, just don't do it. + /// The loaded plugin. + private async Task LoadPluginAsync(FileInfo dllFile, LocalPluginManifest? manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) + { + var name = manifest?.Name ?? dllFile.Name; + var loadPlugin = !doNotLoad; + + LocalPlugin plugin; + + if (manifest != null && manifest.InternalName == null) + { + Log.Error("{FileName}: Your manifest has no internal name set! Can't load this.", dllFile.FullName); + throw new Exception("No internal name"); + } + + if (isDev) + { + Log.Information($"Loading dev plugin {name}"); + var devPlugin = new LocalDevPlugin(dllFile, manifest); + loadPlugin &= !isBoot || devPlugin.StartOnBoot; + + var probablyInternalNameForThisPurpose = manifest?.InternalName ?? dllFile.Name; + var wantsInDefaultProfile = + this.profileManager.DefaultProfile.WantsPlugin(probablyInternalNameForThisPurpose); + if (wantsInDefaultProfile == false && devPlugin.StartOnBoot) + { + this.profileManager.DefaultProfile.AddOrUpdate(probablyInternalNameForThisPurpose, true, false); + } + else if (wantsInDefaultProfile == true && !devPlugin.StartOnBoot) + { + this.profileManager.DefaultProfile.AddOrUpdate(probablyInternalNameForThisPurpose, false, false); + } + + plugin = devPlugin; + } + else + { + Log.Information($"Loading plugin {name}"); + plugin = new LocalPlugin(dllFile, manifest); + } + +#pragma warning disable CS0618 + var defaultState = manifest?.Disabled != true && loadPlugin; +#pragma warning restore CS0618 + + // Need to do this here, so plugins that don't load are still added to the default profile + var wantToLoad = this.profileManager.GetWantState(plugin.Manifest.InternalName, defaultState); + + if (loadPlugin) + { + try + { + if (wantToLoad && !plugin.IsOrphaned) + { + await plugin.LoadAsync(reason); + } + else + { + Log.Verbose($"{name} not loaded, wantToLoad:{wantToLoad} orphaned:{plugin.IsOrphaned}"); + } + } + catch (InvalidPluginException) + { + PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); + throw; + } + catch (BannedPluginException) + { + // Out of date plugins get added so they can be updated. + Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}"); + } + catch (Exception ex) + { + if (plugin.IsDev) + { + // Dev plugins always get added to the list so they can be fiddled with in the UI + Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}"); + + // NOTE(goat): This can't work - plugins don't "unload" if they fail to load. + // plugin.Disable(); // Disable here, otherwise you can't enable+load later + } + else if (plugin.IsOutdated) + { + // Out of date plugins get added, so they can be updated. + Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}"); + } + else if (plugin.IsOrphaned) + { + // Orphaned plugins get added, so that users aren't confused. + Log.Information(ex, $"Plugin was orphaned, adding anyways: {dllFile.Name}"); + } + else if (isBoot) + { + // During boot load, plugins always get added to the list so they can be fiddled with in the UI + Log.Information(ex, $"Regular plugin failed to load, adding anyways: {dllFile.Name}"); + + // NOTE(goat): This can't work - plugins don't "unload" if they fail to load. + // plugin.Disable(); // Disable here, otherwise you can't enable+load later + } + else if (!plugin.CheckPolicy()) + { + // During boot load, plugins always get added to the list so they can be fiddled with in the UI + Log.Information(ex, $"Plugin not loaded due to policy, adding anyways: {dllFile.Name}"); + + // NOTE(goat): This can't work - plugins don't "unload" if they fail to load. + // plugin.Disable(); // Disable here, otherwise you can't enable+load later + } + else + { + PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); + throw; + } + } + } + + lock (this.pluginListLock) + { + this.InstalledPlugins = this.InstalledPlugins.Add(plugin); + } + + return plugin; + } + private void DetectAvailablePluginUpdates() { var updatablePlugins = new List(); diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs index 994069335..aa72200a1 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs @@ -61,6 +61,13 @@ internal class ProfileCommandHandler : IServiceType, IDisposable this.framework.Update += this.FrameworkOnUpdate; } + private enum ProfileOp + { + Enable, + Disable, + Toggle, + } + /// public void Dispose() { @@ -166,11 +173,4 @@ internal class ProfileCommandHandler : IServiceType, IDisposable return name; } - - private enum ProfileOp - { - Enable, - Disable, - Toggle, - } } diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs index b7073ac99..f7eac6d21 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs @@ -165,7 +165,7 @@ internal class ProfileManager : IServiceType /// /// Go through all profiles and plugins, and enable/disable plugins they want active. - /// This will block until all plugins have been loaded/reloaded! + /// This will block until all plugins have been loaded/reloaded. /// public void ApplyAllWantStates() { diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileModel.cs b/Dalamud/Plugin/Internal/Profiles/ProfileModel.cs index 06075a07e..c73e80450 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileModel.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileModel.cs @@ -5,14 +5,29 @@ using Newtonsoft.Json; namespace Dalamud.Plugin.Internal.Profiles; +/// +/// Class representing a profile. +/// public abstract class ProfileModel { + /// + /// Gets or sets the ID of the profile. + /// [JsonProperty("id")] public Guid Guid { get; set; } = Guid.Empty; + /// + /// Gets or sets the name of the profile. + /// [JsonProperty("n")] public string Name { get; set; } = "New Profile"; + /// + /// Deserialize a profile into a model. + /// + /// The string to decompress. + /// The parsed model. + /// Thrown when the parsed string is not a valid profile. public static ProfileModel? Deserialize(string model) { var json = Util.DecompressString(Convert.FromBase64String(model.Substring(3))); @@ -23,6 +38,11 @@ public abstract class ProfileModel throw new ArgumentException("Was not a compressed style model."); } + /// + /// Serialize this model into a string usable for sharing. + /// + /// The serialized representation of the model. + /// Thrown when an unsupported model is serialized. public string Serialize() { string prefix; diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileModelV1.cs b/Dalamud/Plugin/Internal/Profiles/ProfileModelV1.cs index cae585c30..2a851d234 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileModelV1.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileModelV1.cs @@ -1,27 +1,55 @@ using System.Collections.Generic; + using Newtonsoft.Json; namespace Dalamud.Plugin.Internal.Profiles; +/// +/// Version 1 of the profile model. +/// public class ProfileModelV1 : ProfileModel { + /// + /// Gets the prefix of this version. + /// public static string SerializedPrefix => "DP1"; + /// + /// Gets or sets a value indicating whether or not this profile should always be enabled at boot. + /// [JsonProperty("b")] public bool AlwaysEnableOnBoot { get; set; } = false; + /// + /// Gets or sets a value indicating whether or not this profile is currently enabled. + /// [JsonProperty("e")] public bool IsEnabled { get; set; } = false; + /// + /// Gets or sets a value indicating this profile's color. + /// [JsonProperty("c")] public uint Color { get; set; } + /// + /// Gets or sets the list of plugins in this profile. + /// public List Plugins { get; set; } = new(); + /// + /// Class representing a single plugin in a profile. + /// public class ProfileModelV1Plugin { - public string InternalName { get; set; } + /// + /// Gets or sets the internal name of the plugin. + /// + public string? InternalName { get; set; } + /// + /// Gets or sets a value indicating whether or not this entry is enabled. + /// public bool IsEnabled { get; set; } } } diff --git a/Dalamud/Plugin/Internal/Profiles/ProfilePluginEntry.cs b/Dalamud/Plugin/Internal/Profiles/ProfilePluginEntry.cs index 21856e572..0a6f5140b 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfilePluginEntry.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfilePluginEntry.cs @@ -1,14 +1,28 @@ namespace Dalamud.Plugin.Internal.Profiles; +/// +/// Class representing a single plugin in a profile. +/// internal class ProfilePluginEntry { + /// + /// Initializes a new instance of the class. + /// + /// The internal name of the plugin. + /// A value indicating whether or not this entry is enabled. public ProfilePluginEntry(string internalName, bool state) { this.InternalName = internalName; this.IsEnabled = state; } + /// + /// Gets the internal name of the plugin. + /// public string InternalName { get; } + /// + /// Gets a value indicating whether or not this entry is enabled. + /// public bool IsEnabled { get; } } diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 7811939b4..062fc94f1 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -136,7 +136,9 @@ internal class LocalPlugin : IDisposable this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile); if (this.disabledFile.Exists) { +#pragma warning disable CS0618 this.Manifest.Disabled = true; +#pragma warning restore CS0618 this.disabledFile.Delete(); } @@ -627,9 +629,9 @@ internal class LocalPlugin : IDisposable var manifest = LocalPluginManifest.GetManifestFile(this.DllFile); if (manifest.Exists) { - //var isDisabled = this.IsDisabled; // saving the internal state because it could have been deleted + // var isDisabled = this.IsDisabled; // saving the internal state because it could have been deleted this.Manifest = LocalPluginManifest.Load(manifest); - //this.Manifest.Disabled = isDisabled; + // this.Manifest.Disabled = isDisabled; this.SaveManifest(); }