Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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