bug fixes and priority settings

This commit is contained in:
Timothy Baldridge
2021-10-20 21:18:15 -06:00
parent 2d0147ac8a
commit 24830c1df6
28 changed files with 158 additions and 93 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -119,8 +120,8 @@ namespace Wabbajack.App.ViewModels
_slides[InSlideRange(_currentSlideIndex + 1)].PreCache(_httpClient).FireAndForget(); _slides[InSlideRange(_currentSlideIndex + 1)].PreCache(_httpClient).FireAndForget();
var prevSlide = _slides[InSlideRange(_currentSlideIndex - 2)]; var prevSlide = _slides[InSlideRange(_currentSlideIndex - 2)];
if (prevSlide.Image != null) //if (prevSlide.Image != null)
prevSlide.Image = null; // prevSlide.Image = null;
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
@ -180,14 +181,15 @@ namespace Wabbajack.App.ViewModels
_installer = _provider.GetService<StandardInstaller>()!; _installer = _provider.GetService<StandardInstaller>()!;
_installer.OnStatusUpdate += (_, update) => _installer.OnStatusUpdate = async update =>
{ {
Dispatcher.UIThread.InvokeAsync(() => Trace.TraceInformation("Update....");
await Dispatcher.UIThread.InvokeAsync(() =>
{ {
StatusText = update.StatusText; StatusText = update.StatusText;
StepsProgress = update.StepsProgress; StepsProgress = update.StepsProgress;
StepProgress = update.StepProgress; StepProgress = update.StepProgress;
}); }, DispatcherPriority.Background);
}; };
_logger.LogInformation("Installer created, starting the installation process"); _logger.LogInformation("Installer created, starting the installation process");

View File

@ -3,6 +3,7 @@ using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Wabbajack.DTOs.DownloadStates; using Wabbajack.DTOs.DownloadStates;
@ -29,7 +30,13 @@ namespace Wabbajack.App.ViewModels.SubViewModels
{ {
Loading = true; Loading = true;
var url = await client.GetByteArrayAsync(MetaState.ImageURL); var url = await client.GetByteArrayAsync(MetaState.ImageURL);
Image = new Bitmap(new MemoryStream(url)); var img = new Bitmap(new MemoryStream(url));
await Dispatcher.UIThread.InvokeAsync(() =>
{
Image = img;
});
Loading = false; Loading = false;
} }

View File

@ -46,7 +46,6 @@ namespace Wabbajack.CLI
services.AddSingleton<TemporaryFileManager>(); services.AddSingleton<TemporaryFileManager>();
services.AddSingleton<FileExtractor.FileExtractor>(); services.AddSingleton<FileExtractor.FileExtractor>();
services.AddSingleton(new VFSCache(KnownFolders.EntryPoint.Combine("vfscache.sqlite"))); services.AddSingleton(new VFSCache(KnownFolders.EntryPoint.Combine("vfscache.sqlite")));
services.AddSingleton(new FileHashCache(KnownFolders.EntryPoint.Combine("filehashpath.sqlite")));
services.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}); services.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount});
services.AddSingleton<Client>(); services.AddSingleton<Client>();
services.AddSingleton<Networking.WabbajackClientApi.Client>(); services.AddSingleton<Networking.WabbajackClientApi.Client>();

View File

@ -468,6 +468,7 @@ namespace Wabbajack.CLI.Verbs
return $"{definition!.Hash.ToHex()}/parts/{idx}"; return $"{definition!.Hash.ToHex()}/parts/{idx}";
} }
/* Outdated
await definition.Parts.PDo(_parallelOptions, async part => await definition.Parts.PDo(_parallelOptions, async part =>
{ {
_logger.LogInformation("Uploading mirror part of {name} {hash} ({index}/{length})", archive.Name, archive.Hash, part.Index, definition.Parts.Length); _logger.LogInformation("Uploading mirror part of {name} {hash} ({index}/{length})", archive.Name, archive.Hash, part.Index, definition.Parts.Length);
@ -490,6 +491,7 @@ namespace Wabbajack.CLI.Verbs
}); });
*/
await CircuitBreaker.WithAutoRetryAllAsync(_logger, async () => await CircuitBreaker.WithAutoRetryAllAsync(_logger, async () =>
{ {
using var client = await GetMirrorFtpClient(token); using var client = await GetMirrorFtpClient(token);

View File

@ -4,39 +4,25 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using Wabbajack.RateLimiter;
namespace Wabbajack.Common namespace Wabbajack.Common
{ {
public static class AsyncParallelExtensions public static class AsyncParallelExtensions
{ {
public static IAsyncEnumerable<TOut> PMap<TIn, TOut>(this IEnumerable<TIn> coll, ParallelOptions options,
Func<TIn, Task<TOut>> mapFn)
{
var queue = Channel.CreateBounded<TOut>(options.MaxDegreeOfParallelism);
Parallel.ForEachAsync(coll, options, async (x, token) =>
{
var result = await mapFn(x);
await queue.Writer.WriteAsync(result, token);
}).ContinueWith(async t =>
{
queue.Writer.TryComplete();
}).FireAndForget();
return queue.Reader.ReadAllAsync();
}
public static async Task PDo<TIn>(this IEnumerable<TIn> coll, ParallelOptions options, Func<TIn, Task> mapFn)
{
await Parallel.ForEachAsync(coll, options, async (x, token) => await mapFn(x));
}
public static async Task PDoAll<TIn>(this IEnumerable<TIn> coll, Func<TIn, Task> mapFn) public static async Task PDoAll<TIn>(this IEnumerable<TIn> coll, Func<TIn, Task> mapFn)
{ {
var tasks = coll.Select(mapFn).ToList(); var tasks = coll.Select(mapFn).ToList();
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }
public static async Task PDoAll<TIn, TJob>(this IEnumerable<TIn> coll, IResource<TJob> limiter, Func<TIn, Task> mapFn)
{
using var job = await limiter.Begin("", 0, CancellationToken.None);
var tasks = coll.Select(mapFn).ToList();
await Task.WhenAll(tasks);
}
public static async IAsyncEnumerable<TOut> PMapAll<TIn, TOut>(this IEnumerable<TIn> coll, Func<TIn, Task<TOut>> mapFn) public static async IAsyncEnumerable<TOut> PMapAll<TIn, TOut>(this IEnumerable<TIn> coll, Func<TIn, Task<TOut>> mapFn)
{ {
var tasks = coll.Select(mapFn).ToList(); var tasks = coll.Select(mapFn).ToList();
@ -46,6 +32,16 @@ namespace Wabbajack.Common
} }
} }
public static async IAsyncEnumerable<TOut> PMapAll<TIn, TJob, TOut>(this IEnumerable<TIn> coll, IResource<TJob> limiter, Func<TIn, Task<TOut>> mapFn)
{
var tasks = coll.Select(mapFn).ToList();
foreach (var itm in tasks)
{
using var job = await limiter.Begin("", 0, CancellationToken.None);
yield return await itm;
}
}
public static async Task<List<T>> ToList<T>(this IAsyncEnumerable<T> coll) public static async Task<List<T>> ToList<T>(this IAsyncEnumerable<T> coll)
{ {
List<T> lst = new(); List<T> lst = new();

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace Wabbajack.Common namespace Wabbajack.Common
{ {
@ -40,5 +41,14 @@ namespace Wabbajack.Common
yield return itm; yield return itm;
} }
} }
public static async IAsyncEnumerable<T> OnEach<T>(this IEnumerable<T> coll, Func<T, Task> fn)
{
foreach (var itm in coll)
{
await fn(itm);
yield return itm;
}
}
} }
} }

View File

@ -16,6 +16,7 @@ using Wabbajack.DTOs.Texture;
using Wabbajack.Hashing.PHash; using Wabbajack.Hashing.PHash;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Xunit; using Xunit;
namespace Wabbajack.Compiler.Test namespace Wabbajack.Compiler.Test
@ -125,7 +126,8 @@ namespace Wabbajack.Compiler.Test
bsa.Delete(); bsa.Delete();
var creator = BSADispatch.CreateBuilder(bsaState, _manager); var creator = BSADispatch.CreateBuilder(bsaState, _manager);
await fileStates.Take(2).PDo(_parallelOptions, async f => await creator.AddFile(f, f.Path.RelativeTo(_mod.FullPath).Open(FileMode.Open),CancellationToken.None)); await fileStates.Take(2).PDoAll(new Resource<CompilerSanityTests>(),
async f => await creator.AddFile(f, f.Path.RelativeTo(_mod.FullPath).Open(FileMode.Open),CancellationToken.None));
{ {
await using var fs = bsa.Open(FileMode.Create, FileAccess.Write); await using var fs = bsa.Open(FileMode.Create, FileAccess.Write);
await creator.Build(fs, CancellationToken.None); await creator.Build(fs, CancellationToken.None);

View File

@ -56,15 +56,17 @@ namespace Wabbajack.Compiler
private string _statusText; private string _statusText;
private long _currentStepProgress; private long _currentStepProgress;
private readonly Stopwatch _updateStopWatch = new(); private readonly Stopwatch _updateStopWatch = new();
public readonly IResource<ACompiler> CompilerLimiter;
protected long MaxSteps { get; set; } protected long MaxSteps { get; set; }
public event EventHandler<StatusUpdate> OnStatusUpdate; public event EventHandler<StatusUpdate> OnStatusUpdate;
public ACompiler(ILogger logger, FileExtractor.FileExtractor extractor, FileHashCache hashCache, Context vfs, TemporaryFileManager manager, CompilerSettings settings, public ACompiler(ILogger logger, FileExtractor.FileExtractor extractor, FileHashCache hashCache, Context vfs, TemporaryFileManager manager, CompilerSettings settings,
ParallelOptions parallelOptions, DownloadDispatcher dispatcher, Client wjClient, IGameLocator locator, DTOSerializer dtos, ParallelOptions parallelOptions, DownloadDispatcher dispatcher, Client wjClient, IGameLocator locator, DTOSerializer dtos, IResource<ACompiler> compilerLimiter,
IBinaryPatchCache patchCache) IBinaryPatchCache patchCache)
{ {
CompilerLimiter = compilerLimiter;
_logger = logger; _logger = logger;
_extractor = extractor; _extractor = extractor;
_hashCache = hashCache; _hashCache = hashCache;
@ -196,7 +198,7 @@ namespace Wabbajack.Compiler
{ {
_logger.LogInformation("Getting meta data for {count} archives", SelectedArchives.Count); _logger.LogInformation("Getting meta data for {count} archives", SelectedArchives.Count);
NextStep("Gathering Metadata", SelectedArchives.Count); NextStep("Gathering Metadata", SelectedArchives.Count);
await SelectedArchives.PDo(_parallelOptions, async a => await SelectedArchives.PDoAll(CompilerLimiter, async a =>
{ {
UpdateProgress(1); UpdateProgress(1);
await _dispatcher.FillInMetadata(a); await _dispatcher.FillInMetadata(a);
@ -263,7 +265,7 @@ namespace Wabbajack.Compiler
protected async Task CleanInvalidArchivesAndFillState() protected async Task CleanInvalidArchivesAndFillState()
{ {
NextStep("Cleaning Invalid Archives", 1); NextStep("Cleaning Invalid Archives", 1);
var remove = await IndexedArchives.PMap(_parallelOptions, async a => var remove = await IndexedArchives.PMapAll(CompilerLimiter, async a =>
{ {
try try
{ {
@ -319,7 +321,7 @@ namespace Wabbajack.Compiler
var toFind = await _settings.Downloads.EnumerateFiles() var toFind = await _settings.Downloads.EnumerateFiles()
.Where(f => f.Extension != Ext.Meta) .Where(f => f.Extension != Ext.Meta)
.PMap(_parallelOptions, async f => await HasInvalidMeta(f) ? f : default) .PMapAll(CompilerLimiter,async f => await HasInvalidMeta(f) ? f : default)
.Where(f => f != default) .Where(f => f != default)
.Where(f => f.FileExists()) .Where(f => f.FileExists())
.ToList(); .ToList();
@ -480,7 +482,7 @@ namespace Wabbajack.Compiler
// Load in the patches // Load in the patches
await InstallDirectives.OfType<PatchedFromArchive>() await InstallDirectives.OfType<PatchedFromArchive>()
.Where(p => p.PatchID == default) .Where(p => p.PatchID == default)
.PDo(_parallelOptions, async pfa => .PDoAll(CompilerLimiter,async pfa =>
{ {
var patches = await _patchOptions[pfa] var patches = await _patchOptions[pfa]
@ -556,7 +558,7 @@ namespace Wabbajack.Compiler
.ToDictionary(f => f.Key, f => f.First()); .ToDictionary(f => f.Key, f => f.First());
SelectedArchives.Clear(); SelectedArchives.Clear();
SelectedArchives.AddRange(await hashes.PMap(_parallelOptions, hash => SelectedArchives.AddRange(await hashes.PMapAll(CompilerLimiter,hash =>
{ {
UpdateProgress(1); UpdateProgress(1);
return ResolveArchive(hash, archives); return ResolveArchive(hash, archives);

View File

@ -83,7 +83,7 @@ namespace Wabbajack.Compiler.CompilationSteps
//_cleanup = await source.File.Context.Stage(source.File.Children); //_cleanup = await source.File.Context.Stage(source.File.Children);
} }
var matches = await sourceFiles.PMap(_compiler._parallelOptions, e => _mo2Compiler.RunStack(stack, new RawSourceFile(e, Consts.BSACreationDir.Combine((RelativePath)id, (RelativePath)e.Name)))) var matches = await sourceFiles.PMapAll(_compiler.CompilerLimiter, e => _mo2Compiler.RunStack(stack, new RawSourceFile(e, Consts.BSACreationDir.Combine((RelativePath)id, (RelativePath)e.Name))))
.ToList(); .ToList();

View File

@ -16,6 +16,7 @@ using Wabbajack.Installer;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Wabbajack.VFS; using Wabbajack.VFS;
namespace Wabbajack.Compiler namespace Wabbajack.Compiler
@ -26,8 +27,8 @@ namespace Wabbajack.Compiler
public MO2Compiler(ILogger<MO2Compiler> logger, FileExtractor.FileExtractor extractor, FileHashCache hashCache, Context vfs, public MO2Compiler(ILogger<MO2Compiler> logger, FileExtractor.FileExtractor extractor, FileHashCache hashCache, Context vfs,
TemporaryFileManager manager, MO2CompilerSettings settings, ParallelOptions parallelOptions, DownloadDispatcher dispatcher, TemporaryFileManager manager, MO2CompilerSettings settings, ParallelOptions parallelOptions, DownloadDispatcher dispatcher,
Client wjClient, IGameLocator locator, DTOSerializer dtos, IBinaryPatchCache patchCache) : Client wjClient, IGameLocator locator, DTOSerializer dtos, IResource<ACompiler> compilerLimiter, IBinaryPatchCache patchCache) :
base(logger, extractor, hashCache, vfs, manager, settings, parallelOptions, dispatcher, wjClient, locator, dtos, patchCache) base(logger, extractor, hashCache, vfs, manager, settings, parallelOptions, dispatcher, wjClient, locator, dtos, compilerLimiter, patchCache)
{ {
MaxSteps = 14; MaxSteps = 14;
} }
@ -64,7 +65,7 @@ namespace Wabbajack.Compiler
// Find all Downloads // Find all Downloads
IndexedArchives = await Settings.Downloads.EnumerateFiles() IndexedArchives = await Settings.Downloads.EnumerateFiles()
.Where(f => f.WithExtension(Ext.Meta).FileExists()) .Where(f => f.WithExtension(Ext.Meta).FileExists())
.PMap(_parallelOptions, .PMapAll(CompilerLimiter,
async f => new IndexedArchive(_vfs.Index.ByRootPath[f]) async f => new IndexedArchive(_vfs.Index.ByRootPath[f])
{ {
Name = (string)f.FileName, Name = (string)f.FileName,
@ -120,7 +121,7 @@ namespace Wabbajack.Compiler
var stack = MakeStack(); var stack = MakeStack();
NextStep("Running Compilation Stack", AllFiles.Count); NextStep("Running Compilation Stack", AllFiles.Count);
var results = await AllFiles.PMap(_parallelOptions, f => var results = await AllFiles.PMapAll(CompilerLimiter, f =>
{ {
UpdateProgress(1); UpdateProgress(1);
return RunStack(stack, f); return RunStack(stack, f);

View File

@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Xunit; using Xunit;
namespace Wabbajack.Compression.BSA.Test namespace Wabbajack.Compression.BSA.Test
@ -55,7 +56,7 @@ namespace Wabbajack.Compression.BSA.Test
var reader = await BSADispatch.Open(path); var reader = await BSADispatch.Open(path);
var dataStates = await reader.Files var dataStates = await reader.Files
.PMap(_parallelOptions, .PMapAll(new Resource<CompressionTests>("Compression Test", 4),
async file => async file =>
{ {
var ms = new MemoryStream(); var ms = new MemoryStream();
@ -69,7 +70,7 @@ namespace Wabbajack.Compression.BSA.Test
var build = BSADispatch.CreateBuilder(oldState, _tempManager); var build = BSADispatch.CreateBuilder(oldState, _tempManager);
await dataStates.PDo(_parallelOptions, await dataStates.PDoAll(
async itm => { await build.AddFile(itm.State, itm.Stream, CancellationToken.None); }); async itm => { await build.AddFile(itm.State, itm.Stream, CancellationToken.None); });
@ -79,7 +80,7 @@ namespace Wabbajack.Compression.BSA.Test
var reader2 = await BSADispatch.Open(new MemoryStreamFactory(rebuiltStream, path, path.LastModifiedUtc())); var reader2 = await BSADispatch.Open(new MemoryStreamFactory(rebuiltStream, path, path.LastModifiedUtc()));
await reader.Files.Zip(reader2.Files) await reader.Files.Zip(reader2.Files)
.PDo(_parallelOptions, async pair => .PDoAll(async pair =>
{ {
var (oldFile, newFile) = pair; var (oldFile, newFile) = pair;
_logger.LogInformation("Comparing {old} and {new}", oldFile.Path, newFile.Path); _logger.LogInformation("Comparing {old} and {new}", oldFile.Path, newFile.Path);

View File

@ -7,6 +7,7 @@ using Wabbajack.DTOs.JsonConverters;
using Wabbajack.DTOs.Validation; using Wabbajack.DTOs.Validation;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Xunit; using Xunit;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
@ -79,7 +80,8 @@ namespace Wabbajack.DTOs.Test
var statuses = await _wjClient.GetListStatuses(); var statuses = await _wjClient.GetListStatuses();
Assert.True(statuses.Length > 10); Assert.True(statuses.Length > 10);
await statuses.PDo(_parallelOptions, async status => await statuses.PDoAll(new Resource<ModListTests>("Resource Test", 4),
async status =>
{ {
_logger.LogInformation("Loading {machineURL}", status.MachineURL); _logger.LogInformation("Loading {machineURL}", status.MachineURL);
var detailed = await _wjClient.GetDetailedStatus(status.MachineURL); var detailed = await _wjClient.GetDetailedStatus(status.MachineURL);

View File

@ -277,7 +277,7 @@ namespace Wabbajack.FileExtractor
}*/ }*/
var results = await dest.Path.EnumerateFiles() var results = await dest.Path.EnumerateFiles()
.PMap(_parallelOptions, async f => .PMapAll(async f =>
{ {
var path = f.RelativeTo(dest.Path); var path = f.RelativeTo(dest.Path);
if (!shouldExtract(path)) return ((RelativePath, T))default; if (!shouldExtract(path)) return ((RelativePath, T))default;

View File

@ -1,14 +1,15 @@
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Wabbajack.RateLimiter;
namespace Wabbajack.Hashing.xxHash64 namespace Wabbajack.Hashing.xxHash64
{ {
public static class ByteArrayExtensions public static class ByteArrayExtensions
{ {
public static async ValueTask<Hash> Hash(this byte[] data) public static async ValueTask<Hash> Hash(this byte[] data, IJob job = null)
{ {
return await new MemoryStream(data).HashingCopy(Stream.Null, CancellationToken.None); return await new MemoryStream(data).HashingCopy(Stream.Null, CancellationToken.None, job);
} }
} }

View File

@ -55,7 +55,7 @@ namespace Wabbajack.Installer
protected long MaxSteps { get; set; } protected long MaxSteps { get; set; }
public event EventHandler<StatusUpdate> OnStatusUpdate; public Func<StatusUpdate, Task>? OnStatusUpdate;
@ -79,7 +79,7 @@ namespace Wabbajack.Installer
_wjClient = wjClient; _wjClient = wjClient;
} }
public void NextStep(string statusText, long maxStepProgress) public async Task NextStep(string statusText, long maxStepProgress)
{ {
_updateStopWatch.Restart(); _updateStopWatch.Restart();
_maxStepProgress = maxStepProgress; _maxStepProgress = maxStepProgress;
@ -89,24 +89,27 @@ namespace Wabbajack.Installer
if (OnStatusUpdate != null) if (OnStatusUpdate != null)
{ {
OnStatusUpdate(this, new StatusUpdate($"[{_currentStep}/{MaxSteps}] " + statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps), await OnStatusUpdate!(new StatusUpdate($"[{_currentStep}/{MaxSteps}] " + statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps), Percent.Zero));
Percent.Zero));
} }
} }
public void UpdateProgress(long stepProgress)
private const int _limitMS = 100;
public async ValueTask UpdateProgress(long stepProgress)
{ {
Interlocked.Add(ref _currentStepProgress, stepProgress); Interlocked.Add(ref _currentStepProgress, stepProgress);
if (_updateStopWatch.ElapsedMilliseconds < _limitMS) return;
lock (_updateStopWatch) lock (_updateStopWatch)
{ {
if (_updateStopWatch.ElapsedMilliseconds < 100) return; if (_updateStopWatch.ElapsedMilliseconds < _limitMS) return;
_updateStopWatch.Restart(); _updateStopWatch.Restart();
} }
if (OnStatusUpdate != null) if (OnStatusUpdate != null)
{ {
OnStatusUpdate(this, new StatusUpdate(_statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps), await OnStatusUpdate!(new StatusUpdate(_statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress))); Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress)));
} }
} }
@ -184,7 +187,7 @@ namespace Wabbajack.Installer
public async Task InstallArchives(CancellationToken token) public async Task InstallArchives(CancellationToken token)
{ {
NextStep("Installing files", ModList.Directives.Sum(d => d.Size)); await NextStep("Installing files", ModList.Directives.Sum(d => d.Size));
var grouped = ModList.Directives var grouped = ModList.Directives
.OfType<FromArchive>() .OfType<FromArchive>()
.Select(a => new { VF = _vfs.Index.FileForArchiveHashPath(a.ArchiveHashPath), Directive = a }) .Select(a => new { VF = _vfs.Index.FileForArchiveHashPath(a.ArchiveHashPath), Directive = a })
@ -198,7 +201,7 @@ namespace Wabbajack.Installer
foreach (var directive in grouped[vf]) foreach (var directive in grouped[vf])
{ {
var file = directive.Directive; var file = directive.Directive;
UpdateProgress(file.Size); await UpdateProgress(file.Size);
switch (file) switch (file)
{ {
@ -285,7 +288,7 @@ namespace Wabbajack.Installer
} }
_logger.LogInformation("Downloading {count} archives", missing.Count); _logger.LogInformation("Downloading {count} archives", missing.Count);
NextStep("Downloading files", missing.Count); await NextStep("Downloading files", missing.Count);
await missing await missing
.OrderBy(a => a.Size) .OrderBy(a => a.Size)
@ -306,7 +309,7 @@ namespace Wabbajack.Installer
} }
await DownloadArchive(archive, download, token, outputPath); await DownloadArchive(archive, download, token, outputPath);
UpdateProgress(1); await UpdateProgress(1);
}); });
} }
@ -362,7 +365,7 @@ namespace Wabbajack.Installer
var hashResults = await var hashResults = await
toHash toHash
.PMap(_parallelOptions, async e => (await _fileHashCache.FileHashCachedAsync(e, token), e)) .PMapAll(async e => (await _fileHashCache.FileHashCachedAsync(e, token), e))
.ToList(); .ToList();
HashedArchives = hashResults HashedArchives = hashResults
@ -391,11 +394,11 @@ namespace Wabbajack.Installer
var savePath = (RelativePath)"saves"; var savePath = (RelativePath)"saves";
var existingFiles = _configuration.Install.EnumerateFiles().ToList(); var existingFiles = _configuration.Install.EnumerateFiles().ToList();
NextStep("Optimizing Modlist: Looking for files to delete", existingFiles.Count); await NextStep("Optimizing Modlist: Looking for files to delete", existingFiles.Count);
await existingFiles await existingFiles
.PDo(_parallelOptions, async f => .PDoAll(async f =>
{ {
UpdateProgress(1); await UpdateProgress(1);
var relativeTo = f.RelativeTo(_configuration.Install); var relativeTo = f.RelativeTo(_configuration.Install);
if (indexed.ContainsKey(relativeTo) || f.InFolder(_configuration.Downloads)) if (indexed.ContainsKey(relativeTo) || f.InFolder(_configuration.Downloads))
return; return;
@ -410,12 +413,12 @@ namespace Wabbajack.Installer
}); });
_logger.LogInformation("Cleaning empty folders"); _logger.LogInformation("Cleaning empty folders");
NextStep("Optimizing Modlist: Cleaning empty folders", indexed.Keys.Count); await NextStep("Optimizing Modlist: Cleaning empty folders", indexed.Keys.Count);
var expectedFolders = indexed.Keys var expectedFolders = (await indexed.Keys
.Select(f => f.RelativeTo(_configuration.Install)) .Select(f => f.RelativeTo(_configuration.Install))
// We ignore the last part of the path, so we need a dummy file name // We ignore the last part of the path, so we need a dummy file name
.Append(_configuration.Downloads.Combine("_")) .Append(_configuration.Downloads.Combine("_"))
.OnEach(_ => UpdateProgress(1)) .OnEach(async _ => await UpdateProgress(1))
.Where(f => f.InFolder(_configuration.Install)) .Where(f => f.InFolder(_configuration.Install))
.SelectMany(path => .SelectMany(path =>
{ {
@ -424,6 +427,7 @@ namespace Wabbajack.Installer
var split = ((string)path.RelativeTo(_configuration.Install)).Split('\\'); var split = ((string)path.RelativeTo(_configuration.Install)).Split('\\');
return Enumerable.Range(1, split.Length - 1).Select(t => string.Join("\\", split.Take(t))); return Enumerable.Range(1, split.Length - 1).Select(t => string.Join("\\", split.Take(t)));
}) })
.ToList())
.Distinct() .Distinct()
.Select(p => _configuration.Install.Combine(p)) .Select(p => _configuration.Install.Combine(p))
.ToHashSet(); .ToHashSet();
@ -444,10 +448,10 @@ namespace Wabbajack.Installer
var existingfiles = _configuration.Install.EnumerateFiles().ToHashSet(); var existingfiles = _configuration.Install.EnumerateFiles().ToHashSet();
NextStep("Optimizing Modlist: Removing redundant directives", indexed.Count); await NextStep("Optimizing Modlist: Removing redundant directives", indexed.Count);
await indexed.Values.PMapAll<Directive, Directive?>(async d => await indexed.Values.PMapAll<Directive, Directive?>(async d =>
{ {
UpdateProgress(1); await UpdateProgress(1);
// Bit backwards, but we want to return null for // Bit backwards, but we want to return null for
// all files we *want* installed. We return the files // all files we *want* installed. We return the files
// to remove from the install list. // to remove from the install list.

View File

@ -33,6 +33,12 @@ namespace Wabbajack.Installer
return new FileIniDataParser(IniParser()).ReadFile(file.ToString()); return new FileIniDataParser(IniParser()).ReadFile(file.ToString());
} }
public static void SaveIniFile(this IniData data, AbsolutePath file)
{
var parser = new FileIniDataParser(IniParser());
parser.WriteFile(file.ToString(), data);
}
/// <summary> /// <summary>
/// Loads a INI from the given string /// Loads a INI from the given string
/// </summary> /// </summary>

View File

@ -121,6 +121,7 @@ namespace Wabbajack.Installer
await GenerateZEditMerges(token); await GenerateZEditMerges(token);
await ForcePortable(); await ForcePortable();
await RemapMO2File();
CreateOutputMods(); CreateOutputMods();
@ -129,11 +130,24 @@ namespace Wabbajack.Installer
await ExtractedModlistFolder!.DisposeAsync(); await ExtractedModlistFolder!.DisposeAsync();
await _wjClient.SendMetric(MetricNames.FinishInstall, ModList.Name); await _wjClient.SendMetric(MetricNames.FinishInstall, ModList.Name);
NextStep("Finished", 1); await NextStep("Finished", 1);
_logger.LogInformation("Finished Installation"); _logger.LogInformation("Finished Installation");
return true; return true;
} }
private async Task RemapMO2File()
{
var iniFile = _configuration.Install.Combine("ModOrganizer.ini");
if (!iniFile.FileExists()) return;
_logger.LogInformation("Remapping ModOrganizer.ini");
var iniData = iniFile.LoadIniFile();
var settings = iniData["Settings"];
settings["download_directory"] = _configuration.Downloads.ToString();
iniData.SaveIniFile(iniFile);
}
private void CreateOutputMods() private void CreateOutputMods()
{ {
_configuration.Install.Combine("profiles") _configuration.Install.Combine("profiles")
@ -185,7 +199,7 @@ namespace Wabbajack.Installer
private async Task InstallIncludedDownloadMetas(CancellationToken token) private async Task InstallIncludedDownloadMetas(CancellationToken token)
{ {
await ModList.Archives await ModList.Archives
.PDo(_parallelOptions, async archive => .PDoAll(async archive =>
{ {
if (HashedArchives.TryGetValue(archive.Hash, out var paths)) if (HashedArchives.TryGetValue(archive.Hash, out var paths))
{ {
@ -219,8 +233,9 @@ namespace Wabbajack.Installer
var sourceDir = _configuration.Install.Combine(BSACreationDir, bsa.TempID); var sourceDir = _configuration.Install.Combine(BSACreationDir, bsa.TempID);
var a = BSADispatch.CreateBuilder(bsa.State, _manager); var a = BSADispatch.CreateBuilder(bsa.State, _manager);
var streams = await bsa.FileStates.PMap(_parallelOptions, async state => var streams = await bsa.FileStates.PMapAll(async state =>
{ {
var fs = sourceDir.Combine(state.Path).Open(FileMode.Open, FileAccess.Read, FileShare.Read); var fs = sourceDir.Combine(state.Path).Open(FileMode.Open, FileAccess.Read, FileShare.Read);
await a.AddFile(state, fs, token); await a.AddFile(state, fs, token);
return fs; return fs;
@ -246,12 +261,12 @@ namespace Wabbajack.Installer
private async Task InstallIncludedFiles(CancellationToken token) private async Task InstallIncludedFiles(CancellationToken token)
{ {
_logger.LogInformation("Writing inline files"); _logger.LogInformation("Writing inline files");
NextStep("Installing Included Files", ModList.Directives.OfType<InlineFile>().Count()); await NextStep("Installing Included Files", ModList.Directives.OfType<InlineFile>().Count());
await ModList.Directives await ModList.Directives
.OfType<InlineFile>() .OfType<InlineFile>()
.PDo(_parallelOptions, async directive => .PDoAll(async directive =>
{ {
UpdateProgress(1); await UpdateProgress(1);
var outPath = _configuration.Install.Combine(directive.To); var outPath = _configuration.Install.Combine(directive.To);
outPath.Delete(); outPath.Delete();
@ -261,7 +276,7 @@ namespace Wabbajack.Installer
await WriteRemappedFile(file); await WriteRemappedFile(file);
break; break;
default: default:
await outPath.WriteAllBytesAsync(await LoadBytesFromPath(directive.SourceDataID)); await outPath.WriteAllBytesAsync(await LoadBytesFromPath(directive.SourceDataID), token: token);
break; break;
} }
}); });
@ -363,7 +378,7 @@ namespace Wabbajack.Installer
await _configuration.ModList await _configuration.ModList
.Directives .Directives
.OfType<MergedPatch>() .OfType<MergedPatch>()
.PDo(_parallelOptions, async m => .PDoAll(async m =>
{ {
_logger.LogInformation("Generating zEdit merge: {to}", m.To); _logger.LogInformation("Generating zEdit merge: {to}", m.To);

View File

@ -5,6 +5,7 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Wabbajack.Common; using Wabbajack.Common;
@ -20,6 +21,7 @@ using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.VFS;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
@ -37,9 +39,11 @@ namespace Wabbajack.Networking.WabbajackClientApi
private readonly ParallelOptions _parallelOptions; private readonly ParallelOptions _parallelOptions;
private readonly IResource<HttpClient> _limiter; private readonly IResource<HttpClient> _limiter;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly IResource<FileHashCache> _hashLimiter;
public Client(ILogger<Client> logger, HttpClient client, ITokenProvider<WabbajackApiState> token, DTOSerializer dtos, IResource<HttpClient> limiter, Configuration configuration) public Client(ILogger<Client> logger, HttpClient client, ITokenProvider<WabbajackApiState> token, DTOSerializer dtos,
IResource<HttpClient> limiter, IResource<FileHashCache> hashLimiter, Configuration configuration)
{ {
_configuration = configuration; _configuration = configuration;
_token = token; _token = token;
@ -47,6 +51,7 @@ namespace Wabbajack.Networking.WabbajackClientApi
_logger = logger; _logger = logger;
_dtos = dtos; _dtos = dtos;
_limiter = limiter; _limiter = limiter;
_hashLimiter = hashLimiter;
} }
private async ValueTask<HttpRequestMessage> MakeMessage(HttpMethod method, Uri uri) private async ValueTask<HttpRequestMessage> MakeMessage(HttpMethod method, Uri uri)
@ -163,15 +168,16 @@ namespace Wabbajack.Networking.WabbajackClientApi
OriginalFileName = path.FileName, OriginalFileName = path.FileName,
Size = path.Size(), Size = path.Size(),
Hash = await path.Hash(), Hash = await path.Hash(),
Parts = await parts.PMap(_parallelOptions, async part => Parts = await parts.PMapAll(async part =>
{ {
var buffer = new byte[part.Size]; var buffer = new byte[part.Size];
using var job = await _hashLimiter.Begin("Hashing part", part.Size, CancellationToken.None);
await using (var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) await using (var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
{ {
fs.Position = part.Offset; fs.Position = part.Offset;
await fs.ReadAsync(buffer); await fs.ReadAsync(buffer);
} }
part.Hash = await buffer.Hash(); part.Hash = await buffer.Hash(job);
return part; return part;
}).ToArray() }).ToArray()
}; };

View File

@ -16,6 +16,7 @@
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" /> <ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
<ProjectReference Include="..\Wabbajack.DTOs\Wabbajack.DTOs.csproj" /> <ProjectReference Include="..\Wabbajack.DTOs\Wabbajack.DTOs.csproj" />
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" /> <ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
<ProjectReference Include="..\Wabbajack.VFS\Wabbajack.VFS.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,6 +7,7 @@ namespace Wabbajack.RateLimiter
{ {
StatusReport StatusReport { get; } StatusReport StatusReport { get; }
string Name { get; } string Name { get; }
} }
public interface IResource<T> : IResource public interface IResource<T> : IResource
{ {

View File

@ -23,10 +23,10 @@ namespace Wabbajack.RateLimiter
public Resource(string humanName, int maxTasks = Int32.MaxValue, long maxThroughput = long.MaxValue) public Resource(string? humanName = null, int? maxTasks = 0, long maxThroughput = long.MaxValue)
{ {
_humanName = humanName; _humanName = humanName ?? "<unknown>";
_maxTasks = maxTasks; _maxTasks = maxTasks ?? Environment.ProcessorCount;
_maxThroughput = maxThroughput; _maxThroughput = maxThroughput;
_semaphore = new SemaphoreSlim(_maxTasks); _semaphore = new SemaphoreSlim(_maxTasks);

View File

@ -15,6 +15,7 @@ using Wabbajack.Hashing.xxHash64;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Wabbajack.Server.DataLayer; using Wabbajack.Server.DataLayer;
using Wabbajack.Server.DTOs; using Wabbajack.Server.DTOs;
using Wabbajack.Server.TokenProviders; using Wabbajack.Server.TokenProviders;
@ -105,7 +106,7 @@ namespace Wabbajack.Server.Services
return $"{definition.Hash.ToHex()}/parts/{idx}"; return $"{definition.Hash.ToHex()}/parts/{idx}";
} }
await definition.Parts.PDo(_parallelOptions, async part => await definition.Parts.PDoAll(new Resource<MirrorUploader>(), async part =>
{ {
_logger.LogInformation("Uploading mirror part ({index}/{length})", part.Index, definition.Parts.Length); _logger.LogInformation("Uploading mirror part ({index}/{length})", part.Index, definition.Parts.Length);

View File

@ -34,6 +34,7 @@ namespace Wabbajack.Server.Services
await _dispatcher.PrepareAll(archives.Select(a => a.State)); await _dispatcher.PrepareAll(archives.Select(a => a.State));
var random = new Random(); var random = new Random();
/*
var results = await archives.PMap(_parallelOptions, async archive => var results = await archives.PMap(_parallelOptions, async archive =>
{ {
try try
@ -82,7 +83,8 @@ namespace Wabbajack.Server.Services
_logger.Log(LogLevel.Warning, $"Validation failed for {archive.Name} from {archive.State.PrimaryKeyString}"); _logger.Log(LogLevel.Warning, $"Validation failed for {archive.Name} from {archive.State.PrimaryKeyString}");
_logger.Log(LogLevel.Information, $"Non-nexus validation completed {failed} out of {passed} failed"); _logger.Log(LogLevel.Information, $"Non-nexus validation completed {failed} out of {passed} failed");
return failed; */
return default;
} }
} }
} }

View File

@ -44,8 +44,8 @@ namespace Wabbajack.Services.OSIntegrated
service.AddTransient(s => new TemporaryFileManager(KnownFolders.EntryPoint.Combine("temp", Guid.NewGuid().ToString()))); service.AddTransient(s => new TemporaryFileManager(KnownFolders.EntryPoint.Combine("temp", Guid.NewGuid().ToString())));
service.AddSingleton(s => options.UseLocalCache ? service.AddSingleton(s => options.UseLocalCache ?
new FileHashCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path) new FileHashCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path, s.GetService<IResource<FileHashCache>>()!)
: new FileHashCache(KnownFolders.AppDataLocal.Combine("Wabbajack", "GlobalHashCache.sqlite"))); : new FileHashCache(KnownFolders.AppDataLocal.Combine("Wabbajack", "GlobalHashCache.sqlite"), s.GetService<IResource<FileHashCache>>()!));
service.AddSingleton(s => options.UseLocalCache ? service.AddSingleton(s => options.UseLocalCache ?
new VFSCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path) new VFSCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path)
@ -59,6 +59,7 @@ namespace Wabbajack.Services.OSIntegrated
service.AddAllSingleton<IResource, IResource<DownloadDispatcher>>(s => new Resource<DownloadDispatcher>("Downloads", 12)); service.AddAllSingleton<IResource, IResource<DownloadDispatcher>>(s => new Resource<DownloadDispatcher>("Downloads", 12));
service.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", 12)); service.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", 12));
service.AddAllSingleton<IResource, IResource<Context>>(s => new Resource<Context>("VFS", 12)); service.AddAllSingleton<IResource, IResource<Context>>(s => new Resource<Context>("VFS", 12));
service.AddAllSingleton<IResource, IResource<FileHashCache>>(s => new Resource<FileHashCache>("File Hashing", 12));
service.AddAllSingleton<IResource, IResource<FileExtractor.FileExtractor>>(s => service.AddAllSingleton<IResource, IResource<FileExtractor.FileExtractor>>(s =>
new Resource<FileExtractor.FileExtractor>("File Extractor", 12)); new Resource<FileExtractor.FileExtractor>("File Extractor", 12));

View File

@ -21,7 +21,7 @@ namespace Wabbajack.VFS.Test
// Keep this fixed at 2 so that we can detect deadlocks in the VFS parallelOptions // Keep this fixed at 2 so that we can detect deadlocks in the VFS parallelOptions
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = 2}); service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = 2});
service.AddSingleton(new FileHashCache(KnownFolders.EntryPoint.Combine("hashcache.sqlite"))); service.AddSingleton(new FileHashCache(KnownFolders.EntryPoint.Combine("hashcache.sqlite"), new Resource<FileHashCache>("File Hashing", 10)));
service.AddSingleton(new VFSCache(KnownFolders.EntryPoint.Combine("vfscache.sqlite"))); service.AddSingleton(new VFSCache(KnownFolders.EntryPoint.Combine("vfscache.sqlite")));
service.AddTransient<Context>(); service.AddTransient<Context>();
service.AddSingleton<FileExtractor.FileExtractor>(); service.AddSingleton<FileExtractor.FileExtractor>();

View File

@ -80,7 +80,7 @@ namespace Wabbajack.VFS
var filesToIndex = roots.SelectMany(root => root.EnumerateFiles()).ToList(); var filesToIndex = roots.SelectMany(root => root.EnumerateFiles()).ToList();
var allFiles = await filesToIndex var allFiles = await filesToIndex
.PMap(_parallelOptions, async f => .PMapAll(async f =>
{ {
if (native.TryGetValue(f, out var found)) if (native.TryGetValue(f, out var found))
if (found.LastModified == f.LastModifiedUtc().AsUnixTime() && found.Size == f.Size()) if (found.LastModified == f.LastModifiedUtc().AsUnixTime() && found.Size == f.Size())
@ -151,7 +151,7 @@ namespace Wabbajack.VFS
} }
} }
await filesByParent[top].PDo(_parallelOptions, await filesByParent[top].PDoAll(
async file => await HandleFile(file, new ExtractedNativeFile(file.AbsoluteName) { CanMove = false })); async file => await HandleFile(file, new ExtractedNativeFile(file.AbsoluteName) { CanMove = false }));
} }

View File

@ -14,9 +14,11 @@ namespace Wabbajack.VFS
private readonly SQLiteConnection _conn; private readonly SQLiteConnection _conn;
private readonly string _connectionString; private readonly string _connectionString;
private readonly AbsolutePath _location; private readonly AbsolutePath _location;
private readonly IResource<FileHashCache> _limiter;
public FileHashCache(AbsolutePath location) public FileHashCache(AbsolutePath location, IResource<FileHashCache> limiter)
{ {
_limiter = limiter;
_location = location; _location = location;
if (!_location.Parent.DirectoryExists()) if (!_location.Parent.DirectoryExists())
@ -114,10 +116,11 @@ namespace Wabbajack.VFS
WriteHashCache(file, hash); WriteHashCache(file, hash);
} }
public async Task<Hash> FileHashCachedAsync(AbsolutePath file, CancellationToken token, IJob? job = null) public async Task<Hash> FileHashCachedAsync(AbsolutePath file, CancellationToken token)
{ {
if (TryGetHashCache(file, out var foundHash)) return foundHash; if (TryGetHashCache(file, out var foundHash)) return foundHash;
using var job = await _limiter.Begin($"Hasing {file.FileName}", file.Size(), token);
await using var fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read); await using var fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
var hash = await fs.HashingCopy(Stream.Null, token, job); var hash = await fs.HashingCopy(Stream.Null, token, job);

View File

@ -168,7 +168,7 @@ namespace Wabbajack.VFS
{ {
var absPath = (AbsolutePath)extractedFile.Name; var absPath = (AbsolutePath)extractedFile.Name;
job.Size = absPath.Size(); job.Size = absPath.Size();
hash = await context.HashCache.FileHashCachedAsync(absPath, token, job); hash = await context.HashCache.FileHashCachedAsync(absPath, token);
} }
else else
{ {