mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Compare commits
2 commits
5bf901d0c4
...
c4b6e4e00b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4b6e4e00b | ||
|
|
912c183fc6 |
6 changed files with 167 additions and 135 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 283d51f6f6c7721a810548d95ba83eef2484e17e
|
Subproject commit d889f9ef918514a46049725052d378b441915b00
|
||||||
|
|
@ -1,37 +1,69 @@
|
||||||
using System.Threading.Channels;
|
using OtterGui.Services;
|
||||||
using OtterGui.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;
|
// TODO: use ConcurrentSet when it supports comparers in Luna.
|
||||||
private readonly Channel<string> _queue;
|
|
||||||
private readonly CancellationTokenSource _cts = new();
|
|
||||||
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;
|
||||||
|
|
||||||
|
private bool _pausedConsumer;
|
||||||
|
private FileSystemWatcher? _fsw;
|
||||||
|
private CancellationTokenSource? _cts = new();
|
||||||
|
private Task? _consumer;
|
||||||
|
|
||||||
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)
|
if (_config.EnableDirectoryWatch)
|
||||||
|
{
|
||||||
|
SetupFileWatcher(_config.WatchDirectory);
|
||||||
|
SetupConsumerTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Toggle(bool value)
|
||||||
|
{
|
||||||
|
if (_config.EnableDirectoryWatch == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_queue = Channel.CreateBounded<string>(new BoundedChannelOptions(256)
|
_config.EnableDirectoryWatch = value;
|
||||||
|
_config.Save();
|
||||||
|
if (value)
|
||||||
{
|
{
|
||||||
SingleReader = true,
|
SetupFileWatcher(_config.WatchDirectory);
|
||||||
SingleWriter = false,
|
SetupConsumerTask();
|
||||||
FullMode = BoundedChannelFullMode.DropOldest,
|
}
|
||||||
});
|
else
|
||||||
|
{
|
||||||
|
EndFileWatcher();
|
||||||
|
EndConsumerTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_fsw = new FileSystemWatcher(_config.WatchDirectory)
|
internal void PauseConsumer(bool pause)
|
||||||
|
=> _pausedConsumer = pause;
|
||||||
|
|
||||||
|
private void EndFileWatcher()
|
||||||
|
{
|
||||||
|
if (_fsw is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_fsw.Dispose();
|
||||||
|
_fsw = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupFileWatcher(string directory)
|
||||||
|
{
|
||||||
|
EndFileWatcher();
|
||||||
|
_fsw = new FileSystemWatcher
|
||||||
{
|
{
|
||||||
IncludeSubdirectories = false,
|
IncludeSubdirectories = false,
|
||||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime,
|
NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime,
|
||||||
|
|
@ -46,44 +78,77 @@ public class FileWatcher : IDisposable, IService
|
||||||
|
|
||||||
_fsw.Created += OnPath;
|
_fsw.Created += OnPath;
|
||||||
_fsw.Renamed += OnPath;
|
_fsw.Renamed += OnPath;
|
||||||
|
UpdateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void EndConsumerTask()
|
||||||
|
{
|
||||||
|
if (_cts is not null)
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_cts = null;
|
||||||
|
}
|
||||||
|
_consumer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupConsumerTask()
|
||||||
|
{
|
||||||
|
EndConsumerTask();
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
_consumer = Task.Factory.StartNew(
|
_consumer = Task.Factory.StartNew(
|
||||||
() => ConsumerLoopAsync(_cts.Token),
|
() => ConsumerLoopAsync(_cts.Token),
|
||||||
_cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
|
_cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateDirectory(string newPath)
|
||||||
|
{
|
||||||
|
if (_config.WatchDirectory != newPath)
|
||||||
|
{
|
||||||
|
_config.WatchDirectory = newPath;
|
||||||
|
_config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_fsw is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_fsw.EnableRaisingEvents = false;
|
||||||
|
if (!Directory.Exists(newPath) || newPath.Length is 0)
|
||||||
|
{
|
||||||
|
_fsw.Path = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fsw.Path = newPath;
|
||||||
_fsw.EnableRaisingEvents = true;
|
_fsw.EnableRaisingEvents = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPath(object? sender, FileSystemEventArgs e)
|
private void OnPath(object? sender, FileSystemEventArgs e)
|
||||||
{
|
=> _pending.TryAdd(e.FullPath, 0);
|
||||||
// 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)
|
private async Task ConsumerLoopAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!_config.EnableDirectoryWatch)
|
while (true)
|
||||||
return;
|
{
|
||||||
|
var (path, _) = _pending.FirstOrDefault();
|
||||||
|
if (path is null || _pausedConsumer)
|
||||||
|
{
|
||||||
|
await Task.Delay(500, token).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var reader = _queue.Reader;
|
|
||||||
while (await reader.WaitToReadAsync(token).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
while (reader.TryRead(out var path))
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProcessOneAsync(path, token).ConfigureAwait(false);
|
await ProcessOneAsync(path, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug($"[FileWatcher] Canceled via Token.");
|
Penumbra.Log.Debug("[FileWatcher] Canceled via Token.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Debug($"[FileWatcher] Error during Processing: {ex}");
|
Penumbra.Log.Warning($"[FileWatcher] Error during Processing: {ex}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -91,7 +156,6 @@ public class FileWatcher : IDisposable, IService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessOneAsync(string path, CancellationToken token)
|
private async Task ProcessOneAsync(string path, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
|
@ -115,28 +179,10 @@ public class FileWatcher : IDisposable, IService
|
||||||
if (len > 0 && len == lastLen)
|
if (len > 0 && len == lastLen)
|
||||||
{
|
{
|
||||||
if (_config.EnableAutomaticModImport)
|
if (_config.EnableAutomaticModImport)
|
||||||
{
|
|
||||||
_modImportManager.AddUnpack(path);
|
_modImportManager.AddUnpack(path);
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
_messageService.AddMessage(new InstallNotification(_modImportManager, path), false);
|
||||||
var invoked = false;
|
|
||||||
Action<bool> installRequest = args =>
|
|
||||||
{
|
|
||||||
if (invoked)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
invoked = true;
|
|
||||||
_modImportManager.AddUnpack(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
_messageService.PrintModFoundInfo(
|
|
||||||
Path.GetFileNameWithoutExtension(path),
|
|
||||||
installRequest);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLen = len;
|
lastLen = len;
|
||||||
|
|
@ -154,34 +200,10 @@ public class FileWatcher : IDisposable, IService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_config.EnableDirectoryWatch)
|
EndConsumerTask();
|
||||||
return;
|
EndFileWatcher();
|
||||||
|
|
||||||
_fsw.EnableRaisingEvents = false;
|
|
||||||
_cts.Cancel();
|
|
||||||
_fsw.Dispose();
|
|
||||||
_queue.Writer.TryComplete();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_consumer.Wait(TimeSpan.FromSeconds(5));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
/* swallow */
|
|
||||||
}
|
|
||||||
|
|
||||||
_cts.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
Penumbra/Services/InstallNotification.cs
Normal file
39
Penumbra/Services/InstallNotification.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
|
using Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
|
|
||||||
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
public class InstallNotification(ModImportManager modImportManager, string filePath) : OtterGui.Classes.MessageService.IMessage
|
||||||
|
{
|
||||||
|
public string Message
|
||||||
|
=> "A new mod has been found!";
|
||||||
|
|
||||||
|
public NotificationType NotificationType
|
||||||
|
=> NotificationType.Info;
|
||||||
|
|
||||||
|
public uint NotificationDuration
|
||||||
|
=> uint.MaxValue;
|
||||||
|
|
||||||
|
public string NotificationTitle { get; } = Path.GetFileNameWithoutExtension(filePath);
|
||||||
|
|
||||||
|
public string LogMessage
|
||||||
|
=> $"A new mod has been found: {Path.GetFileName(filePath)}";
|
||||||
|
|
||||||
|
public void OnNotificationActions(INotificationDrawArgs args)
|
||||||
|
{
|
||||||
|
var region = ImGui.GetContentRegionAvail();
|
||||||
|
var buttonSize = new Vector2((region.X - ImGui.GetStyle().ItemSpacing.X) / 2, 0);
|
||||||
|
if (ImUtf8.ButtonEx("Install"u8, ""u8, buttonSize))
|
||||||
|
{
|
||||||
|
modImportManager.AddUnpack(filePath);
|
||||||
|
args.Notification.DismissNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImUtf8.ButtonEx("Ignore"u8, ""u8, buttonSize))
|
||||||
|
args.Notification.DismissNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,43 +1,19 @@
|
||||||
using Dalamud.Bindings.ImGui;
|
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Interface.ImGuiNotification.EventArgs;
|
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using OtterGui.Text;
|
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using static OtterGui.Classes.MessageService;
|
|
||||||
using Notification = OtterGui.Classes.Notification;
|
using Notification = OtterGui.Classes.Notification;
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
public class InstallNotification(string message, Action<bool> 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)
|
public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INotificationManager notificationManager)
|
||||||
: OtterGui.Classes.MessageService(log, builder, chat, notificationManager), IService
|
: 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}",
|
$"Cowardly refusing to load replacement for {originalGamePath.Filename().ToString().ToLowerInvariant()} by {mod.Name}{(messageComplement.Length > 0 ? ":\n" : ".")}{messageComplement}",
|
||||||
NotificationType.Warning, 10000));
|
NotificationType.Warning, 10000));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrintModFoundInfo(string fileName, Action<bool> installRequest)
|
|
||||||
{
|
|
||||||
AddMessage(
|
|
||||||
new InstallNotification($"A new mod has been found: {fileName}", installRequest)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@ public class SettingsTab : ITab, IUiService
|
||||||
|
|
||||||
public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial,
|
public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial,
|
||||||
Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector,
|
Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector,
|
||||||
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,
|
MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService,
|
||||||
|
|
@ -651,7 +652,7 @@ public class SettingsTab : ITab, IUiService
|
||||||
DrawDefaultModExportPath();
|
DrawDefaultModExportPath();
|
||||||
Checkbox("Enable Directory Watcher",
|
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.",
|
"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, _fileWatcher.Toggle);
|
||||||
Checkbox("Enable Fully Automatic Import",
|
Checkbox("Enable Fully Automatic Import",
|
||||||
"Uses the File Watcher in order to skip the query popup and automatically import any 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);
|
||||||
|
|
@ -735,19 +736,24 @@ public class SettingsTab : ITab, IUiService
|
||||||
+ "Keep this empty to use the root directory.");
|
+ "Keep this empty to use the root directory.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _tempWatchDirectory = string.Empty;
|
private string? _tempWatchDirectory;
|
||||||
|
|
||||||
/// <summary> Draw input for the Automatic Mod import path. </summary>
|
/// <summary> Draw input for the Automatic Mod import path. </summary>
|
||||||
private void DrawFileWatcherPath()
|
private void DrawFileWatcherPath()
|
||||||
{
|
{
|
||||||
var tmp = _config.WatchDirectory;
|
var tmp = _tempWatchDirectory ?? _config.WatchDirectory;
|
||||||
var spacing = new Vector2(UiHelpers.ScaleX3);
|
var spacing = new Vector2(UiHelpers.ScaleX3);
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3);
|
ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3);
|
||||||
if (ImGui.InputText("##fileWatchPath", ref tmp, 256))
|
if (ImGui.InputText("##fileWatchPath", ref tmp, 256))
|
||||||
_tempWatchDirectory = tmp;
|
_tempWatchDirectory = tmp;
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivated() && _tempWatchDirectory is not null)
|
||||||
|
{
|
||||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||||
_fileWatcher.UpdateDirectory(_tempWatchDirectory);
|
_fileWatcher.UpdateDirectory(_tempWatchDirectory);
|
||||||
|
_tempWatchDirectory = null;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Folder.ToIconString()}##fileWatch", UiHelpers.IconButtonSize,
|
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Folder.ToIconString()}##fileWatch", UiHelpers.IconButtonSize,
|
||||||
|
|
@ -761,11 +767,7 @@ public class SettingsTab : ITab, IUiService
|
||||||
_fileDialog.OpenFolderPicker("Choose Automatic Import Directory", (b, s) =>
|
_fileDialog.OpenFolderPicker("Choose Automatic Import Directory", (b, s) =>
|
||||||
{
|
{
|
||||||
if (b)
|
if (b)
|
||||||
{
|
|
||||||
_fileWatcher.UpdateDirectory(s);
|
_fileWatcher.UpdateDirectory(s);
|
||||||
_config.WatchDirectory = s;
|
|
||||||
_config.Save();
|
|
||||||
}
|
|
||||||
}, startDir, false);
|
}, startDir, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"Description": "Runtime mod loader and manager.",
|
"Description": "Runtime mod loader and manager.",
|
||||||
"InternalName": "Penumbra",
|
"InternalName": "Penumbra",
|
||||||
"AssemblyVersion": "1.5.1.6",
|
"AssemblyVersion": "1.5.1.6",
|
||||||
"TestingAssemblyVersion": "1.5.1.6",
|
"TestingAssemblyVersion": "1.5.1.7",
|
||||||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 13,
|
"DalamudApiLevel": 13,
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"LoadRequiredState": 2,
|
"LoadRequiredState": 2,
|
||||||
"LoadSync": true,
|
"LoadSync": true,
|
||||||
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.6/Penumbra.zip",
|
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.6/Penumbra.zip",
|
||||||
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.6/Penumbra.zip",
|
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.5.1.7/Penumbra.zip",
|
||||||
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.6/Penumbra.zip",
|
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.6/Penumbra.zip",
|
||||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue