Skip to content
1 change: 1 addition & 0 deletions Penumbra/Config/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public bool EnableMods
public bool DefaultTemporaryMode { get; set; } = false;
public bool EnableDirectoryWatch { get; set; } = false;
public bool EnableAutomaticModImport { get; set; } = false;
public bool EnableContainerPeeking { get; set; } = true;
public bool AutoDismissModImportSuccessReports { get; set; } = true;
public bool AlwaysShowDetailedModImport { get; set; } = false;
public bool PreventExportLoopback { get; set; } = true;
Expand Down
21 changes: 12 additions & 9 deletions Penumbra/Import/TexToolsImport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public partial class TexToolsImporter : IDisposable
private readonly CancellationTokenSource _cancellation = new();
private readonly CancellationToken _token;

public ImporterState State { get; private set; }
public readonly List<(FileInfo File, DirectoryInfo? Mod, Exception? Error)> ExtractedMods;
public ImporterState State { get; private set; }
public readonly List<ModImportResult> ExtractedMods;

private readonly Configuration _config;
private readonly DuplicateManager _duplicates;
Expand All @@ -37,8 +37,9 @@ public partial class TexToolsImporter : IDisposable
private readonly MigrationManager _migrationManager;

public TexToolsImporter(int count, IEnumerable<FileInfo> modPackFiles, Action<FileInfo, DirectoryInfo?, Exception?> handler,
Configuration config, DuplicateManager duplicates, ModNormalizer modNormalizer, ModManager modManager, FileCompactor compactor,
MigrationManager migrationManager, TexToolsImporter? previous)
TaskCompletionSource<ModImportResult[]> taskCompletionSource, Configuration config, DuplicateManager duplicates,
ModNormalizer modNormalizer, ModManager modManager, FileCompactor compactor, MigrationManager migrationManager,
TexToolsImporter? previous)
{
if (previous is not null)
{
Expand All @@ -58,7 +59,7 @@ public TexToolsImporter(int count, IEnumerable<FileInfo> modPackFiles, Action<Fi
_compactor = compactor;
_migrationManager = migrationManager;
_modPackCount = count + _previousModPackCount;
ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count + _previousModPackCount);
ExtractedMods = new List<ModImportResult>(count + _previousModPackCount);
_token = _cancellation.Token;
if (previous is not null)
ExtractedMods.AddRange(previous.ExtractedMods);
Expand All @@ -68,7 +69,9 @@ public TexToolsImporter(int count, IEnumerable<FileInfo> modPackFiles, Action<Fi
{
foreach (var (file, dir, error) in ExtractedMods.Skip(_previousModPackCount))
handler(file, dir, error);
}, TaskScheduler.Default);
}, TaskScheduler.Default)
.ContinueWith(_ => { taskCompletionSource.SetResult(ExtractedMods.Skip(_previousModPackCount).ToArray()); },
TaskScheduler.Default);
}

private void CloseStreams()
Expand Down Expand Up @@ -100,14 +103,14 @@ private void ImportFiles()
_currentModDirectory = null;
if (_token.IsCancellationRequested)
{
ExtractedMods.Add((file, null, new TaskCanceledException("Task canceled by user.")));
ExtractedMods.Add(new ModImportResult(file, null, new TaskCanceledException("Task canceled by user.")));
continue;
}

try
{
var directory = VerifyVersionAndImport(file);
ExtractedMods.Add((file, directory, null));
ExtractedMods.Add(new ModImportResult(file, directory, null));
if (_config.AutoDeduplicateOnImport)
{
State = ImporterState.DeduplicatingFiles;
Expand All @@ -116,7 +119,7 @@ private void ImportFiles()
}
catch (Exception e)
{
ExtractedMods.Add((file, _currentModDirectory, e));
ExtractedMods.Add(new ModImportResult(file, _currentModDirectory, e));
_currentNumOptions = 0;
_currentOptionIdx = 0;
_currentFileIdx = 0;
Expand Down
28 changes: 17 additions & 11 deletions Penumbra/Mods/Manager/ModImportManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class ModImportManager(
FileCompactor compactor) : IDisposable, IService
{
private readonly Dictionary<string, DateTime> _uniqueModsToUnpack = new(StringComparer.OrdinalIgnoreCase);
internal readonly Queue<List<string>> ModsToUnpack = new();
internal readonly Queue<UnpackRequest> ModsToUnpack = new();

/// <summary> Mods need to be added thread-safely outside of iteration. </summary>
private readonly ConcurrentQueue<DirectoryInfo> _modsToAdd = new();
Expand All @@ -31,14 +31,14 @@ public void TryUnpacking()
if (Importing && _import!.State is not ImporterState.Done)
return;

List<string> newMods;
UnpackRequest newMods;
lock (ModsToUnpack)
{
if (!ModsToUnpack.TryDequeue(out newMods!))
if (!ModsToUnpack.TryDequeue(out newMods))
return;
}

var files = newMods.Where(s =>
var files = newMods.Paths.Where(s =>
{
if (File.Exists(s))
return true;
Expand All @@ -50,10 +50,13 @@ public void TryUnpacking()

Penumbra.Log.Debug($"Unpacking mods: {string.Join("\n\t", files.Select(f => f.FullName))}.");
if (files.Length == 0)
{
newMods.TaskCompletionSource.SetResult([]);
return;
}

_import = new TexToolsImporter(files.Length, files, AddNewMod, config, duplicates, modNormalizer, modManager, compactor,
migrationManager, _import);
_import = new TexToolsImporter(files.Length, files, AddNewMod, newMods.TaskCompletionSource, config, duplicates, modNormalizer,
modManager, compactor, migrationManager, _import);
}

public bool Importing
Expand All @@ -65,10 +68,7 @@ public bool IsImporting([NotNullWhen(true)] out TexToolsImporter? importer)
return _import != null;
}

public void AddUnpack(IEnumerable<string> paths)
=> AddUnpack(paths.ToList());

public void AddUnpack(params List<string> paths)
public Task<ModImportResult[]> AddUnpack(params List<string> paths)
{
lock (ModsToUnpack)
{
Expand All @@ -95,9 +95,13 @@ public void AddUnpack(params List<string> paths)
if (paths.Count > 0)
{
Penumbra.Log.Debug($"Adding mods to install: {string.Join("\n\t", paths)}");
ModsToUnpack.Enqueue(paths);
var tcs = new TaskCompletionSource<ModImportResult[]>();
ModsToUnpack.Enqueue(new UnpackRequest(paths, tcs));
return tcs.Task;
}
}

return Task.FromResult(Array.Empty<ModImportResult>());
}

public void ClearImport()
Expand Down Expand Up @@ -156,4 +160,6 @@ private void AddNewMod(FileInfo file, DirectoryInfo? dir, Exception? error)
_modsToAdd.Enqueue(dir);
}
}

internal readonly record struct UnpackRequest(List<string> Paths, TaskCompletionSource<ModImportResult[]> TaskCompletionSource);
}
3 changes: 3 additions & 0 deletions Penumbra/Mods/Manager/ModImportResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Penumbra.Mods.Manager;

public readonly record struct ModImportResult(FileInfo File, DirectoryInfo? Mod, Exception? Error);
91 changes: 91 additions & 0 deletions Penumbra/Services/ArchiveExtractionNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification.EventArgs;
using ImSharp;
using Luna;

namespace Penumbra.Services;

public sealed class ArchiveExtractionNotification(MessageService messageService)
: AmassingNotification<ArchiveExtractionNotification.ArchiveInfo>(messageService), IService
{
public readonly record struct ArchiveInfo(string ArchiveName, int ModCount);

private volatile bool _isExtracting;
private volatile int _currentEntryIndex;
private volatile int _totalEntries;
private volatile string? _currentEntryName;

private bool _wasExtracting;

public void AddArchive(string archiveName, int modCount)
=> AddObject(new ArchiveInfo(archiveName, modCount));

public void SetProgress(int currentEntry, int totalEntries, string entryName)
{
_currentEntryIndex = currentEntry;
_totalEntries = totalEntries;
_currentEntryName = entryName;
_isExtracting = true;
}

public void ClearProgress()
{
_isExtracting = false;
_currentEntryIndex = 0;
_totalEntries = 0;
_currentEntryName = null;
}

public override NotificationType NotificationType
=> NotificationType.Info;

public override string NotificationTitle
=> Count switch
{
1 => $"Extracting mods from {GatheredObjects[0].ArchiveName}",
_ => $"Extracting mods from {Count} archives",
};

public override string NotificationMessage
=> _isExtracting
? $"Extracting '{_currentEntryName}'..."
: "Waiting...";

public override TimeSpan NotificationDuration
=> TimeSpan.MaxValue;

public override void NotificationActions(INotificationDrawArgs args)
{
if (_isExtracting)
{
_wasExtracting = true;
var total = _totalEntries;
if (total > 0 && CurrentNotification is { } notification)
{
notification.Progress = _currentEntryIndex / (float)total;
notification.Content = $"Extracting '{_currentEntryName}' ({_currentEntryIndex + 1} of {total})...";
}
}
else if (_wasExtracting)
{
_wasExtracting = false;
if (CurrentNotification is { } notification)
{
notification.Progress = 1.0f;
notification.Content = "Extraction complete.";
notification.InitialDuration = TimeSpan.FromSeconds(15);
}
}
}

protected override StoredNotification CreateStored(in ArchiveInfo @object)
=> new Stored(this, @object);

private sealed class Stored(ArchiveExtractionNotification parent, ArchiveInfo info)
: StoredNotification(parent, info)
{
public override string LogMessage { get; } = $"Extracted {info.ModCount} mod(s) from {info.ArchiveName}.";
public override StringU8 StoredMessage { get; } = new($"[{info.ArchiveName}] {info.ModCount} mod(s) extracted.");
public override StringU8 StoredTooltip { get; } = new($"Archive: {info.ArchiveName}\nMods found: {info.ModCount}");
}
}
Loading
Loading