Slight cleanup and autoformat.

This commit is contained in:
Ottermandias 2025-10-22 21:56:16 +02:00
parent c8cf560fc1
commit cbedc878b9
5 changed files with 66 additions and 40 deletions

View file

@ -53,7 +53,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService
public string ModDirectory { get; set; } = string.Empty; public string ModDirectory { get; set; } = string.Empty;
public string ExportDirectory { get; set; } = string.Empty; public string ExportDirectory { get; set; } = string.Empty;
public string WatchDirectory { get; set; } = string.Empty; public string WatchDirectory { get; set; } = string.Empty;
public bool? UseCrashHandler { get; set; } = null; public bool? UseCrashHandler { get; set; } = null;
public bool OpenWindowAtStart { get; set; } = false; public bool OpenWindowAtStart { get; set; } = false;

View file

@ -43,7 +43,6 @@ public class Penumbra : IDalamudPlugin
private readonly TempModManager _tempMods; private readonly TempModManager _tempMods;
private readonly TempCollectionManager _tempCollections; private readonly TempCollectionManager _tempCollections;
private readonly ModManager _modManager; private readonly ModManager _modManager;
private readonly FileWatcher _fileWatcher;
private readonly CollectionManager _collectionManager; private readonly CollectionManager _collectionManager;
private readonly Configuration _config; private readonly Configuration _config;
private readonly CharacterUtility _characterUtility; private readonly CharacterUtility _characterUtility;
@ -81,7 +80,6 @@ public class Penumbra : IDalamudPlugin
_residentResources = _services.GetService<ResidentResourceManager>(); _residentResources = _services.GetService<ResidentResourceManager>();
_services.GetService<ResourceManagerService>(); // Initialize because not required anywhere else. _services.GetService<ResourceManagerService>(); // Initialize because not required anywhere else.
_modManager = _services.GetService<ModManager>(); _modManager = _services.GetService<ModManager>();
_fileWatcher = _services.GetService<FileWatcher>();
_collectionManager = _services.GetService<CollectionManager>(); _collectionManager = _services.GetService<CollectionManager>();
_tempCollections = _services.GetService<TempCollectionManager>(); _tempCollections = _services.GetService<TempCollectionManager>();
_redrawService = _services.GetService<RedrawService>(); _redrawService = _services.GetService<RedrawService>();

View file

@ -1,40 +1,41 @@
using System.Threading.Channels; using System.Threading.Channels;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Services;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
namespace Penumbra.Services; namespace Penumbra.Services;
public class FileWatcher : IDisposable, IService public class FileWatcher : IDisposable, IService
{ {
private readonly FileSystemWatcher _fsw; private readonly FileSystemWatcher _fsw;
private readonly Channel<string> _queue; private readonly Channel<string> _queue;
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private readonly Task _consumer; private readonly Task _consumer;
private readonly ConcurrentDictionary<string, byte> _pending = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, byte> _pending = new(StringComparer.OrdinalIgnoreCase);
private readonly ModImportManager _modImportManager; private readonly ModImportManager _modImportManager;
private readonly MessageService _messageService; private readonly MessageService _messageService;
private readonly Configuration _config; private readonly Configuration _config;
public FileWatcher(ModImportManager modImportManager, MessageService messageService, Configuration config) public FileWatcher(ModImportManager modImportManager, MessageService messageService, Configuration config)
{ {
_modImportManager = modImportManager; _modImportManager = modImportManager;
_messageService = messageService; _messageService = messageService;
_config = config; _config = config;
if (!_config.EnableDirectoryWatch) return; if (!_config.EnableDirectoryWatch)
return;
_queue = Channel.CreateBounded<string>(new BoundedChannelOptions(256) _queue = Channel.CreateBounded<string>(new BoundedChannelOptions(256)
{ {
SingleReader = true, SingleReader = true,
SingleWriter = false, SingleWriter = false,
FullMode = BoundedChannelFullMode.DropOldest FullMode = BoundedChannelFullMode.DropOldest,
}); });
_fsw = new FileSystemWatcher(_config.WatchDirectory) _fsw = new FileSystemWatcher(_config.WatchDirectory)
{ {
IncludeSubdirectories = false, IncludeSubdirectories = false,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime, NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime,
InternalBufferSize = 32 * 1024 InternalBufferSize = 32 * 1024,
}; };
// Only wake us for the exact patterns we care about // Only wake us for the exact patterns we care about
@ -56,13 +57,17 @@ public class FileWatcher : IDisposable, IService
private void OnPath(object? sender, FileSystemEventArgs e) private void OnPath(object? sender, FileSystemEventArgs e)
{ {
// Cheap de-dupe: only queue once per filename until processed // Cheap de-dupe: only queue once per filename until processed
if (!_config.EnableDirectoryWatch || !_pending.TryAdd(e.FullPath, 0)) return; if (!_config.EnableDirectoryWatch || !_pending.TryAdd(e.FullPath, 0))
return;
_ = _queue.Writer.TryWrite(e.FullPath); _ = _queue.Writer.TryWrite(e.FullPath);
} }
private async Task ConsumerLoopAsync(CancellationToken token) private async Task ConsumerLoopAsync(CancellationToken token)
{ {
if (!_config.EnableDirectoryWatch) return; if (!_config.EnableDirectoryWatch)
return;
var reader = _queue.Reader; var reader = _queue.Reader;
while (await reader.WaitToReadAsync(token).ConfigureAwait(false)) while (await reader.WaitToReadAsync(token).ConfigureAwait(false))
{ {
@ -72,7 +77,10 @@ public class FileWatcher : IDisposable, IService
{ {
await ProcessOneAsync(path, token).ConfigureAwait(false); await ProcessOneAsync(path, token).ConfigureAwait(false);
} }
catch (OperationCanceledException) { Penumbra.Log.Debug($"[FileWatcher] Canceled via Token."); } catch (OperationCanceledException)
{
Penumbra.Log.Debug($"[FileWatcher] Canceled via Token.");
}
catch (Exception ex) catch (Exception ex)
{ {
Penumbra.Log.Debug($"[FileWatcher] Error during Processing: {ex}"); Penumbra.Log.Debug($"[FileWatcher] Error during Processing: {ex}");
@ -90,15 +98,19 @@ public class FileWatcher : IDisposable, IService
// Downloads often finish via rename; file may be locked briefly. // Downloads often finish via rename; file may be locked briefly.
// Wait until it exists and is readable; also require two stable size checks. // Wait until it exists and is readable; also require two stable size checks.
const int maxTries = 40; const int maxTries = 40;
long lastLen = -1; long lastLen = -1;
for (int i = 0; i < maxTries && !token.IsCancellationRequested; i++) for (var i = 0; i < maxTries && !token.IsCancellationRequested; i++)
{ {
if (!File.Exists(path)) { await Task.Delay(100, token); continue; } if (!File.Exists(path))
{
await Task.Delay(100, token);
continue;
}
try try
{ {
var fi = new FileInfo(path); var fi = new FileInfo(path);
var len = fi.Length; var len = fi.Length;
if (len > 0 && len == lastLen) if (len > 0 && len == lastLen)
{ {
@ -112,7 +124,9 @@ public class FileWatcher : IDisposable, IService
var invoked = false; var invoked = false;
Action<bool> installRequest = args => Action<bool> installRequest = args =>
{ {
if (invoked) return; if (invoked)
return;
invoked = true; invoked = true;
_modImportManager.AddUnpack(path); _modImportManager.AddUnpack(path);
}; };
@ -122,13 +136,19 @@ public class FileWatcher : IDisposable, IService
installRequest); installRequest);
return; return;
} }
} }
lastLen = len; lastLen = len;
} }
catch (IOException) { Penumbra.Log.Debug($"[FileWatcher] File is still being written to."); } catch (IOException)
catch (UnauthorizedAccessException) { Penumbra.Log.Debug($"[FileWatcher] File is locked."); } {
Penumbra.Log.Debug($"[FileWatcher] File is still being written to.");
}
catch (UnauthorizedAccessException)
{
Penumbra.Log.Debug($"[FileWatcher] File is locked.");
}
await Task.Delay(150, token); await Task.Delay(150, token);
} }
@ -136,21 +156,32 @@ public class FileWatcher : IDisposable, IService
public void UpdateDirectory(string newPath) public void UpdateDirectory(string newPath)
{ {
if (!_config.EnableDirectoryWatch || _fsw is null || !Directory.Exists(newPath) || string.IsNullOrWhiteSpace(newPath)) return; if (!_config.EnableDirectoryWatch || _fsw is null || !Directory.Exists(newPath) || string.IsNullOrWhiteSpace(newPath))
return;
_fsw.EnableRaisingEvents = false; _fsw.EnableRaisingEvents = false;
_fsw.Path = newPath; _fsw.Path = newPath;
_fsw.EnableRaisingEvents = true; _fsw.EnableRaisingEvents = true;
} }
public void Dispose() public void Dispose()
{ {
if (!_config.EnableDirectoryWatch) return; if (!_config.EnableDirectoryWatch)
return;
_fsw.EnableRaisingEvents = false; _fsw.EnableRaisingEvents = false;
_cts.Cancel(); _cts.Cancel();
_fsw.Dispose(); _fsw.Dispose();
_queue.Writer.TryComplete(); _queue.Writer.TryComplete();
try { _consumer.Wait(TimeSpan.FromSeconds(5)); } catch { /* swallow */ } try
{
_consumer.Wait(TimeSpan.FromSeconds(5));
}
catch
{
/* swallow */
}
_cts.Dispose(); _cts.Dispose();
} }
} }

View file

@ -20,7 +20,6 @@ namespace Penumbra.Services;
public class InstallNotification(string message, Action<bool> installRequest) : IMessage public class InstallNotification(string message, Action<bool> installRequest) : IMessage
{ {
private readonly Action<bool> _installRequest = installRequest;
private bool _invoked = false; private bool _invoked = false;
public string Message { get; } = message; public string Message { get; } = message;
@ -33,7 +32,7 @@ public class InstallNotification(string message, Action<bool> installRequest) :
{ {
if (ImUtf8.ButtonEx("Install"u8, "Install this mod."u8, disabled: _invoked)) if (ImUtf8.ButtonEx("Install"u8, "Install this mod."u8, disabled: _invoked))
{ {
_installRequest(true); installRequest(true);
_invoked = true; _invoked = true;
} }
} }

View file

@ -53,7 +53,6 @@ public class SettingsTab : ITab, IUiService
private readonly MigrationSectionDrawer _migrationDrawer; private readonly MigrationSectionDrawer _migrationDrawer;
private readonly CollectionAutoSelector _autoSelector; private readonly CollectionAutoSelector _autoSelector;
private readonly CleanupService _cleanupService; private readonly CleanupService _cleanupService;
private readonly MessageService _messageService;
private readonly AttributeHook _attributeHook; private readonly AttributeHook _attributeHook;
private readonly PcpService _pcpService; private readonly PcpService _pcpService;
@ -70,7 +69,7 @@ public class SettingsTab : ITab, IUiService
CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, FileWatcher fileWatcher, HttpApi httpApi, CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, FileWatcher fileWatcher, HttpApi httpApi,
DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig, DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig,
IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService, IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService,
MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService, MessageService messageService, MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService,
AttributeHook attributeHook, PcpService pcpService) AttributeHook attributeHook, PcpService pcpService)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
@ -97,7 +96,6 @@ public class SettingsTab : ITab, IUiService
_migrationDrawer = migrationDrawer; _migrationDrawer = migrationDrawer;
_autoSelector = autoSelector; _autoSelector = autoSelector;
_cleanupService = cleanupService; _cleanupService = cleanupService;
_messageService = messageService;
_attributeHook = attributeHook; _attributeHook = attributeHook;
_pcpService = pcpService; _pcpService = pcpService;
} }
@ -652,10 +650,10 @@ public class SettingsTab : ITab, IUiService
DrawPcpFolder(); DrawPcpFolder();
DrawDefaultModExportPath(); DrawDefaultModExportPath();
Checkbox("Enable Directory Watcher", Checkbox("Enable Directory Watcher",
"Enables a File Watcher that automatically listens for Mod files that enter, causing Penumbra to open a Popup to import these mods.", "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); _config.EnableDirectoryWatch, v => _config.EnableDirectoryWatch = v);
Checkbox("Enable Fully Automatic Import", Checkbox("Enable Fully Automatic Import",
"Uses the File Watcher in order to not just open a Popup, but fully automatically import new mods.", "Uses the File Watcher in order to skip the query popup and automatically import any new mods.",
_config.EnableAutomaticModImport, v => _config.EnableAutomaticModImport = v); _config.EnableAutomaticModImport, v => _config.EnableAutomaticModImport = v);
DrawFileWatcherPath(); DrawFileWatcherPath();
} }