diff --git a/Penumbra.Api b/Penumbra.Api index 13ade28e..d7e8c8c4 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 13ade28e21bed02e16bbd081b2e6567382cf69bd +Subproject commit d7e8c8c44d92bd50764394af51ac24cb07f362dc diff --git a/Penumbra/Api/ExternalModImporter.cs b/Penumbra/Api/ExternalModImporter.cs deleted file mode 100644 index 49005c50..00000000 --- a/Penumbra/Api/ExternalModImporter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Dalamud.Game.ClientState.Keys; -using OtterGui.Filesystem; -using OtterGui.FileSystem.Selector; -using Penumbra.Mods; -using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab; -using System; -using System.IO; -using System.Linq; - -namespace Penumbra.Api { - public class ExternalModImporter { - private static ModFileSystemSelector modFileSystemSelectorInstance; - - public static ModFileSystemSelector ModFileSystemSelectorInstance { get => modFileSystemSelectorInstance; set => modFileSystemSelectorInstance = value; } - - public static void UnpackMod(string modPackagePath) - { - modFileSystemSelectorInstance.ImportStandaloneModPackage(modPackagePath); - } - } -} diff --git a/Penumbra/Api/HttpApi.cs b/Penumbra/Api/HttpApi.cs index 1b9cc33e..e6d31104 100644 --- a/Penumbra/Api/HttpApi.cs +++ b/Penumbra/Api/HttpApi.cs @@ -12,12 +12,12 @@ public class HttpApi : IDisposable private partial class Controller : WebApiController { // @formatter:off - [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods(); - [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw(); - [Route( HttpVerbs.Post, "/redrawAll" )] public partial void RedrawAll(); - [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod(); - [Route( HttpVerbs.Post, "/unpackmod" )] public partial Task UnpackMod(); - [Route( HttpVerbs.Post, "/openwindow")] public partial Task OpenWindow(); + [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods(); + [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw(); + [Route( HttpVerbs.Post, "/redrawAll" )] public partial void RedrawAll(); + [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod(); + [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod(); + [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow(); // @formatter:on } @@ -103,16 +103,15 @@ public class HttpApi : IDisposable _api.ReloadMod(data.Path, data.Name); } - public async partial Task UnpackMod() + public async partial Task InstallMod() { - var data = await HttpContext.GetRequestDataAsync(); - Penumbra.Log.Debug($"[HTTP] {nameof(UnpackMod)} triggered with {data}."); - // Unpack the mod package if its valid. + var data = await HttpContext.GetRequestDataAsync(); + Penumbra.Log.Debug($"[HTTP] {nameof(InstallMod)} triggered with {data}."); if (data.Path.Length != 0) - _api.UnpackMod(data.Path); + _api.InstallMod(data.Path); } - public async partial Task OpenWindow() + public partial void OpenWindow() { Penumbra.Log.Debug($"[HTTP] {nameof(OpenWindow)} triggered."); _api.OpenMainWindow(TabType.Mods, string.Empty, string.Empty); @@ -125,9 +124,9 @@ public class HttpApi : IDisposable { } } - private record ModUnpackData(string Path) + private record ModInstallData(string Path) { - public ModUnpackData() + public ModInstallData() : this(string.Empty) { } } diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index da216702..467bfa7e 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -18,8 +18,8 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI; -using Penumbra.Collections.Manager; - +using Penumbra.Collections.Manager; + namespace Penumbra.Api; public class IpcTester : IDisposable @@ -847,13 +847,15 @@ public class IpcTester : IDisposable { private readonly DalamudPluginInterface _pi; - private string _modDirectory = string.Empty; - private string _modName = string.Empty; - private string _pathInput = string.Empty; + private string _modDirectory = string.Empty; + private string _modName = string.Empty; + private string _pathInput = string.Empty; + private string _newInstallPath = string.Empty; private PenumbraApiEc _lastReloadEc; private PenumbraApiEc _lastAddEc; private PenumbraApiEc _lastDeleteEc; private PenumbraApiEc _lastSetPathEc; + private PenumbraApiEc _lastInstallEc; private IList<(string, string)> _mods = new List<(string, string)>(); public readonly EventSubscriber DeleteSubscriber; @@ -895,9 +897,10 @@ public class IpcTester : IDisposable if (!_) return; - ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100); - ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100); - ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100); + ImGui.InputTextWithHint("##install", "Install File Path...", ref _newInstallPath, 100); + ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100); + ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100); + ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100); using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit); if (!table) return; @@ -916,6 +919,13 @@ public class IpcTester : IDisposable ImGui.SameLine(); ImGui.TextUnformatted(_lastReloadEc.ToString()); + DrawIntro(Ipc.InstallMod.Label, "Install Mod"); + if (ImGui.Button("Install")) + _lastInstallEc = Ipc.InstallMod.Subscriber(_pi).Invoke(_newInstallPath); + + ImGui.SameLine(); + ImGui.TextUnformatted(_lastInstallEc.ToString()); + DrawIntro(Ipc.AddMod.Label, "Add Mod"); if (ImGui.Button("Add")) _lastAddEc = Ipc.AddMod.Subscriber(_pi).Invoke(_modDirectory); @@ -1140,7 +1150,7 @@ public class IpcTester : IDisposable private class Temporary { private readonly DalamudPluginInterface _pi; - private readonly ModManager _modManager; + private readonly ModManager _modManager; public Temporary(DalamudPluginInterface pi, ModManager modManager) { diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 6388362a..63ff92f4 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -109,23 +109,25 @@ public class PenumbraApi : IDisposable, IPenumbraApi private ActorService _actors; private CollectionResolver _collectionResolver; private CutsceneService _cutsceneService; + private ModImportManager _modImportManager; public unsafe PenumbraApi(CommunicatorService communicator, Penumbra penumbra, ModManager modManager, ResourceLoader resourceLoader, Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections, - TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService) + TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService, ModImportManager modImportManager) { - _communicator = communicator; - _penumbra = penumbra; - _modManager = modManager; - _resourceLoader = resourceLoader; - _config = config; - _collectionManager = collectionManager; - _dalamud = dalamud; - _tempCollections = tempCollections; - _tempMods = tempMods; - _actors = actors; - _collectionResolver = collectionResolver; - _cutsceneService = cutsceneService; + _communicator = communicator; + _penumbra = penumbra; + _modManager = modManager; + _resourceLoader = resourceLoader; + _config = config; + _collectionManager = collectionManager; + _dalamud = dalamud; + _tempCollections = tempCollections; + _tempMods = tempMods; + _actors = actors; + _collectionResolver = collectionResolver; + _cutsceneService = cutsceneService; + _modImportManager = modImportManager; _lumina = (Lumina.GameData?)_dalamud.GameData.GetType() .GetField("gameData", BindingFlags.Instance | BindingFlags.NonPublic) @@ -602,11 +604,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi return PenumbraApiEc.Success; } - public PenumbraApiEc UnpackMod(string modFilePackagePath) + public PenumbraApiEc InstallMod(string modFilePackagePath) { if (File.Exists(modFilePackagePath)) { - ExternalModImporter.UnpackMod(modFilePackagePath); + _modImportManager.AddUnpack(modFilePackagePath); return PenumbraApiEc.Success; } else diff --git a/Penumbra/Api/PenumbraIpcProviders.cs b/Penumbra/Api/PenumbraIpcProviders.cs index 0a28f0de..1457b3a6 100644 --- a/Penumbra/Api/PenumbraIpcProviders.cs +++ b/Penumbra/Api/PenumbraIpcProviders.cs @@ -10,7 +10,7 @@ using Penumbra.Mods.Manager; namespace Penumbra.Api; -using CurrentSettings = ValueTuple< PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)? >; +using CurrentSettings = ValueTuple>, bool)?>; public class PenumbraIpcProviders : IDisposable { @@ -18,210 +18,215 @@ public class PenumbraIpcProviders : IDisposable internal readonly IpcTester Tester; // Plugin State - internal readonly EventProvider Initialized; - internal readonly EventProvider Disposed; - internal readonly FuncProvider< int > ApiVersion; - internal readonly FuncProvider< (int Breaking, int Features) > ApiVersions; - internal readonly FuncProvider< bool > GetEnabledState; - internal readonly EventProvider< bool > EnabledChange; + internal readonly EventProvider Initialized; + internal readonly EventProvider Disposed; + internal readonly FuncProvider ApiVersion; + internal readonly FuncProvider<(int Breaking, int Features)> ApiVersions; + internal readonly FuncProvider GetEnabledState; + internal readonly EventProvider EnabledChange; // Configuration - internal readonly FuncProvider< string > GetModDirectory; - internal readonly FuncProvider< string > GetConfiguration; - internal readonly EventProvider< string, bool > ModDirectoryChanged; + internal readonly FuncProvider GetModDirectory; + internal readonly FuncProvider GetConfiguration; + internal readonly EventProvider ModDirectoryChanged; // UI - internal readonly EventProvider< string > PreSettingsDraw; - internal readonly EventProvider< string > PostSettingsDraw; - internal readonly EventProvider< ChangedItemType, uint > ChangedItemTooltip; - internal readonly EventProvider< MouseButton, ChangedItemType, uint > ChangedItemClick; - internal readonly FuncProvider< TabType, string, string, PenumbraApiEc > OpenMainWindow; - internal readonly ActionProvider CloseMainWindow; + internal readonly EventProvider PreSettingsDraw; + internal readonly EventProvider PostSettingsDraw; + internal readonly EventProvider ChangedItemTooltip; + internal readonly EventProvider ChangedItemClick; + internal readonly FuncProvider OpenMainWindow; + internal readonly ActionProvider CloseMainWindow; // Redrawing - internal readonly ActionProvider< RedrawType > RedrawAll; - internal readonly ActionProvider< GameObject, RedrawType > RedrawObject; - internal readonly ActionProvider< int, RedrawType > RedrawObjectByIndex; - internal readonly ActionProvider< string, RedrawType > RedrawObjectByName; - internal readonly EventProvider< nint, int > GameObjectRedrawn; + internal readonly ActionProvider RedrawAll; + internal readonly ActionProvider RedrawObject; + internal readonly ActionProvider RedrawObjectByIndex; + internal readonly ActionProvider RedrawObjectByName; + internal readonly EventProvider GameObjectRedrawn; // Game State - internal readonly FuncProvider< nint, (nint, string) > GetDrawObjectInfo; - internal readonly FuncProvider< int, int > GetCutsceneParentIndex; - internal readonly EventProvider< nint, string, nint, nint, nint > CreatingCharacterBase; - internal readonly EventProvider< nint, string, nint > CreatedCharacterBase; - internal readonly EventProvider< nint, string, string > GameObjectResourcePathResolved; + internal readonly FuncProvider GetDrawObjectInfo; + internal readonly FuncProvider GetCutsceneParentIndex; + internal readonly EventProvider CreatingCharacterBase; + internal readonly EventProvider CreatedCharacterBase; + internal readonly EventProvider GameObjectResourcePathResolved; // Resolve - internal readonly FuncProvider< string, string > ResolveDefaultPath; - internal readonly FuncProvider< string, string > ResolveInterfacePath; - internal readonly FuncProvider< string, string > ResolvePlayerPath; - internal readonly FuncProvider< string, int, string > ResolveGameObjectPath; - internal readonly FuncProvider< string, string, string > ResolveCharacterPath; - internal readonly FuncProvider< string, string, string[] > ReverseResolvePath; - internal readonly FuncProvider< string, int, string[] > ReverseResolveGameObjectPath; - internal readonly FuncProvider< string, string[] > ReverseResolvePlayerPath; - internal readonly FuncProvider< string[], string[], (string[], string[][]) > ResolvePlayerPaths; + internal readonly FuncProvider ResolveDefaultPath; + internal readonly FuncProvider ResolveInterfacePath; + internal readonly FuncProvider ResolvePlayerPath; + internal readonly FuncProvider ResolveGameObjectPath; + internal readonly FuncProvider ResolveCharacterPath; + internal readonly FuncProvider ReverseResolvePath; + internal readonly FuncProvider ReverseResolveGameObjectPath; + internal readonly FuncProvider ReverseResolvePlayerPath; + internal readonly FuncProvider ResolvePlayerPaths; // Collections - internal readonly FuncProvider< IList< string > > GetCollections; - internal readonly FuncProvider< string > GetCurrentCollectionName; - internal readonly FuncProvider< string > GetDefaultCollectionName; - internal readonly FuncProvider< string > GetInterfaceCollectionName; - internal readonly FuncProvider< string, (string, bool) > GetCharacterCollectionName; - internal readonly FuncProvider< ApiCollectionType, string > GetCollectionForType; - internal readonly FuncProvider< ApiCollectionType, string, bool, bool, (PenumbraApiEc, string) > SetCollectionForType; - internal readonly FuncProvider< int, (bool, bool, string) > GetCollectionForObject; - internal readonly FuncProvider< int, string, bool, bool, (PenumbraApiEc, string) > SetCollectionForObject; - internal readonly FuncProvider< string, IReadOnlyDictionary< string, object? > > GetChangedItems; + internal readonly FuncProvider> GetCollections; + internal readonly FuncProvider GetCurrentCollectionName; + internal readonly FuncProvider GetDefaultCollectionName; + internal readonly FuncProvider GetInterfaceCollectionName; + internal readonly FuncProvider GetCharacterCollectionName; + internal readonly FuncProvider GetCollectionForType; + internal readonly FuncProvider SetCollectionForType; + internal readonly FuncProvider GetCollectionForObject; + internal readonly FuncProvider SetCollectionForObject; + internal readonly FuncProvider> GetChangedItems; // Meta - internal readonly FuncProvider< string > GetPlayerMetaManipulations; - internal readonly FuncProvider< string, string > GetMetaManipulations; - internal readonly FuncProvider< int, string > GetGameObjectMetaManipulations; + internal readonly FuncProvider GetPlayerMetaManipulations; + internal readonly FuncProvider GetMetaManipulations; + internal readonly FuncProvider GetGameObjectMetaManipulations; // Mods - internal readonly FuncProvider< IList< (string, string) > > GetMods; - internal readonly FuncProvider< string, string, PenumbraApiEc > ReloadMod; - internal readonly FuncProvider< string, PenumbraApiEc > AddMod; - internal readonly FuncProvider< string, string, PenumbraApiEc > DeleteMod; - internal readonly FuncProvider< string, string, (PenumbraApiEc, string, bool) > GetModPath; - internal readonly FuncProvider< string, string, string, PenumbraApiEc > SetModPath; - internal readonly EventProvider< string > ModDeleted; - internal readonly EventProvider< string > ModAdded; - internal readonly EventProvider< string, string > ModMoved; + internal readonly FuncProvider> GetMods; + internal readonly FuncProvider ReloadMod; + internal readonly FuncProvider InstallMod; + internal readonly FuncProvider AddMod; + internal readonly FuncProvider DeleteMod; + internal readonly FuncProvider GetModPath; + internal readonly FuncProvider SetModPath; + internal readonly EventProvider ModDeleted; + internal readonly EventProvider ModAdded; + internal readonly EventProvider ModMoved; // ModSettings - internal readonly FuncProvider< string, string, IDictionary< string, (IList< string >, GroupType) >? > GetAvailableModSettings; - internal readonly FuncProvider< string, string, string, bool, CurrentSettings > GetCurrentModSettings; - internal readonly FuncProvider< string, string, string, bool, PenumbraApiEc > TryInheritMod; - internal readonly FuncProvider< string, string, string, bool, PenumbraApiEc > TrySetMod; - internal readonly FuncProvider< string, string, string, int, PenumbraApiEc > TrySetModPriority; - internal readonly FuncProvider< string, string, string, string, string, PenumbraApiEc > TrySetModSetting; - internal readonly FuncProvider< string, string, string, string, IReadOnlyList< string >, PenumbraApiEc > TrySetModSettings; - internal readonly EventProvider< ModSettingChange, string, string, bool > ModSettingChanged; - internal readonly FuncProvider< string, string, string, PenumbraApiEc > CopyModSettings; + internal readonly FuncProvider, GroupType)>?> GetAvailableModSettings; + internal readonly FuncProvider GetCurrentModSettings; + internal readonly FuncProvider TryInheritMod; + internal readonly FuncProvider TrySetMod; + internal readonly FuncProvider TrySetModPriority; + internal readonly FuncProvider TrySetModSetting; + internal readonly FuncProvider, PenumbraApiEc> TrySetModSettings; + internal readonly EventProvider ModSettingChanged; + internal readonly FuncProvider CopyModSettings; // Temporary - internal readonly FuncProvider< string, string, bool, (PenumbraApiEc, string) > CreateTemporaryCollection; - internal readonly FuncProvider< string, PenumbraApiEc > RemoveTemporaryCollection; - internal readonly FuncProvider< string, PenumbraApiEc > CreateNamedTemporaryCollection; - internal readonly FuncProvider< string, PenumbraApiEc > RemoveTemporaryCollectionByName; - internal readonly FuncProvider< string, int, bool, PenumbraApiEc > AssignTemporaryCollection; - internal readonly FuncProvider< string, Dictionary< string, string >, string, int, PenumbraApiEc > AddTemporaryModAll; - internal readonly FuncProvider< string, string, Dictionary< string, string >, string, int, PenumbraApiEc > AddTemporaryMod; - internal readonly FuncProvider< string, int, PenumbraApiEc > RemoveTemporaryModAll; - internal readonly FuncProvider< string, string, int, PenumbraApiEc > RemoveTemporaryMod; + internal readonly FuncProvider CreateTemporaryCollection; + internal readonly FuncProvider RemoveTemporaryCollection; + internal readonly FuncProvider CreateNamedTemporaryCollection; + internal readonly FuncProvider RemoveTemporaryCollectionByName; + internal readonly FuncProvider AssignTemporaryCollection; + internal readonly FuncProvider, string, int, PenumbraApiEc> AddTemporaryModAll; + internal readonly FuncProvider, string, int, PenumbraApiEc> AddTemporaryMod; + internal readonly FuncProvider RemoveTemporaryModAll; + internal readonly FuncProvider RemoveTemporaryMod; - public PenumbraIpcProviders( DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager ) + public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager) { Api = api; // Plugin State - Initialized = Ipc.Initialized.Provider( pi ); - Disposed = Ipc.Disposed.Provider( pi ); - ApiVersion = Ipc.ApiVersion.Provider( pi, DeprecatedVersion ); - ApiVersions = Ipc.ApiVersions.Provider( pi, () => Api.ApiVersion ); - GetEnabledState = Ipc.GetEnabledState.Provider( pi, Api.GetEnabledState ); - EnabledChange = Ipc.EnabledChange.Provider( pi, () => Api.EnabledChange += EnabledChangeEvent, () => Api.EnabledChange -= EnabledChangeEvent ); + Initialized = Ipc.Initialized.Provider(pi); + Disposed = Ipc.Disposed.Provider(pi); + ApiVersion = Ipc.ApiVersion.Provider(pi, DeprecatedVersion); + ApiVersions = Ipc.ApiVersions.Provider(pi, () => Api.ApiVersion); + GetEnabledState = Ipc.GetEnabledState.Provider(pi, Api.GetEnabledState); + EnabledChange = + Ipc.EnabledChange.Provider(pi, () => Api.EnabledChange += EnabledChangeEvent, () => Api.EnabledChange -= EnabledChangeEvent); // Configuration - GetModDirectory = Ipc.GetModDirectory.Provider( pi, Api.GetModDirectory ); - GetConfiguration = Ipc.GetConfiguration.Provider( pi, Api.GetConfiguration ); - ModDirectoryChanged = Ipc.ModDirectoryChanged.Provider( pi, a => Api.ModDirectoryChanged += a, a => Api.ModDirectoryChanged -= a ); + GetModDirectory = Ipc.GetModDirectory.Provider(pi, Api.GetModDirectory); + GetConfiguration = Ipc.GetConfiguration.Provider(pi, Api.GetConfiguration); + ModDirectoryChanged = Ipc.ModDirectoryChanged.Provider(pi, a => Api.ModDirectoryChanged += a, a => Api.ModDirectoryChanged -= a); // UI - PreSettingsDraw = Ipc.PreSettingsDraw.Provider( pi, a => Api.PreSettingsPanelDraw += a, a => Api.PreSettingsPanelDraw -= a ); - PostSettingsDraw = Ipc.PostSettingsDraw.Provider( pi, a => Api.PostSettingsPanelDraw += a, a => Api.PostSettingsPanelDraw -= a ); - ChangedItemTooltip = Ipc.ChangedItemTooltip.Provider( pi, () => Api.ChangedItemTooltip += OnTooltip, () => Api.ChangedItemTooltip -= OnTooltip ); - ChangedItemClick = Ipc.ChangedItemClick.Provider( pi, () => Api.ChangedItemClicked += OnClick, () => Api.ChangedItemClicked -= OnClick ); - OpenMainWindow = Ipc.OpenMainWindow.Provider( pi, Api.OpenMainWindow ); - CloseMainWindow = Ipc.CloseMainWindow.Provider( pi, Api.CloseMainWindow ); + PreSettingsDraw = Ipc.PreSettingsDraw.Provider(pi, a => Api.PreSettingsPanelDraw += a, a => Api.PreSettingsPanelDraw -= a); + PostSettingsDraw = Ipc.PostSettingsDraw.Provider(pi, a => Api.PostSettingsPanelDraw += a, a => Api.PostSettingsPanelDraw -= a); + ChangedItemTooltip = + Ipc.ChangedItemTooltip.Provider(pi, () => Api.ChangedItemTooltip += OnTooltip, () => Api.ChangedItemTooltip -= OnTooltip); + ChangedItemClick = Ipc.ChangedItemClick.Provider(pi, () => Api.ChangedItemClicked += OnClick, () => Api.ChangedItemClicked -= OnClick); + OpenMainWindow = Ipc.OpenMainWindow.Provider(pi, Api.OpenMainWindow); + CloseMainWindow = Ipc.CloseMainWindow.Provider(pi, Api.CloseMainWindow); // Redrawing - RedrawAll = Ipc.RedrawAll.Provider( pi, Api.RedrawAll ); - RedrawObject = Ipc.RedrawObject.Provider( pi, Api.RedrawObject ); - RedrawObjectByIndex = Ipc.RedrawObjectByIndex.Provider( pi, Api.RedrawObject ); - RedrawObjectByName = Ipc.RedrawObjectByName.Provider( pi, Api.RedrawObject ); - GameObjectRedrawn = Ipc.GameObjectRedrawn.Provider( pi, () => Api.GameObjectRedrawn += OnGameObjectRedrawn, () => Api.GameObjectRedrawn -= OnGameObjectRedrawn ); + RedrawAll = Ipc.RedrawAll.Provider(pi, Api.RedrawAll); + RedrawObject = Ipc.RedrawObject.Provider(pi, Api.RedrawObject); + RedrawObjectByIndex = Ipc.RedrawObjectByIndex.Provider(pi, Api.RedrawObject); + RedrawObjectByName = Ipc.RedrawObjectByName.Provider(pi, Api.RedrawObject); + GameObjectRedrawn = Ipc.GameObjectRedrawn.Provider(pi, () => Api.GameObjectRedrawn += OnGameObjectRedrawn, + () => Api.GameObjectRedrawn -= OnGameObjectRedrawn); // Game State - GetDrawObjectInfo = Ipc.GetDrawObjectInfo.Provider( pi, Api.GetDrawObjectInfo ); - GetCutsceneParentIndex = Ipc.GetCutsceneParentIndex.Provider( pi, Api.GetCutsceneParentIndex ); - CreatingCharacterBase = Ipc.CreatingCharacterBase.Provider( pi, + GetDrawObjectInfo = Ipc.GetDrawObjectInfo.Provider(pi, Api.GetDrawObjectInfo); + GetCutsceneParentIndex = Ipc.GetCutsceneParentIndex.Provider(pi, Api.GetCutsceneParentIndex); + CreatingCharacterBase = Ipc.CreatingCharacterBase.Provider(pi, () => Api.CreatingCharacterBase += CreatingCharacterBaseEvent, - () => Api.CreatingCharacterBase -= CreatingCharacterBaseEvent ); - CreatedCharacterBase = Ipc.CreatedCharacterBase.Provider( pi, + () => Api.CreatingCharacterBase -= CreatingCharacterBaseEvent); + CreatedCharacterBase = Ipc.CreatedCharacterBase.Provider(pi, () => Api.CreatedCharacterBase += CreatedCharacterBaseEvent, - () => Api.CreatedCharacterBase -= CreatedCharacterBaseEvent ); - GameObjectResourcePathResolved = Ipc.GameObjectResourcePathResolved.Provider( pi, + () => Api.CreatedCharacterBase -= CreatedCharacterBaseEvent); + GameObjectResourcePathResolved = Ipc.GameObjectResourcePathResolved.Provider(pi, () => Api.GameObjectResourceResolved += GameObjectResourceResolvedEvent, - () => Api.GameObjectResourceResolved -= GameObjectResourceResolvedEvent ); + () => Api.GameObjectResourceResolved -= GameObjectResourceResolvedEvent); // Resolve - ResolveDefaultPath = Ipc.ResolveDefaultPath.Provider( pi, Api.ResolveDefaultPath ); - ResolveInterfacePath = Ipc.ResolveInterfacePath.Provider( pi, Api.ResolveInterfacePath ); - ResolvePlayerPath = Ipc.ResolvePlayerPath.Provider( pi, Api.ResolvePlayerPath ); - ResolveGameObjectPath = Ipc.ResolveGameObjectPath.Provider( pi, Api.ResolveGameObjectPath ); - ResolveCharacterPath = Ipc.ResolveCharacterPath.Provider( pi, Api.ResolvePath ); - ReverseResolvePath = Ipc.ReverseResolvePath.Provider( pi, Api.ReverseResolvePath ); - ReverseResolveGameObjectPath = Ipc.ReverseResolveGameObjectPath.Provider( pi, Api.ReverseResolveGameObjectPath ); - ReverseResolvePlayerPath = Ipc.ReverseResolvePlayerPath.Provider( pi, Api.ReverseResolvePlayerPath ); - ResolvePlayerPaths = Ipc.ResolvePlayerPaths.Provider( pi, Api.ResolvePlayerPaths ); + ResolveDefaultPath = Ipc.ResolveDefaultPath.Provider(pi, Api.ResolveDefaultPath); + ResolveInterfacePath = Ipc.ResolveInterfacePath.Provider(pi, Api.ResolveInterfacePath); + ResolvePlayerPath = Ipc.ResolvePlayerPath.Provider(pi, Api.ResolvePlayerPath); + ResolveGameObjectPath = Ipc.ResolveGameObjectPath.Provider(pi, Api.ResolveGameObjectPath); + ResolveCharacterPath = Ipc.ResolveCharacterPath.Provider(pi, Api.ResolvePath); + ReverseResolvePath = Ipc.ReverseResolvePath.Provider(pi, Api.ReverseResolvePath); + ReverseResolveGameObjectPath = Ipc.ReverseResolveGameObjectPath.Provider(pi, Api.ReverseResolveGameObjectPath); + ReverseResolvePlayerPath = Ipc.ReverseResolvePlayerPath.Provider(pi, Api.ReverseResolvePlayerPath); + ResolvePlayerPaths = Ipc.ResolvePlayerPaths.Provider(pi, Api.ResolvePlayerPaths); // Collections - GetCollections = Ipc.GetCollections.Provider( pi, Api.GetCollections ); - GetCurrentCollectionName = Ipc.GetCurrentCollectionName.Provider( pi, Api.GetCurrentCollection ); - GetDefaultCollectionName = Ipc.GetDefaultCollectionName.Provider( pi, Api.GetDefaultCollection ); - GetInterfaceCollectionName = Ipc.GetInterfaceCollectionName.Provider( pi, Api.GetInterfaceCollection ); - GetCharacterCollectionName = Ipc.GetCharacterCollectionName.Provider( pi, Api.GetCharacterCollection ); - GetCollectionForType = Ipc.GetCollectionForType.Provider( pi, Api.GetCollectionForType ); - SetCollectionForType = Ipc.SetCollectionForType.Provider( pi, Api.SetCollectionForType ); - GetCollectionForObject = Ipc.GetCollectionForObject.Provider( pi, Api.GetCollectionForObject ); - SetCollectionForObject = Ipc.SetCollectionForObject.Provider( pi, Api.SetCollectionForObject ); - GetChangedItems = Ipc.GetChangedItems.Provider( pi, Api.GetChangedItemsForCollection ); + GetCollections = Ipc.GetCollections.Provider(pi, Api.GetCollections); + GetCurrentCollectionName = Ipc.GetCurrentCollectionName.Provider(pi, Api.GetCurrentCollection); + GetDefaultCollectionName = Ipc.GetDefaultCollectionName.Provider(pi, Api.GetDefaultCollection); + GetInterfaceCollectionName = Ipc.GetInterfaceCollectionName.Provider(pi, Api.GetInterfaceCollection); + GetCharacterCollectionName = Ipc.GetCharacterCollectionName.Provider(pi, Api.GetCharacterCollection); + GetCollectionForType = Ipc.GetCollectionForType.Provider(pi, Api.GetCollectionForType); + SetCollectionForType = Ipc.SetCollectionForType.Provider(pi, Api.SetCollectionForType); + GetCollectionForObject = Ipc.GetCollectionForObject.Provider(pi, Api.GetCollectionForObject); + SetCollectionForObject = Ipc.SetCollectionForObject.Provider(pi, Api.SetCollectionForObject); + GetChangedItems = Ipc.GetChangedItems.Provider(pi, Api.GetChangedItemsForCollection); // Meta - GetPlayerMetaManipulations = Ipc.GetPlayerMetaManipulations.Provider( pi, Api.GetPlayerMetaManipulations ); - GetMetaManipulations = Ipc.GetMetaManipulations.Provider( pi, Api.GetMetaManipulations ); - GetGameObjectMetaManipulations = Ipc.GetGameObjectMetaManipulations.Provider( pi, Api.GetGameObjectMetaManipulations ); + GetPlayerMetaManipulations = Ipc.GetPlayerMetaManipulations.Provider(pi, Api.GetPlayerMetaManipulations); + GetMetaManipulations = Ipc.GetMetaManipulations.Provider(pi, Api.GetMetaManipulations); + GetGameObjectMetaManipulations = Ipc.GetGameObjectMetaManipulations.Provider(pi, Api.GetGameObjectMetaManipulations); // Mods - GetMods = Ipc.GetMods.Provider( pi, Api.GetModList ); - ReloadMod = Ipc.ReloadMod.Provider( pi, Api.ReloadMod ); - AddMod = Ipc.AddMod.Provider( pi, Api.AddMod ); - DeleteMod = Ipc.DeleteMod.Provider( pi, Api.DeleteMod ); - GetModPath = Ipc.GetModPath.Provider( pi, Api.GetModPath ); - SetModPath = Ipc.SetModPath.Provider( pi, Api.SetModPath ); - ModDeleted = Ipc.ModDeleted.Provider( pi, () => Api.ModDeleted += ModDeletedEvent, () => Api.ModDeleted -= ModDeletedEvent ); - ModAdded = Ipc.ModAdded.Provider( pi, () => Api.ModAdded += ModAddedEvent, () => Api.ModAdded -= ModAddedEvent ); - ModMoved = Ipc.ModMoved.Provider( pi, () => Api.ModMoved += ModMovedEvent, () => Api.ModMoved -= ModMovedEvent ); + GetMods = Ipc.GetMods.Provider(pi, Api.GetModList); + ReloadMod = Ipc.ReloadMod.Provider(pi, Api.ReloadMod); + InstallMod = Ipc.InstallMod.Provider(pi, Api.InstallMod); + AddMod = Ipc.AddMod.Provider(pi, Api.AddMod); + DeleteMod = Ipc.DeleteMod.Provider(pi, Api.DeleteMod); + GetModPath = Ipc.GetModPath.Provider(pi, Api.GetModPath); + SetModPath = Ipc.SetModPath.Provider(pi, Api.SetModPath); + ModDeleted = Ipc.ModDeleted.Provider(pi, () => Api.ModDeleted += ModDeletedEvent, () => Api.ModDeleted -= ModDeletedEvent); + ModAdded = Ipc.ModAdded.Provider(pi, () => Api.ModAdded += ModAddedEvent, () => Api.ModAdded -= ModAddedEvent); + ModMoved = Ipc.ModMoved.Provider(pi, () => Api.ModMoved += ModMovedEvent, () => Api.ModMoved -= ModMovedEvent); // ModSettings - GetAvailableModSettings = Ipc.GetAvailableModSettings.Provider( pi, Api.GetAvailableModSettings ); - GetCurrentModSettings = Ipc.GetCurrentModSettings.Provider( pi, Api.GetCurrentModSettings ); - TryInheritMod = Ipc.TryInheritMod.Provider( pi, Api.TryInheritMod ); - TrySetMod = Ipc.TrySetMod.Provider( pi, Api.TrySetMod ); - TrySetModPriority = Ipc.TrySetModPriority.Provider( pi, Api.TrySetModPriority ); - TrySetModSetting = Ipc.TrySetModSetting.Provider( pi, Api.TrySetModSetting ); - TrySetModSettings = Ipc.TrySetModSettings.Provider( pi, Api.TrySetModSettings ); - ModSettingChanged = Ipc.ModSettingChanged.Provider( pi, + GetAvailableModSettings = Ipc.GetAvailableModSettings.Provider(pi, Api.GetAvailableModSettings); + GetCurrentModSettings = Ipc.GetCurrentModSettings.Provider(pi, Api.GetCurrentModSettings); + TryInheritMod = Ipc.TryInheritMod.Provider(pi, Api.TryInheritMod); + TrySetMod = Ipc.TrySetMod.Provider(pi, Api.TrySetMod); + TrySetModPriority = Ipc.TrySetModPriority.Provider(pi, Api.TrySetModPriority); + TrySetModSetting = Ipc.TrySetModSetting.Provider(pi, Api.TrySetModSetting); + TrySetModSettings = Ipc.TrySetModSettings.Provider(pi, Api.TrySetModSettings); + ModSettingChanged = Ipc.ModSettingChanged.Provider(pi, () => Api.ModSettingChanged += ModSettingChangedEvent, - () => Api.ModSettingChanged -= ModSettingChangedEvent ); - CopyModSettings = Ipc.CopyModSettings.Provider( pi, Api.CopyModSettings ); + () => Api.ModSettingChanged -= ModSettingChangedEvent); + CopyModSettings = Ipc.CopyModSettings.Provider(pi, Api.CopyModSettings); // Temporary - CreateTemporaryCollection = Ipc.CreateTemporaryCollection.Provider( pi, Api.CreateTemporaryCollection ); - RemoveTemporaryCollection = Ipc.RemoveTemporaryCollection.Provider( pi, Api.RemoveTemporaryCollection ); - CreateNamedTemporaryCollection = Ipc.CreateNamedTemporaryCollection.Provider( pi, Api.CreateNamedTemporaryCollection ); - RemoveTemporaryCollectionByName = Ipc.RemoveTemporaryCollectionByName.Provider( pi, Api.RemoveTemporaryCollectionByName ); - AssignTemporaryCollection = Ipc.AssignTemporaryCollection.Provider( pi, Api.AssignTemporaryCollection ); - AddTemporaryModAll = Ipc.AddTemporaryModAll.Provider( pi, Api.AddTemporaryModAll ); - AddTemporaryMod = Ipc.AddTemporaryMod.Provider( pi, Api.AddTemporaryMod ); - RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider( pi, Api.RemoveTemporaryModAll ); - RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider( pi, Api.RemoveTemporaryMod ); + CreateTemporaryCollection = Ipc.CreateTemporaryCollection.Provider(pi, Api.CreateTemporaryCollection); + RemoveTemporaryCollection = Ipc.RemoveTemporaryCollection.Provider(pi, Api.RemoveTemporaryCollection); + CreateNamedTemporaryCollection = Ipc.CreateNamedTemporaryCollection.Provider(pi, Api.CreateNamedTemporaryCollection); + RemoveTemporaryCollectionByName = Ipc.RemoveTemporaryCollectionByName.Provider(pi, Api.RemoveTemporaryCollectionByName); + AssignTemporaryCollection = Ipc.AssignTemporaryCollection.Provider(pi, Api.AssignTemporaryCollection); + AddTemporaryModAll = Ipc.AddTemporaryModAll.Provider(pi, Api.AddTemporaryModAll); + AddTemporaryMod = Ipc.AddTemporaryMod.Provider(pi, Api.AddTemporaryMod); + RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider(pi, Api.RemoveTemporaryModAll); + RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider(pi, Api.RemoveTemporaryMod); - Tester = new IpcTester( pi, this, modManager ); + Tester = new IpcTester(pi, this, modManager); Initialized.Invoke(); } @@ -295,6 +300,7 @@ public class PenumbraIpcProviders : IDisposable // Mods GetMods.Dispose(); ReloadMod.Dispose(); + InstallMod.Dispose(); AddMod.Dispose(); DeleteMod.Dispose(); GetModPath.Dispose(); @@ -332,46 +338,46 @@ public class PenumbraIpcProviders : IDisposable // Wrappers private int DeprecatedVersion() { - Penumbra.Log.Warning( $"{Ipc.ApiVersion.Label} is outdated. Please use {Ipc.ApiVersions.Label} instead." ); + Penumbra.Log.Warning($"{Ipc.ApiVersion.Label} is outdated. Please use {Ipc.ApiVersions.Label} instead."); return Api.ApiVersion.Breaking; } - private void OnClick( MouseButton click, object? item ) + private void OnClick(MouseButton click, object? item) { - var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item ); - ChangedItemClick.Invoke( click, type, id ); + var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(item); + ChangedItemClick.Invoke(click, type, id); } - private void OnTooltip( object? item ) + private void OnTooltip(object? item) { - var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item ); - ChangedItemTooltip.Invoke( type, id ); + var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(item); + ChangedItemTooltip.Invoke(type, id); } - private void EnabledChangeEvent( bool value ) - => EnabledChange.Invoke( value ); + private void EnabledChangeEvent(bool value) + => EnabledChange.Invoke(value); - private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) - => GameObjectRedrawn.Invoke( objectAddress, objectTableIndex ); + private void OnGameObjectRedrawn(IntPtr objectAddress, int objectTableIndex) + => GameObjectRedrawn.Invoke(objectAddress, objectTableIndex); - private void CreatingCharacterBaseEvent( IntPtr gameObject, string collectionName, IntPtr modelId, IntPtr customize, IntPtr equipData ) - => CreatingCharacterBase.Invoke( gameObject, collectionName, modelId, customize, equipData ); + private void CreatingCharacterBaseEvent(IntPtr gameObject, string collectionName, IntPtr modelId, IntPtr customize, IntPtr equipData) + => CreatingCharacterBase.Invoke(gameObject, collectionName, modelId, customize, equipData); - private void CreatedCharacterBaseEvent( IntPtr gameObject, string collectionName, IntPtr drawObject ) - => CreatedCharacterBase.Invoke( gameObject, collectionName, drawObject ); + private void CreatedCharacterBaseEvent(IntPtr gameObject, string collectionName, IntPtr drawObject) + => CreatedCharacterBase.Invoke(gameObject, collectionName, drawObject); - private void GameObjectResourceResolvedEvent( IntPtr gameObject, string gamePath, string localPath ) - => GameObjectResourcePathResolved.Invoke( gameObject, gamePath, localPath ); + private void GameObjectResourceResolvedEvent(IntPtr gameObject, string gamePath, string localPath) + => GameObjectResourcePathResolved.Invoke(gameObject, gamePath, localPath); - private void ModSettingChangedEvent( ModSettingChange type, string collection, string mod, bool inherited ) - => ModSettingChanged.Invoke( type, collection, mod, inherited ); + private void ModSettingChangedEvent(ModSettingChange type, string collection, string mod, bool inherited) + => ModSettingChanged.Invoke(type, collection, mod, inherited); - private void ModDeletedEvent( string name ) - => ModDeleted.Invoke( name ); + private void ModDeletedEvent(string name) + => ModDeleted.Invoke(name); - private void ModAddedEvent( string name ) - => ModAdded.Invoke( name ); + private void ModAddedEvent(string name) + => ModAdded.Invoke(name); - private void ModMovedEvent( string from, string to ) - => ModMoved.Invoke( from, to ); -} \ No newline at end of file + private void ModMovedEvent(string from, string to) + => ModMoved.Invoke(from, to); +} diff --git a/Penumbra/Import/TexToolsImport.cs b/Penumbra/Import/TexToolsImport.cs index a1478e5b..dff1c921 100644 --- a/Penumbra/Import/TexToolsImport.cs +++ b/Penumbra/Import/TexToolsImport.cs @@ -39,10 +39,10 @@ public partial class TexToolsImporter : IDisposable private readonly ModEditor _editor; private readonly ModManager _modManager; - public TexToolsImporter( DirectoryInfo baseDirectory, int count, IEnumerable< FileInfo > modPackFiles, + public TexToolsImporter( int count, IEnumerable< FileInfo > modPackFiles, Action< FileInfo, DirectoryInfo?, Exception? > handler, Configuration config, ModEditor editor, ModManager modManager) { - _baseDirectory = baseDirectory; + _baseDirectory = modManager.BasePath; _tmpFile = Path.Combine( _baseDirectory.FullName, TempFileName ); _modPackFiles = modPackFiles; _config = config; diff --git a/Penumbra/Mods/Editor/ModBackup.cs b/Penumbra/Mods/Editor/ModBackup.cs index 72162b95..72680091 100644 --- a/Penumbra/Mods/Editor/ModBackup.cs +++ b/Penumbra/Mods/Editor/ModBackup.cs @@ -18,10 +18,10 @@ public class ModBackup public readonly string Name; public readonly bool Exists; - public ModBackup(ExportManager exportManager, Mod mod) + public ModBackup(ModExportManager modExportManager, Mod mod) { _mod = mod; - Name = Path.Combine(exportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp"; + Name = Path.Combine(modExportManager.ExportDirectory.FullName, _mod.ModPath.Name) + ".pmp"; Exists = File.Exists(Name); } diff --git a/Penumbra/Mods/Manager/ExportManager.cs b/Penumbra/Mods/Manager/ModExportManager.cs similarity index 92% rename from Penumbra/Mods/Manager/ExportManager.cs rename to Penumbra/Mods/Manager/ModExportManager.cs index 3d091105..6396e1f9 100644 --- a/Penumbra/Mods/Manager/ExportManager.cs +++ b/Penumbra/Mods/Manager/ModExportManager.cs @@ -4,7 +4,7 @@ using Penumbra.Services; namespace Penumbra.Mods.Manager; -public class ExportManager : IDisposable +public class ModExportManager : IDisposable { private readonly Configuration _config; private readonly CommunicatorService _communicator; @@ -15,7 +15,7 @@ public class ExportManager : IDisposable public DirectoryInfo ExportDirectory => _exportDirectory ?? _modManager.BasePath; - public ExportManager(Configuration config, CommunicatorService communicator, ModManager modManager) + public ModExportManager(Configuration config, CommunicatorService communicator, ModManager modManager) { _config = config; _communicator = communicator; @@ -89,4 +89,4 @@ public class ExportManager : IDisposable new ModBackup(this, mod).Move(null, newDirectory.Name); mod.ModPath = newDirectory; } -} +} \ No newline at end of file diff --git a/Penumbra/Mods/Manager/ModImportManager.cs b/Penumbra/Mods/Manager/ModImportManager.cs new file mode 100644 index 00000000..84aa2c7f --- /dev/null +++ b/Penumbra/Mods/Manager/ModImportManager.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Dalamud.Interface.Internal.Notifications; +using Penumbra.Import; + +namespace Penumbra.Mods.Manager; + +public class ModImportManager : IDisposable +{ + private readonly ModManager _modManager; + private readonly Configuration _config; + private readonly ModEditor _modEditor; + + private readonly ConcurrentQueue _modsToUnpack = new(); + + /// Mods need to be added thread-safely outside of iteration. + private readonly ConcurrentQueue _modsToAdd = new(); + + private TexToolsImporter? _import; + + public ModImportManager(ModManager modManager, Configuration config, ModEditor modEditor) + { + _modManager = modManager; + _config = config; + _modEditor = modEditor; + } + + public void TryUnpacking() + { + if (Importing || !_modsToUnpack.TryDequeue(out var newMods)) + return; + + var files = newMods.Where(s => + { + if (File.Exists(s)) + return true; + + Penumbra.ChatService.NotificationMessage($"Failed to import queued mod at {s}, the file does not exist.", "Warning", + NotificationType.Warning); + return false; + + }).Select(s => new FileInfo(s)).ToArray(); + + if (files.Length == 0) + return; + + _import = new TexToolsImporter(files.Length, files, AddNewMod, _config, _modEditor, _modManager); + } + + public bool Importing + => _import != null; + + public bool IsImporting([NotNullWhen(true)] out TexToolsImporter? importer) + { + importer = _import; + return _import != null; + } + + public void AddUnpack(IEnumerable paths) + => _modsToUnpack.Enqueue(paths.ToArray()); + + public void AddUnpack(params string[] paths) + => _modsToUnpack.Enqueue(paths); + + public void ClearImport() + { + _import?.Dispose(); + _import = null; + } + + + public bool AddUnpackedMod([NotNullWhen(true)] out Mod? mod) + { + if (!_modsToAdd.TryDequeue(out var directory)) + { + mod = null; + return false; + } + + _modManager.AddMod(directory); + mod = _modManager.LastOrDefault(); + return mod != null && mod.ModPath == directory; + } + + public void Dispose() + { + ClearImport(); + _modsToAdd.Clear(); + _modsToUnpack.Clear(); + } + + /// + /// Clean up invalid directory if necessary. + /// Add successfully extracted mods. + /// + private void AddNewMod(FileInfo file, DirectoryInfo? dir, Exception? error) + { + if (error != null) + { + if (dir != null && Directory.Exists(dir.FullName)) + try + { + Directory.Delete(dir.FullName, true); + } + catch (Exception e) + { + Penumbra.Log.Error($"Error cleaning up failed mod extraction of {file.FullName} to {dir.FullName}:\n{e}"); + } + + if (error is not OperationCanceledException) + Penumbra.Log.Error($"Error extracting {file.FullName}, mod skipped:\n{error}"); + } + else if (dir != null) + { + _modsToAdd.Enqueue(dir); + } + } +} diff --git a/Penumbra/PenumbraNew.cs b/Penumbra/PenumbraNew.cs index f7e2da03..58dfd7ac 100644 --- a/Penumbra/PenumbraNew.cs +++ b/Penumbra/PenumbraNew.cs @@ -21,8 +21,8 @@ using Penumbra.UI.Tabs; using Penumbra.Util; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; using ModFileSystemSelector = Penumbra.UI.ModsTab.ModFileSystemSelector; -using Penumbra.Mods.Manager; - +using Penumbra.Mods.Manager; + namespace Penumbra; public class PenumbraNew @@ -61,7 +61,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton(); - + // Add Game Services services.AddSingleton() .AddSingleton() @@ -75,8 +75,8 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); - + .AddSingleton(); + // Add PathResolver services.AddSingleton() .AddSingleton(); @@ -98,7 +98,8 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); @@ -106,7 +107,7 @@ public class PenumbraNew services.AddSingleton() .AddSingleton() .AddSingleton(); - + // Add Path Resolver services.AddSingleton() .AddSingleton() @@ -116,7 +117,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton(); // Add Interface services.AddSingleton() @@ -131,6 +132,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -147,7 +149,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton(); - + // Add Mod Editor services.AddSingleton() .AddSingleton() @@ -156,7 +158,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton(); // Add API services.AddSingleton() diff --git a/Penumbra/UI/ImportPopup.cs b/Penumbra/UI/ImportPopup.cs new file mode 100644 index 00000000..f11cd492 --- /dev/null +++ b/Penumbra/UI/ImportPopup.cs @@ -0,0 +1,64 @@ +using System; +using System.Numerics; +using Dalamud.Interface.Windowing; +using ImGuiNET; +using OtterGui.Raii; +using Penumbra.Import.Structs; +using Penumbra.Mods.Manager; + +namespace Penumbra.UI; + +/// Draw the progress information for import. +public sealed class ImportPopup : Window +{ + private readonly ModImportManager _modImportManager; + + public ImportPopup(ModImportManager modImportManager) + : base("Penumbra Import Status", + ImGuiWindowFlags.Modal + | ImGuiWindowFlags.Popup + | ImGuiWindowFlags.NoCollapse + | ImGuiWindowFlags.NoDecoration + | ImGuiWindowFlags.NoBackground + | ImGuiWindowFlags.NoMove + | ImGuiWindowFlags.NoInputs + | ImGuiWindowFlags.NoFocusOnAppearing + | ImGuiWindowFlags.NoBringToFrontOnFocus, true) + { + _modImportManager = modImportManager; + IsOpen = true; + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = Vector2.Zero, + MaximumSize = Vector2.Zero, + }; + } + + public override void Draw() + { + _modImportManager.TryUnpacking(); + if (!_modImportManager.IsImporting(out var import)) + return; + + ImGui.OpenPopup("##importPopup"); + + var display = ImGui.GetIO().DisplaySize; + var height = Math.Max(display.Y / 4, 15 * ImGui.GetFrameHeightWithSpacing()); + var width = display.X / 8; + var size = new Vector2(width * 2, height); + ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2); + ImGui.SetNextWindowSize(size); + using var popup = ImRaii.Popup("##importPopup", ImGuiWindowFlags.Modal); + using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2))) + { + if (child) + import.DrawProgressInfo(new Vector2(-1, ImGui.GetFrameHeight())); + } + + if ((import.State != ImporterState.Done || !ImGui.Button("Close", -Vector2.UnitX)) + && (import.State == ImporterState.Done || !import.DrawCancelButton(-Vector2.UnitX))) + return; + + _modImportManager.ClearImport(); + } +} diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index c78aa099..b6099c64 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; @@ -13,12 +10,9 @@ using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.FileSystem.Selector; using OtterGui.Raii; -using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; -using Penumbra.Import; -using Penumbra.Import.Structs; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; @@ -27,7 +21,7 @@ using Penumbra.Util; namespace Penumbra.UI.ModsTab; -public sealed partial class ModFileSystemSelector : FileSystemSelector +public sealed class ModFileSystemSelector : FileSystemSelector { private readonly CommunicatorService _communicator; private readonly ChatService _chat; @@ -37,18 +31,13 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector _modUnpackQueue = new Queue(); - - private TexToolsImporter? _import; - public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; - public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; - - private uint _infoPopupId = 0; + private readonly ModImportManager _modImportManager; + public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; + public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; public ModFileSystemSelector(CommunicatorService communicator, ModFileSystem fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config, TutorialService tutorial, FileDialogService fileDialog, ChatService chat, - ModEditor modEditor, ModCacheManager modCaches) + ModCacheManager modCaches, ModImportManager modImportManager) : base(fileSystem, DalamudServices.KeyState, HandleException) { _communicator = communicator; @@ -58,8 +47,8 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector Add an import mods button that opens a file selector. private void AddImportModButton(Vector2 size) { - _infoPopupId = ImGui.GetID("Import Status"); - ExternalImportListener(); var button = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size, "Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files.", !Penumbra.ModManager.Valid, true); _tutorial.OpenTutorial(BasicTutorialSteps.ModImport); @@ -251,105 +229,11 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector new FileInfo(file)), - AddNewMod, _config, _modEditor, _modManager); - ImGui.OpenPopup(_infoPopupId); + + _modImportManager.AddUnpack(f); }, 0, modPath, _config.AlwaysOpenDefaultImport); } - private void ExternalImportListener() - { - if (_modUnpackQueue.Count > 0) - { - // Attempt to avoid triggering if other mods are already unpacking - if (!_modsCurrentlyUnpacking) - { - string modPackagePath = _modUnpackQueue.Dequeue(); - if (File.Exists(modPackagePath)) - { - _modsCurrentlyUnpacking = true; - var modPath = !_config.AlwaysOpenDefaultImport ? null - : _config.DefaultModImportPath.Length > 0 ? _config.DefaultModImportPath - : _config.ModDirectory.Length > 0 ? _config.ModDirectory : null; - - _import = new TexToolsImporter(Penumbra.ModManager.BasePath, 1, new List() { new FileInfo(modPackagePath) }, AddNewMod, - _config, _modEditor, _modManager); - ImGui.OpenPopup(_infoPopupId); - } - } - } - } - - /// - /// Unpacks the specified standalone package - /// - /// The package to unpack - public void ImportStandaloneModPackage(string modPackagePath) - { - _modUnpackQueue.Enqueue(modPackagePath); - } - - /// Draw the progress information for import. - private void DrawInfoPopup() - { - var display = ImGui.GetIO().DisplaySize; - var height = Math.Max(display.Y / 4, 15 * ImGui.GetFrameHeightWithSpacing()); - var width = display.X / 8; - var size = new Vector2(width * 2, height); - ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2); - ImGui.SetNextWindowSize(size); - var infoPopupId = ImGui.GetID("Import Status"); - using var popup = ImRaii.Popup("Import Status", ImGuiWindowFlags.Modal); - if (_import == null || !popup.Success) - return; - - using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2))) - { - if (child) - _import.DrawProgressInfo(new Vector2(-1, ImGui.GetFrameHeight())); - } - - if ((_import.State != ImporterState.Done || !ImGui.Button("Close", -Vector2.UnitX)) - && (_import.State == ImporterState.Done || !_import.DrawCancelButton(-Vector2.UnitX))) - return; - - _import?.Dispose(); - _import = null; - ImGui.CloseCurrentPopup(); - } - - /// Mods need to be added thread-safely outside of iteration. - private readonly ConcurrentQueue _modsToAdd = new(); - - /// - /// Clean up invalid directory if necessary. - /// Add successfully extracted mods. - /// - private void AddNewMod(FileInfo file, DirectoryInfo? dir, Exception? error) - { - if (error != null) - { - if (dir != null && Directory.Exists(dir.FullName)) - try - { - Directory.Delete(dir.FullName, true); - } - catch (Exception e) - { - Penumbra.Log.Error($"Error cleaning up failed mod extraction of {file.FullName} to {dir.FullName}:\n{e}"); - } - - if (error is not OperationCanceledException) - Penumbra.Log.Error($"Error extracting {file.FullName}, mod skipped:\n{error}"); - } - else if (dir != null) - { - _modsToAdd.Enqueue(dir); - } - _modsCurrentlyUnpacking = false; - } - private void DeleteModButton(Vector2 size) { var keys = _config.DeleteModModifier.IsActive(); @@ -573,7 +457,6 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector Label @@ -150,7 +150,7 @@ public class ModPanelEditTab : ITab private void BackupButtons(Vector2 buttonSize) { - var backup = new ModBackup(_exportManager, _mod); + var backup = new ModBackup(_modExportManager, _mod); var tt = ModBackup.CreatingBackup ? "Already exporting a mod." : backup.Exists diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 8d51ec65..d29ce49a 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -32,7 +32,7 @@ public class SettingsTab : ITab private readonly Penumbra _penumbra; private readonly FileDialogService _fileDialog; private readonly ModManager _modManager; - private readonly ExportManager _exportManager; + private readonly ModExportManager _modExportManager; private readonly ModFileSystemSelector _selector; private readonly CharacterUtility _characterUtility; private readonly ResidentResourceManager _residentResources; @@ -40,7 +40,7 @@ public class SettingsTab : ITab public SettingsTab(Configuration config, FontReloader fontReloader, TutorialService tutorial, Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, CharacterUtility characterUtility, - ResidentResourceManager residentResources, DalamudServices dalamud, ExportManager exportManager) + ResidentResourceManager residentResources, DalamudServices dalamud, ModExportManager modExportManager) { _config = config; _fontReloader = fontReloader; @@ -52,7 +52,7 @@ public class SettingsTab : ITab _characterUtility = characterUtility; _residentResources = residentResources; _dalamud = dalamud; - _exportManager = exportManager; + _modExportManager = modExportManager; } public void DrawHeader() @@ -116,7 +116,7 @@ public class SettingsTab : ITab /// Check a potential new root directory for validity and return the button text and whether it is valid. private static (string Text, bool Valid) CheckRootDirectoryPath(string newName, string old, bool selected) - { + { static bool IsSubPathOf(string basePath, string subPath) { if (basePath.Length == 0) @@ -555,7 +555,7 @@ public class SettingsTab : ITab _tempExportDirectory = tmp; if (ImGui.IsItemDeactivatedAfterEdit()) - _exportManager.UpdateExportDirectory(_tempExportDirectory); + _modExportManager.UpdateExportDirectory(_tempExportDirectory); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Folder.ToIconString()}##export", UiHelpers.IconButtonSize, @@ -569,7 +569,7 @@ public class SettingsTab : ITab _fileDialog.OpenFolderPicker("Choose Default Export Directory", (b, s) => { if (b) - _exportManager.UpdateExportDirectory(s); + _modExportManager.UpdateExportDirectory(s); }, startDir, false); } diff --git a/Penumbra/UI/WindowSystem.cs b/Penumbra/UI/WindowSystem.cs index f2f0a8b6..4756accf 100644 --- a/Penumbra/UI/WindowSystem.cs +++ b/Penumbra/UI/WindowSystem.cs @@ -16,17 +16,17 @@ public class PenumbraWindowSystem : IDisposable public readonly PenumbraChangelog Changelog; public PenumbraWindowSystem(DalamudPluginInterface pi, Configuration config, PenumbraChangelog changelog, ConfigWindow window, - LaunchButton _, - ModEditWindow editWindow, FileDialogService fileDialog) + LaunchButton _, ModEditWindow editWindow, FileDialogService fileDialog, ImportPopup importPopup) { - _uiBuilder = pi.UiBuilder; - _fileDialog = fileDialog; - Changelog = changelog; - Window = window; - _windowSystem = new WindowSystem("Penumbra"); + _uiBuilder = pi.UiBuilder; + _fileDialog = fileDialog; + Changelog = changelog; + Window = window; + _windowSystem = new WindowSystem("Penumbra"); _windowSystem.AddWindow(changelog.Changelog); _windowSystem.AddWindow(window); _windowSystem.AddWindow(editWindow); + _windowSystem.AddWindow(importPopup); _uiBuilder.OpenConfigUi += Window.Toggle; _uiBuilder.Draw += _windowSystem.Draw; _uiBuilder.Draw += _fileDialog.Draw;