diff --git a/OtterGui b/OtterGui index a63f6735..9af1e5fc 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 +Subproject commit 9af1e5fce4c13ef98842807d4f593dec8ae80c87 diff --git a/Penumbra.Api b/Penumbra.Api index c23ee05c..dd141317 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669 +Subproject commit dd14131793e5ae47cc8e9232f46469216017b5aa diff --git a/Penumbra.GameData b/Penumbra.GameData index 283d51f6..3baace73 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 283d51f6f6c7721a810548d95ba83eef2484e17e +Subproject commit 3baace73c828271dcb71a8156e3e7b91e1dd12ae diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 2991230e..f9cad217 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -53,7 +53,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService public string ModDirectory { get; set; } = string.Empty; public string ExportDirectory { get; set; } = string.Empty; - public string WatchDirectory { get; set; } = string.Empty; public bool? UseCrashHandler { get; set; } = null; public bool OpenWindowAtStart { get; set; } = false; @@ -77,8 +76,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool HideRedrawBar { get; set; } = false; public bool HideMachinistOffhandFromChangedItems { get; set; } = true; public bool DefaultTemporaryMode { get; set; } = false; - public bool EnableDirectoryWatch { get; set; } = false; - public bool EnableAutomaticModImport { get; set; } = false; public bool EnableCustomShapes { get; set; } = true; public PcpSettings PcpSettings = new(); public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; diff --git a/Penumbra/Interop/PathResolving/CutsceneService.cs b/Penumbra/Interop/PathResolving/CutsceneService.cs index 97e64f84..6be19c46 100644 --- a/Penumbra/Interop/PathResolving/CutsceneService.cs +++ b/Penumbra/Interop/PathResolving/CutsceneService.cs @@ -75,7 +75,6 @@ public sealed class CutsceneService : IRequiredService, IDisposable return false; _copiedCharacters[copyIdx - CutsceneStartIdx] = (short)parentIdx; - _objects.InvokeRequiredUpdates(); return true; } diff --git a/Penumbra/Services/FileWatcher.cs b/Penumbra/Services/FileWatcher.cs deleted file mode 100644 index 141825f5..00000000 --- a/Penumbra/Services/FileWatcher.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Threading.Channels; -using OtterGui.Services; -using Penumbra.Mods.Manager; - -namespace Penumbra.Services; - -public class FileWatcher : IDisposable, IService -{ - private readonly FileSystemWatcher _fsw; - private readonly Channel _queue; - private readonly CancellationTokenSource _cts = new(); - private readonly Task _consumer; - private readonly ConcurrentDictionary _pending = new(StringComparer.OrdinalIgnoreCase); - private readonly ModImportManager _modImportManager; - private readonly MessageService _messageService; - private readonly Configuration _config; - - public FileWatcher(ModImportManager modImportManager, MessageService messageService, Configuration config) - { - _modImportManager = modImportManager; - _messageService = messageService; - _config = config; - - if (!_config.EnableDirectoryWatch) - return; - - _queue = Channel.CreateBounded(new BoundedChannelOptions(256) - { - SingleReader = true, - SingleWriter = false, - FullMode = BoundedChannelFullMode.DropOldest, - }); - - _fsw = new FileSystemWatcher(_config.WatchDirectory) - { - IncludeSubdirectories = false, - NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime, - InternalBufferSize = 32 * 1024, - }; - - // Only wake us for the exact patterns we care about - _fsw.Filters.Add("*.pmp"); - _fsw.Filters.Add("*.pcp"); - _fsw.Filters.Add("*.ttmp"); - _fsw.Filters.Add("*.ttmp2"); - - _fsw.Created += OnPath; - _fsw.Renamed += OnPath; - - _consumer = Task.Factory.StartNew( - () => ConsumerLoopAsync(_cts.Token), - _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); - - _fsw.EnableRaisingEvents = true; - } - - private void OnPath(object? sender, FileSystemEventArgs e) - { - // Cheap de-dupe: only queue once per filename until processed - if (!_config.EnableDirectoryWatch || !_pending.TryAdd(e.FullPath, 0)) - return; - - _ = _queue.Writer.TryWrite(e.FullPath); - } - - private async Task ConsumerLoopAsync(CancellationToken token) - { - if (!_config.EnableDirectoryWatch) - return; - - var reader = _queue.Reader; - while (await reader.WaitToReadAsync(token).ConfigureAwait(false)) - { - while (reader.TryRead(out var path)) - { - try - { - await ProcessOneAsync(path, token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - Penumbra.Log.Debug($"[FileWatcher] Canceled via Token."); - } - catch (Exception ex) - { - Penumbra.Log.Debug($"[FileWatcher] Error during Processing: {ex}"); - } - finally - { - _pending.TryRemove(path, out _); - } - } - } - } - - private async Task ProcessOneAsync(string path, CancellationToken token) - { - // Downloads often finish via rename; file may be locked briefly. - // Wait until it exists and is readable; also require two stable size checks. - const int maxTries = 40; - long lastLen = -1; - - for (var i = 0; i < maxTries && !token.IsCancellationRequested; i++) - { - if (!File.Exists(path)) - { - await Task.Delay(100, token); - continue; - } - - try - { - var fi = new FileInfo(path); - var len = fi.Length; - if (len > 0 && len == lastLen) - { - if (_config.EnableAutomaticModImport) - { - _modImportManager.AddUnpack(path); - return; - } - else - { - var invoked = false; - Action installRequest = args => - { - if (invoked) - return; - - invoked = true; - _modImportManager.AddUnpack(path); - }; - - _messageService.PrintModFoundInfo( - Path.GetFileNameWithoutExtension(path), - installRequest); - - return; - } - } - - lastLen = len; - } - catch (IOException) - { - Penumbra.Log.Debug($"[FileWatcher] File is still being written to."); - } - catch (UnauthorizedAccessException) - { - Penumbra.Log.Debug($"[FileWatcher] File is locked."); - } - - await Task.Delay(150, token); - } - } - - public void UpdateDirectory(string newPath) - { - if (!_config.EnableDirectoryWatch || _fsw is null || !Directory.Exists(newPath) || string.IsNullOrWhiteSpace(newPath)) - return; - - _fsw.EnableRaisingEvents = false; - _fsw.Path = newPath; - _fsw.EnableRaisingEvents = true; - } - - public void Dispose() - { - if (!_config.EnableDirectoryWatch) - return; - - _fsw.EnableRaisingEvents = false; - _cts.Cancel(); - _fsw.Dispose(); - _queue.Writer.TryComplete(); - try - { - _consumer.Wait(TimeSpan.FromSeconds(5)); - } - catch - { - /* swallow */ - } - - _cts.Dispose(); - } -} diff --git a/Penumbra/Services/MessageService.cs b/Penumbra/Services/MessageService.cs index 3dc6a90c..70ccf47b 100644 --- a/Penumbra/Services/MessageService.cs +++ b/Penumbra/Services/MessageService.cs @@ -1,43 +1,19 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.EventArgs; using Dalamud.Plugin.Services; using Lumina.Excel.Sheets; using OtterGui.Log; using OtterGui.Services; -using OtterGui.Text; using Penumbra.GameData.Data; using Penumbra.Mods.Manager; using Penumbra.String.Classes; -using static OtterGui.Classes.MessageService; using Notification = OtterGui.Classes.Notification; namespace Penumbra.Services; -public class InstallNotification(string message, Action installRequest) : IMessage -{ - private bool _invoked = false; - - public string Message { get; } = message; - - public NotificationType NotificationType => NotificationType.Info; - - public uint NotificationDuration => 10000; - - public void OnNotificationActions(INotificationDrawArgs args) - { - if (ImUtf8.ButtonEx("Install"u8, "Install this mod."u8, disabled: _invoked)) - { - installRequest(true); - _invoked = true; - } - } -} - public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INotificationManager notificationManager) : OtterGui.Classes.MessageService(log, builder, chat, notificationManager), IService { @@ -79,11 +55,4 @@ public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INoti $"Cowardly refusing to load replacement for {originalGamePath.Filename().ToString().ToLowerInvariant()} by {mod.Name}{(messageComplement.Length > 0 ? ":\n" : ".")}{messageComplement}", NotificationType.Warning, 10000)); } - - public void PrintModFoundInfo(string fileName, Action installRequest) - { - AddMessage( - new InstallNotification($"A new mod has been found: {fileName}", installRequest) - ); - } } diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 46f4d38f..308cc471 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -37,7 +37,6 @@ public class SettingsTab : ITab, IUiService private readonly Penumbra _penumbra; private readonly FileDialogService _fileDialog; private readonly ModManager _modManager; - private readonly FileWatcher _fileWatcher; private readonly ModExportManager _modExportManager; private readonly ModFileSystemSelector _selector; private readonly CharacterUtility _characterUtility; @@ -66,7 +65,7 @@ public class SettingsTab : ITab, IUiService public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial, Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, - CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, FileWatcher fileWatcher, HttpApi httpApi, + CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi, DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig, IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService, MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService, @@ -83,7 +82,6 @@ public class SettingsTab : ITab, IUiService _characterUtility = characterUtility; _residentResources = residentResources; _modExportManager = modExportManager; - _fileWatcher = fileWatcher; _httpApi = httpApi; _dalamudSubstitutionProvider = dalamudSubstitutionProvider; _compactor = compactor; @@ -649,13 +647,6 @@ public class SettingsTab : ITab, IUiService DrawDefaultModImportFolder(); DrawPcpFolder(); DrawDefaultModExportPath(); - Checkbox("Enable Directory Watcher", - "Enables a File Watcher that automatically listens for Mod files that enter a specified directory, causing Penumbra to open a popup to import these mods.", - _config.EnableDirectoryWatch, v => _config.EnableDirectoryWatch = v); - Checkbox("Enable Fully Automatic Import", - "Uses the File Watcher in order to skip the query popup and automatically import any new mods.", - _config.EnableAutomaticModImport, v => _config.EnableAutomaticModImport = v); - DrawFileWatcherPath(); } @@ -735,45 +726,6 @@ public class SettingsTab : ITab, IUiService + "Keep this empty to use the root directory."); } - private string _tempWatchDirectory = string.Empty; - /// Draw input for the Automatic Mod import path. - private void DrawFileWatcherPath() - { - var tmp = _config.WatchDirectory; - var spacing = new Vector2(UiHelpers.ScaleX3); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); - ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3); - if (ImGui.InputText("##fileWatchPath", ref tmp, 256)) - _tempWatchDirectory = tmp; - - if (ImGui.IsItemDeactivatedAfterEdit()) - _fileWatcher.UpdateDirectory(_tempWatchDirectory); - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Folder.ToIconString()}##fileWatch", UiHelpers.IconButtonSize, - "Select a directory via dialog.", false, true)) - { - var startDir = _config.WatchDirectory.Length > 0 && Directory.Exists(_config.WatchDirectory) - ? _config.WatchDirectory - : Directory.Exists(_config.ModDirectory) - ? _config.ModDirectory - : null; - _fileDialog.OpenFolderPicker("Choose Automatic Import Directory", (b, s) => - { - if (b) - { - _fileWatcher.UpdateDirectory(s); - _config.WatchDirectory = s; - _config.Save(); - } - }, startDir, false); - } - - style.Pop(); - ImGuiUtil.LabeledHelpMarker("Automatic Import Director", - "Choose the Directory the File Watcher listens to."); - } - /// Draw input for the default name to input as author into newly generated mods. private void DrawDefaultModAuthor() {