mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Slight cleanup and autoformat.
This commit is contained in:
parent
c8cf560fc1
commit
cbedc878b9
5 changed files with 66 additions and 40 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
@ -127,8 +141,14 @@ public class FileWatcher : IDisposable, IService
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue