mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Merge pull request #2083 from wabbajack-tools/compiler-performance-fixes
Compiler performance fixes
This commit is contained in:
commit
1941e8161d
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,7 +1,15 @@
|
|||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
#### Version - 3.0.1.5 - 9/??/2022
|
#### Version - 3.0.1.5 - 9/26/2022
|
||||||
* Fix MO2ArchiveName resolution
|
* Fix MO2ArchiveName resolution
|
||||||
|
* Improve performance of the compiler stack
|
||||||
|
* Save the location of the browser window and open the next window in the same location
|
||||||
|
* Fix a leak of msedgwebview2.exe instances when doing manual downloads
|
||||||
|
* Massively improve patch load times
|
||||||
|
* Massively improve patch build times
|
||||||
|
* Reduce situations where the UI appears to be hung due the above two issues
|
||||||
|
* Fix file extraction progress bars not displaying properly (and going away)
|
||||||
|
* Update status bars to be a bit more accurate
|
||||||
|
|
||||||
#### Version - 3.0.1.4 - 9/21/2022
|
#### Version - 3.0.1.4 - 9/21/2022
|
||||||
* Fix several of case sensitive path comparisons, that could result in deleting downloads
|
* Fix several of case sensitive path comparisons, that could result in deleting downloads
|
||||||
|
@ -307,7 +307,7 @@ namespace Wabbajack
|
|||||||
.Subscribe(update =>
|
.Subscribe(update =>
|
||||||
{
|
{
|
||||||
var s = update.EventArgs;
|
var s = update.EventArgs;
|
||||||
StatusText = $"[{s.StepsProgress}] {s.StatusText}";
|
StatusText = $"[Step {s.CurrentStep}] {s.StatusText}";
|
||||||
StatusProgress = s.StepProgress;
|
StatusProgress = s.StepProgress;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
Style="{StaticResource {x:Type Window}}"
|
Style="{StaticResource {x:Type Window}}"
|
||||||
TitleBarHeight="25"
|
TitleBarHeight="25"
|
||||||
UseLayoutRounding="True"
|
UseLayoutRounding="True"
|
||||||
|
SaveWindowPosition="True"
|
||||||
WindowTitleBrush="{StaticResource MahApps.Brushes.Accent}"
|
WindowTitleBrush="{StaticResource MahApps.Brushes.Accent}"
|
||||||
ContentRendered="BrowserWindow_OnActivated"
|
ContentRendered="BrowserWindow_OnActivated"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
@ -54,6 +54,11 @@ public partial class BrowserWindow : MetroWindow
|
|||||||
});
|
});
|
||||||
|
|
||||||
vm.RunWrapper(CancellationToken.None)
|
vm.RunWrapper(CancellationToken.None)
|
||||||
.ContinueWith(_ => Dispatcher.Invoke(Close));
|
.ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
Browser.Dispose();
|
||||||
|
Browser = null;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,6 +62,47 @@ public static class AsyncParallelExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Faster version of PMapAll for when the function invocation will take a very small amount of time
|
||||||
|
/// batches all the inputs into N groups and executes them all on one task, where N is the number of
|
||||||
|
/// threads supported by the limiter
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="coll"></param>
|
||||||
|
/// <param name="limiter"></param>
|
||||||
|
/// <param name="mapFn"></param>
|
||||||
|
/// <typeparam name="TIn"></typeparam>
|
||||||
|
/// <typeparam name="TJob"></typeparam>
|
||||||
|
/// <typeparam name="TOut"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async IAsyncEnumerable<TOut> PMapAllBatched<TIn, TJob, TOut>(this IEnumerable<TIn> coll,
|
||||||
|
IResource<TJob> limiter, Func<TIn, Task<TOut>> mapFn)
|
||||||
|
{
|
||||||
|
var asList = coll.ToList();
|
||||||
|
|
||||||
|
var tasks = new List<Task<List<TOut>>>();
|
||||||
|
|
||||||
|
tasks.AddRange(Enumerable.Range(0, limiter.MaxTasks).Select(i => Task.Run(async () =>
|
||||||
|
{
|
||||||
|
using var job = await limiter.Begin(limiter.Name, asList.Count / limiter.MaxTasks, CancellationToken.None);
|
||||||
|
var list = new List<TOut>();
|
||||||
|
for (var idx = i; idx < asList.Count; idx += limiter.MaxTasks)
|
||||||
|
{
|
||||||
|
job.ReportNoWait(1);
|
||||||
|
list.Add(await mapFn(asList[idx]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
})));
|
||||||
|
|
||||||
|
foreach (var result in tasks)
|
||||||
|
{
|
||||||
|
foreach (var itm in (await result))
|
||||||
|
{
|
||||||
|
yield return itm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async IAsyncEnumerable<TOut> PKeepAll<TIn, TJob, TOut>(this IEnumerable<TIn> coll,
|
public static async IAsyncEnumerable<TOut> PKeepAll<TIn, TJob, TOut>(this IEnumerable<TIn> coll,
|
||||||
IResource<TJob> limiter, Func<TIn, Task<TOut>> mapFn)
|
IResource<TJob> limiter, Func<TIn, Task<TOut>> mapFn)
|
||||||
where TOut : class
|
where TOut : class
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Wabbajack.Common;
|
namespace Wabbajack.Common;
|
||||||
@ -13,6 +14,7 @@ public static class IEnumerableExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region Shuffle
|
#region Shuffle
|
||||||
|
|
||||||
/// https://stackoverflow.com/questions/5807128/an-extension-method-on-ienumerable-needed-for-shuffling
|
/// https://stackoverflow.com/questions/5807128/an-extension-method-on-ienumerable-needed-for-shuffling
|
||||||
|
|
||||||
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
|
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
|
||||||
@ -32,6 +34,7 @@ public static class IEnumerableExtensions
|
|||||||
buffer[j] = buffer[i];
|
buffer[j] = buffer[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@ -55,6 +58,22 @@ public static class IEnumerableExtensions
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> coll, int size)
|
||||||
|
{
|
||||||
|
var asList = coll.ToList();
|
||||||
|
|
||||||
|
IEnumerable<T> SkipEnumerable(IList<T> list, int offset, int size)
|
||||||
|
{
|
||||||
|
for (var i = offset; i < list.Count; i += size)
|
||||||
|
{
|
||||||
|
yield return list[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enumerable.Range(0, size).Select(offset => SkipEnumerable(asList, offset, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static IEnumerable<T> OnEach<T>(this IEnumerable<T> coll, Action<T> fn)
|
public static IEnumerable<T> OnEach<T>(this IEnumerable<T> coll, Action<T> fn)
|
||||||
{
|
{
|
||||||
foreach (var itm in coll)
|
foreach (var itm in coll)
|
||||||
|
@ -87,6 +87,7 @@ public class ModListHarness
|
|||||||
settings.ModListName = _profileName;
|
settings.ModListName = _profileName;
|
||||||
settings.Profile = _profileName;
|
settings.Profile = _profileName;
|
||||||
settings.OutputFile = _outputFile;
|
settings.OutputFile = _outputFile;
|
||||||
|
settings.UseTextureRecompression = true;
|
||||||
configureSettings(settings);
|
configureSettings(settings);
|
||||||
|
|
||||||
var modLines = _mods.Select(
|
var modLines = _mods.Select(
|
||||||
|
@ -16,6 +16,7 @@ using Wabbajack.DTOs;
|
|||||||
using Wabbajack.DTOs.Directives;
|
using Wabbajack.DTOs.Directives;
|
||||||
using Wabbajack.DTOs.DownloadStates;
|
using Wabbajack.DTOs.DownloadStates;
|
||||||
using Wabbajack.DTOs.JsonConverters;
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
|
using Wabbajack.FileExtractor.ExtractedFiles;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Installer;
|
using Wabbajack.Installer;
|
||||||
using Wabbajack.Networking.WabbajackClientApi;
|
using Wabbajack.Networking.WabbajackClientApi;
|
||||||
@ -120,7 +121,7 @@ public abstract class ACompiler
|
|||||||
if (OnStatusUpdate != null)
|
if (OnStatusUpdate != null)
|
||||||
OnStatusUpdate(this, new StatusUpdate(statusCategory, statusText,
|
OnStatusUpdate(this, new StatusUpdate(statusCategory, statusText,
|
||||||
Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||||
Percent.Zero));
|
Percent.Zero, _currentStep));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProgress(long stepProgress)
|
public void UpdateProgress(long stepProgress)
|
||||||
@ -135,7 +136,7 @@ public abstract class ACompiler
|
|||||||
|
|
||||||
if (OnStatusUpdate != null)
|
if (OnStatusUpdate != null)
|
||||||
OnStatusUpdate(this, new StatusUpdate(_statusCategory, _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
OnStatusUpdate(this, new StatusUpdate(_statusCategory, _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||||
Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress)));
|
Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress), _currentStep));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProgressAbsolute(long cur, long max)
|
public void UpdateProgressAbsolute(long cur, long max)
|
||||||
@ -151,7 +152,7 @@ public abstract class ACompiler
|
|||||||
|
|
||||||
if (OnStatusUpdate != null)
|
if (OnStatusUpdate != null)
|
||||||
OnStatusUpdate(this, new StatusUpdate(_statusCategory, _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
OnStatusUpdate(this, new StatusUpdate(_statusCategory, _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||||
Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress)));
|
Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress), _currentStep));
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task<bool> Begin(CancellationToken token);
|
public abstract Task<bool> Begin(CancellationToken token);
|
||||||
@ -395,12 +396,15 @@ public abstract class ACompiler
|
|||||||
|
|
||||||
_settings.OutputFile.Delete();
|
_settings.OutputFile.Delete();
|
||||||
|
|
||||||
|
var allFiles = _stagingFolder.EnumerateFiles().ToList();
|
||||||
|
NextStep("Finalizing", "Writing Wabbajack File", allFiles.Count);
|
||||||
await using (var fs = _settings.OutputFile.Open(FileMode.Create, FileAccess.Write))
|
await using (var fs = _settings.OutputFile.Open(FileMode.Create, FileAccess.Write))
|
||||||
{
|
{
|
||||||
using var za = new ZipArchive(fs, ZipArchiveMode.Create);
|
using var za = new ZipArchive(fs, ZipArchiveMode.Create);
|
||||||
|
|
||||||
foreach (var f in _stagingFolder.EnumerateFiles())
|
foreach (var f in allFiles)
|
||||||
{
|
{
|
||||||
|
UpdateProgress(1);
|
||||||
var ze = za.CreateEntry((string) f.FileName);
|
var ze = za.CreateEntry((string) f.FileName);
|
||||||
await using var os = ze.Open();
|
await using var os = ze.Open();
|
||||||
await using var ins = f.Open(FileMode.Open);
|
await using var ins = f.Open(FileMode.Open);
|
||||||
@ -447,6 +451,12 @@ public abstract class ACompiler
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected async Task BuildPatches(CancellationToken token)
|
protected async Task BuildPatches(CancellationToken token)
|
||||||
{
|
{
|
||||||
|
await using var tempPath = _manager.CreateFolder();
|
||||||
|
|
||||||
|
AbsolutePath TempPath(Hash file)
|
||||||
|
{
|
||||||
|
return tempPath.Path.Combine(file.ToHex());
|
||||||
|
}
|
||||||
|
|
||||||
NextStep("Compiling","Looking for patches");
|
NextStep("Compiling","Looking for patches");
|
||||||
var toBuild = InstallDirectives.OfType<PatchedFromArchive>()
|
var toBuild = InstallDirectives.OfType<PatchedFromArchive>()
|
||||||
@ -455,42 +465,55 @@ public abstract class ACompiler
|
|||||||
{
|
{
|
||||||
To = p.To,
|
To = p.To,
|
||||||
Hash = p.Hash,
|
Hash = p.Hash,
|
||||||
|
FromHash = c.Hash,
|
||||||
ArchiveHashPath = c.MakeRelativePaths(),
|
ArchiveHashPath = c.MakeRelativePaths(),
|
||||||
Size = p.Size
|
Size = p.Size
|
||||||
}))
|
}))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
NextStep("Compiling","Generating Patches", toBuild.Length);
|
if (toBuild.Any())
|
||||||
if (toBuild.Length == 0) return;
|
{
|
||||||
|
|
||||||
// Extract all the source files
|
NextStep("Compiling", "Generating Patches", toBuild.Length);
|
||||||
var indexed = toBuild.GroupBy(f => _vfs.Index.FileForArchiveHashPath(f.ArchiveHashPath))
|
|
||||||
.ToDictionary(f => f.Key);
|
var allFiles = toBuild.SelectMany(f =>
|
||||||
await _vfs.Extract(indexed.Keys.ToHashSet(),
|
|
||||||
async (vf, sf) =>
|
|
||||||
{
|
{
|
||||||
UpdateProgress(1);
|
UpdateProgress(1);
|
||||||
// For each, extract the destination
|
return new[]
|
||||||
var matches = indexed[vf];
|
|
||||||
foreach (var match in matches)
|
|
||||||
{
|
|
||||||
var destFile = FindDestFile(match.To);
|
|
||||||
_logger.LogInformation("Patching {from} {to}", destFile, match.To);
|
|
||||||
// Build the patch
|
|
||||||
await _vfs.Extract(new[] {destFile}.ToHashSet(),
|
|
||||||
async (destvf, destsfn) =>
|
|
||||||
{
|
{
|
||||||
|
_vfs.Index.FileForArchiveHashPath(f.ArchiveHashPath),
|
||||||
|
FindDestFile(f.To)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.DistinctBy(f => f.Hash)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
await using var srcStream = await sf.GetStream();
|
_logger.LogInformation("Extracting {Count} ({Size}) files for building patches", allFiles.Count,
|
||||||
await using var destStream = await destsfn.GetStream();
|
allFiles.Sum(f => f.Size).ToFileSizeString());
|
||||||
using var _ = await CompilerLimiter.Begin($"Patching {match.To}", 100, token);
|
|
||||||
var patchSize =
|
NextStep("Compiling", "Extracting Patch Files", allFiles.Count);
|
||||||
await _patchCache.CreatePatch(srcStream, vf.Hash, destStream, destvf.Hash);
|
await _vfs.Extract(allFiles, async (vf, file) =>
|
||||||
_logger.LogInformation("Patch size {patchSize} for {to}", patchSize, match.To);
|
{
|
||||||
|
UpdateProgress(1);
|
||||||
|
await using var ostream = TempPath(vf.Hash).Open(FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
await using var istream = await file.GetStream();
|
||||||
|
await istream.CopyToAsync(ostream, token);
|
||||||
}, token);
|
}, token);
|
||||||
}
|
|
||||||
}, token, runInParallel: false);
|
|
||||||
|
|
||||||
|
if (toBuild.Length == 0) return;
|
||||||
|
|
||||||
|
NextStep("Compiling", "Generating Patch Files", toBuild.Length);
|
||||||
|
await toBuild.PMapAllBatched(CompilerLimiter, async patch =>
|
||||||
|
{
|
||||||
|
UpdateProgress(1);
|
||||||
|
await using var src = TempPath(patch.FromHash).Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
await using var dst = TempPath(patch.Hash).Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
await _patchCache.CreatePatch(src, patch.FromHash, dst, patch.Hash);
|
||||||
|
return patch;
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
NextStep("Compiling", "Loading Patch Files");
|
||||||
// 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)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Compression.BSA;
|
using Wabbajack.Compression.BSA;
|
||||||
using Wabbajack.DTOs;
|
using Wabbajack.DTOs;
|
||||||
@ -73,6 +74,8 @@ public class DeconstructBSAs : ACompilationStep
|
|||||||
"Please re-compress this BSA into a more manageable size.");
|
"Please re-compress this BSA into a more manageable size.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_compiler._logger.LogInformation("Deconstructing BSA: {Name}", source.File.FullPath.FileName);
|
||||||
|
|
||||||
var sourceFiles = source.File.Children;
|
var sourceFiles = source.File.Children;
|
||||||
|
|
||||||
var stack = defaultInclude ? _microstackWithInclude(source.File) : _microstack(source.File);
|
var stack = defaultInclude ? _microstackWithInclude(source.File) : _microstack(source.File);
|
||||||
@ -86,8 +89,8 @@ public class DeconstructBSAs : ACompilationStep
|
|||||||
//_cleanup = await source.File.Context.Stage(source.File.Children);
|
//_cleanup = await source.File.Context.Stage(source.File.Children);
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = await sourceFiles.PMapAll(_compiler.CompilerLimiter,
|
var matches = await sourceFiles.SelectAsync(
|
||||||
e => _mo2Compiler.RunStack(stack,
|
async e => await _mo2Compiler.RunStack(stack,
|
||||||
new RawSourceFile(e, Consts.BSACreationDir.Combine(id, (RelativePath) e.Name))))
|
new RawSourceFile(e, Consts.BSACreationDir.Combine(id, (RelativePath) e.Name))))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ public class MO2Compiler : ACompiler
|
|||||||
var stack = MakeStack();
|
var stack = MakeStack();
|
||||||
|
|
||||||
NextStep("Compiling", "Running Compilation Stack", AllFiles.Count);
|
NextStep("Compiling", "Running Compilation Stack", AllFiles.Count);
|
||||||
var results = await AllFiles.PMapAll(CompilerLimiter, f =>
|
var results = await AllFiles.PMapAllBatched(CompilerLimiter, f =>
|
||||||
{
|
{
|
||||||
UpdateProgress(1);
|
UpdateProgress(1);
|
||||||
return RunStack(stack, f);
|
return RunStack(stack, f);
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Compiler.PatchCache;
|
using Wabbajack.Compiler.PatchCache;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
@ -15,94 +18,63 @@ public class BinaryPatchCache : IBinaryPatchCache
|
|||||||
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 ILogger<BinaryPatchCache> _logger;
|
||||||
|
|
||||||
public BinaryPatchCache(AbsolutePath location)
|
public BinaryPatchCache(ILogger<BinaryPatchCache> logger, AbsolutePath location)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_location = location;
|
_location = location;
|
||||||
if (!_location.Parent.DirectoryExists())
|
if (!_location.DirectoryExists())
|
||||||
_location.Parent.CreateDirectory();
|
_location.CreateDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
_connectionString =
|
public AbsolutePath PatchLocation(Hash srcHash, Hash destHash)
|
||||||
string.Intern($"URI=file:{location.ToString()};Pooling=True;Max Pool Size=100; Journal Mode=Memory;");
|
{
|
||||||
_conn = new SQLiteConnection(_connectionString);
|
return _location.Combine($"{srcHash.ToHex()}_{destHash.ToHex()}.octodiff");
|
||||||
_conn.Open();
|
|
||||||
|
|
||||||
using var cmd = new SQLiteCommand(_conn);
|
|
||||||
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS PatchCache (
|
|
||||||
FromHash BIGINT,
|
|
||||||
ToHash BIGINT,
|
|
||||||
PatchSize BLOB,
|
|
||||||
Patch BLOB,
|
|
||||||
PRIMARY KEY (FromHash, ToHash))
|
|
||||||
WITHOUT ROWID;";
|
|
||||||
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CacheEntry> CreatePatch(Stream srcStream, Hash srcHash, Stream destStream, Hash destHash, IJob? job)
|
public async Task<CacheEntry> CreatePatch(Stream srcStream, Hash srcHash, Stream destStream, Hash destHash, IJob? job)
|
||||||
{
|
{
|
||||||
await using var rcmd = new SQLiteCommand(_conn);
|
|
||||||
rcmd.CommandText = "SELECT PatchSize FROM PatchCache WHERE FromHash = @fromHash AND ToHash = @toHash";
|
|
||||||
rcmd.Parameters.AddWithValue("@fromHash", (long) srcHash);
|
|
||||||
rcmd.Parameters.AddWithValue("@toHash", (long) destHash);
|
|
||||||
|
|
||||||
await using var rdr = await rcmd.ExecuteReaderAsync();
|
var location = PatchLocation(srcHash, destHash);
|
||||||
while (await rdr.ReadAsync()) return new CacheEntry(srcHash, destHash, rdr.GetInt64(0), this);
|
if (location.FileExists())
|
||||||
|
return new CacheEntry(srcHash, destHash, location.Size(), this);
|
||||||
await using var cmd = new SQLiteCommand(_conn);
|
|
||||||
cmd.CommandText = @"INSERT INTO PatchCache (FromHash, ToHash, PatchSize, Patch)
|
|
||||||
VALUES (@fromHash, @toHash, @patchSize, @patch)";
|
|
||||||
|
|
||||||
cmd.Parameters.AddWithValue("@fromHash", (long) srcHash);
|
|
||||||
cmd.Parameters.AddWithValue("@toHash", (long) destHash);
|
|
||||||
|
|
||||||
await using var sigStream = new MemoryStream();
|
await using var sigStream = new MemoryStream();
|
||||||
await using var patchStream = new MemoryStream();
|
var tempName = _location.Combine(Guid.NewGuid().ToString()).WithExtension(Ext.Temp);
|
||||||
OctoDiff.Create(srcStream, destStream, sigStream, patchStream, job);
|
await using var patchStream = tempName.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None);
|
||||||
|
|
||||||
cmd.Parameters.AddWithValue("@patchSize", patchStream.Length);
|
|
||||||
cmd.Parameters.AddWithValue("@patch", patchStream.ToArray());
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
|
OctoDiff.Create(srcStream, destStream, sigStream, patchStream, job);
|
||||||
|
|
||||||
|
patchStream.Close();
|
||||||
|
await tempName.MoveToAsync(location, true, CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch (SQLiteException ex)
|
finally
|
||||||
{
|
{
|
||||||
if (!ex.Message.StartsWith("constraint failed"))
|
await patchStream.DisposeAsync();
|
||||||
throw;
|
if (tempName.FileExists())
|
||||||
|
tempName.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CacheEntry(srcHash, destHash, patchStream.Length, this);
|
return new CacheEntry(srcHash, destHash, location.Size(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<CacheEntry?> GetPatch(Hash fromHash, Hash toHash)
|
public async Task<CacheEntry?> GetPatch(Hash fromHash, Hash toHash)
|
||||||
{
|
{
|
||||||
await using var cmd = new SQLiteCommand(_conn);
|
var location = PatchLocation(fromHash, toHash);
|
||||||
cmd.CommandText = @"SELECT PatchSize FROM PatchCache WHERE FromHash = @fromHash AND ToHash = @toHash";
|
if (location.FileExists())
|
||||||
cmd.Parameters.AddWithValue("@fromHash", (long) fromHash);
|
return new CacheEntry(fromHash, toHash, location.Size(), this);
|
||||||
cmd.Parameters.AddWithValue("@toHash", (long) toHash);
|
|
||||||
|
|
||||||
await using var rdr = await cmd.ExecuteReaderAsync();
|
|
||||||
while (await rdr.ReadAsync()) return new CacheEntry(fromHash, toHash, rdr.GetInt64(0), this);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> GetData(CacheEntry entry)
|
public async Task<byte[]> GetData(CacheEntry entry)
|
||||||
{
|
{
|
||||||
await using var cmd = new SQLiteCommand(_conn);
|
var location = PatchLocation(entry.From, entry.To);
|
||||||
cmd.CommandText = @"SELECT PatchSize, Patch FROM PatchCache WHERE FromHash = @fromHash AND ToHash = @toHash";
|
if (location.FileExists())
|
||||||
cmd.Parameters.AddWithValue("@fromHash", (long) entry.From);
|
return await location.ReadAllBytesAsync();
|
||||||
cmd.Parameters.AddWithValue("@toHash", (long) entry.To);
|
|
||||||
|
|
||||||
await using var rdr = await cmd.ExecuteReaderAsync();
|
|
||||||
while (await rdr.ReadAsync())
|
|
||||||
{
|
|
||||||
var array = new byte[rdr.GetInt64(0)];
|
|
||||||
rdr.GetBytes(1, 0, array, 0, array.Length);
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.Empty<byte>();
|
return Array.Empty<byte>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,7 +459,7 @@ public static class GameRegistry
|
|||||||
|
|
||||||
public static GameMetaData? GetByMO2ArchiveName(string gameName)
|
public static GameMetaData? GetByMO2ArchiveName(string gameName)
|
||||||
{
|
{
|
||||||
return Games.Values.FirstOrDefault(g => (g.MO2ArchiveName ?? g.NexusName)!.Equals(gameName, StringComparison.InvariantCultureIgnoreCase));
|
return Games.Values.FirstOrDefault(g => (g.MO2ArchiveName ?? g.NexusName ?? "")!.Equals(gameName, StringComparison.InvariantCultureIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameMetaData? GetByNexusName(string gameName)
|
public static GameMetaData? GetByNexusName(string gameName)
|
||||||
|
@ -13,7 +13,6 @@ using OMODFramework;
|
|||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Common.FileSignatures;
|
using Wabbajack.Common.FileSignatures;
|
||||||
using Wabbajack.Compression.BSA;
|
using Wabbajack.Compression.BSA;
|
||||||
using Wabbajack.Compression.BSA.FO4Archive;
|
|
||||||
using Wabbajack.DTOs.Streams;
|
using Wabbajack.DTOs.Streams;
|
||||||
using Wabbajack.FileExtractor.ExtractedFiles;
|
using Wabbajack.FileExtractor.ExtractedFiles;
|
||||||
using Wabbajack.IO.Async;
|
using Wabbajack.IO.Async;
|
||||||
@ -343,8 +342,8 @@ public class FileExtractor
|
|||||||
|
|
||||||
if (!int.TryParse(line[..3], out var percentInt)) return;
|
if (!int.TryParse(line[..3], out var percentInt)) return;
|
||||||
|
|
||||||
var oldPosition = lastPercent == 0 ? 0 : totalSize / lastPercent;
|
var oldPosition = lastPercent == 0 ? 0 : totalSize / 100 * lastPercent;
|
||||||
var newPosition = percentInt == 0 ? 0 : totalSize / percentInt;
|
var newPosition = percentInt == 0 ? 0 : totalSize / 100 * percentInt;
|
||||||
var throughput = newPosition - oldPosition;
|
var throughput = newPosition - oldPosition;
|
||||||
job.ReportNoWait((int) throughput);
|
job.ReportNoWait((int) throughput);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ using Wabbajack.VFS;
|
|||||||
|
|
||||||
namespace Wabbajack.Installer;
|
namespace Wabbajack.Installer;
|
||||||
|
|
||||||
public record StatusUpdate(string StatusCategory, string StatusText, Percent StepsProgress, Percent StepProgress)
|
public record StatusUpdate(string StatusCategory, string StatusText, Percent StepsProgress, Percent StepProgress, int CurrentStep)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ public abstract class AInstaller<T>
|
|||||||
_logger.LogInformation("Next Step: {Step}", statusText);
|
_logger.LogInformation("Next Step: {Step}", statusText);
|
||||||
|
|
||||||
OnStatusUpdate?.Invoke(new StatusUpdate(statusCategory, statusText,
|
OnStatusUpdate?.Invoke(new StatusUpdate(statusCategory, statusText,
|
||||||
Percent.FactoryPutInRange(_currentStep, MaxSteps), Percent.Zero));
|
Percent.FactoryPutInRange(_currentStep, MaxSteps), Percent.Zero, _currentStep));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProgress(long stepProgress)
|
public void UpdateProgress(long stepProgress)
|
||||||
@ -122,7 +122,7 @@ public abstract class AInstaller<T>
|
|||||||
|
|
||||||
OnStatusUpdate?.Invoke(new StatusUpdate(_statusCategory, $"[{_currentStep}/{MaxSteps}] {_statusText} ({_statusFormatter(_currentStepProgress)}/{_statusFormatter(MaxStepProgress)})",
|
OnStatusUpdate?.Invoke(new StatusUpdate(_statusCategory, $"[{_currentStep}/{MaxSteps}] {_statusText} ({_statusFormatter(_currentStepProgress)}/{_statusFormatter(MaxStepProgress)})",
|
||||||
Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||||
Percent.FactoryPutInRange(_currentStepProgress, MaxStepProgress)));
|
Percent.FactoryPutInRange(_currentStepProgress, MaxStepProgress), _currentStep));
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task<bool> Begin(CancellationToken token);
|
public abstract Task<bool> Begin(CancellationToken token);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Wabbajack.Paths.IO;
|
namespace Wabbajack.Paths.IO;
|
||||||
@ -13,8 +14,15 @@ public static class KnownFolders
|
|||||||
{
|
{
|
||||||
var result = Process.GetCurrentProcess().MainModule?.FileName?.ToAbsolutePath() ?? default;
|
var result = Process.GetCurrentProcess().MainModule?.FileName?.ToAbsolutePath() ?? default;
|
||||||
|
|
||||||
|
if (result != default &&
|
||||||
|
result.PathParts.Any(p => p.Equals("TestRunner", StringComparison.CurrentCultureIgnoreCase)))
|
||||||
|
{
|
||||||
|
return Assembly.GetExecutingAssembly().Location.ToAbsolutePath().Parent;
|
||||||
|
}
|
||||||
|
|
||||||
if ((result != default && result.Depth > 1 && result.FileName == "dotnet".ToRelativePath()) || Assembly.GetEntryAssembly() != null)
|
|
||||||
|
if ((result != default && result.Depth > 1 && result.FileName == "dotnet".ToRelativePath())
|
||||||
|
|| Assembly.GetEntryAssembly() != null)
|
||||||
{
|
{
|
||||||
result = Assembly.GetEntryAssembly()!.Location.ToAbsolutePath();
|
result = Assembly.GetEntryAssembly()!.Location.ToAbsolutePath();
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<Absolu
|
|||||||
|
|
||||||
internal readonly string[] Parts;
|
internal readonly string[] Parts;
|
||||||
|
|
||||||
|
public string[] PathParts => Parts == default ? Array.Empty<string>() : Parts;
|
||||||
|
|
||||||
public Extension Extension => Extension.FromPath(Parts[^1]);
|
public Extension Extension => Extension.FromPath(Parts[^1]);
|
||||||
public RelativePath FileName => new(Parts[^1..]);
|
public RelativePath FileName => new(Parts[^1..]);
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<Absolu
|
|||||||
return ArrayExtensions.AreEqualIgnoreCase(parent.Parts, 0, Parts, 0, parent.Parts.Length);
|
return ArrayExtensions.AreEqualIgnoreCase(parent.Parts, 0, Parts, 0, parent.Parts.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbsolutePath Combine(params object[] paths)
|
public readonly AbsolutePath Combine(params object[] paths)
|
||||||
{
|
{
|
||||||
var converted = paths.Select(p =>
|
var converted = paths.Select(p =>
|
||||||
{
|
{
|
||||||
|
@ -69,8 +69,8 @@ public static class ServiceExtensions
|
|||||||
});
|
});
|
||||||
|
|
||||||
service.AddSingleton<IBinaryPatchCache>(s => options.UseLocalCache
|
service.AddSingleton<IBinaryPatchCache>(s => options.UseLocalCache
|
||||||
? new BinaryPatchCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path)
|
? new BinaryPatchCache(s.GetRequiredService<ILogger<BinaryPatchCache>>(), s.GetService<TemporaryFileManager>()!.CreateFolder().Path)
|
||||||
: new BinaryPatchCache(KnownFolders.WabbajackAppLocal.Combine("patchCache.sqlite")));
|
: new BinaryPatchCache(s.GetRequiredService<ILogger<BinaryPatchCache>>(),KnownFolders.WabbajackAppLocal.Combine("PatchCache")));
|
||||||
|
|
||||||
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount});
|
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount});
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ mkdir c:\tmp\publish-wj
|
|||||||
|
|
||||||
dotnet clean
|
dotnet clean
|
||||||
dotnet restore
|
dotnet restore
|
||||||
dotnet publish Wabbajack.App.Wpf\Wabbajack.App.Wpf.csproj --runtime win10-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app /p:PublishedTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true --self-contained
|
dotnet publish Wabbajack.App.Wpf\Wabbajack.App.Wpf.csproj --runtime win10-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app /p:PublishTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true --self-contained
|
||||||
dotnet publish Wabbajack.Launcher\Wabbajack.Launcher.csproj --runtime win10-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\launcher /p:PublishedTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true --self-contained
|
dotnet publish Wabbajack.Launcher\Wabbajack.Launcher.csproj --runtime win10-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\launcher /p:PublishTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true --self-contained
|
||||||
dotnet publish c:\oss\Wabbajack\Wabbajack.CLI\Wabbajack.CLI.csproj --runtime win10-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app --self-contained
|
dotnet publish c:\oss\Wabbajack\Wabbajack.CLI\Wabbajack.CLI.csproj --runtime win10-x64 --configuration Release /p:Platform=x64 -o c:\tmp\publish-wj\app --self-contained
|
||||||
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /t http://timestamp.sectigo.com c:\tmp\publish-wj\app\Wabbajack.exe
|
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /t http://timestamp.sectigo.com c:\tmp\publish-wj\app\Wabbajack.exe
|
||||||
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /t http://timestamp.sectigo.com c:\tmp\publish-wj\launcher\Wabbajack.exe
|
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /t http://timestamp.sectigo.com c:\tmp\publish-wj\launcher\Wabbajack.exe
|
||||||
|
Loading…
Reference in New Issue
Block a user