Speed up decompression

This commit is contained in:
Loporrit
2025-03-14 23:50:01 +00:00
parent a9e8d6f8a8
commit c7d8ba0ef0
2 changed files with 82 additions and 41 deletions

View File

@@ -90,6 +90,26 @@ public sealed class FileCompactor
CompactFile(filePath); CompactFile(filePath);
} }
public void RenameAndCompact(string filePath, string originalFilePath)
{
try
{
File.Move(originalFilePath, filePath);
}
catch (IOException)
{
// File already exists
return;
}
if (_dalamudUtilService.IsWine || !_mareConfigService.Current.UseCompactor)
{
return;
}
CompactFile(filePath);
}
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern int DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out IntPtr lpBytesReturned, out IntPtr lpOverlapped); private static extern int DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out IntPtr lpBytesReturned, out IntPtr lpOverlapped);

View File

@@ -305,6 +305,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
} }
FileStream? fileBlockStream = null; FileStream? fileBlockStream = null;
var threadCount = Math.Clamp((int)(Environment.ProcessorCount / 2.0f), 2, 8);
var tasks = new List<Task>();
try try
{ {
if (_downloadStatus.TryGetValue(fileGroup.Key, out var status)) if (_downloadStatus.TryGetValue(fileGroup.Key, out var status))
@@ -316,28 +318,49 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
while (fileBlockStream.Position < fileBlockStream.Length) while (fileBlockStream.Position < fileBlockStream.Length)
{ {
(string fileHash, long fileLengthBytes) = ReadBlockFileHeader(fileBlockStream); (string fileHash, long fileLengthBytes) = ReadBlockFileHeader(fileBlockStream);
var chunkPosition = fileBlockStream.Position;
fileBlockStream.Position += fileLengthBytes;
while (tasks.Count > threadCount && tasks.Where(t => !t.IsCompleted).Count() > 4)
await Task.Delay(10, CancellationToken.None);
try
{
var fileExtension = fileReplacement.First(f => string.Equals(f.Hash, fileHash, StringComparison.OrdinalIgnoreCase)).GamePaths[0].Split(".")[^1]; var fileExtension = fileReplacement.First(f => string.Equals(f.Hash, fileHash, StringComparison.OrdinalIgnoreCase)).GamePaths[0].Split(".")[^1];
var tmpPath = _fileDbManager.GetCacheFilePath(Guid.NewGuid().ToString(), "tmp");
var filePath = _fileDbManager.GetCacheFilePath(fileHash, fileExtension); var filePath = _fileDbManager.GetCacheFilePath(fileHash, fileExtension);
Logger.LogDebug("{dlName}: Decompressing {file}:{le} => {dest}", fi.Name, fileHash, fileLengthBytes, filePath); Logger.LogDebug("{dlName}: Decompressing {file}:{le} => {dest}", fi.Name, fileHash, fileLengthBytes, filePath);
using var decompressedFile = new MemoryStream(64 * 1024); tasks.Add(Task.Run(() => {
using var innerFileStream = new LimitedStream(fileBlockStream, fileLengthBytes); try
innerFileStream.DisposeUnderlying = false; {
using var decStream = LZ4Stream.Decode(innerFileStream, 0, true); using var tmpFileStream = new FileStream(tmpPath, new FileStreamOptions()
long startPos = fileBlockStream.Position; {
await decStream.CopyToAsync(decompressedFile, CancellationToken.None).ConfigureAwait(false); Mode = FileMode.CreateNew,
long readBytes = fileBlockStream.Position - startPos; Access = FileAccess.Write,
Share = FileShare.None
});
using var fileChunkStream = new FileStream(blockFile, new FileStreamOptions()
{
BufferSize = 80000,
Mode = FileMode.Open,
Access = FileAccess.Read
});
fileChunkStream.Position = chunkPosition;
using var innerFileStream = new LimitedStream(fileChunkStream, fileLengthBytes);
using var decoder = LZ4Frame.Decode(innerFileStream);
long startPos = fileChunkStream.Position;
decoder.AsStream().CopyTo(tmpFileStream);
long readBytes = fileChunkStream.Position - startPos;
if (readBytes != fileLengthBytes) if (readBytes != fileLengthBytes)
{ {
throw new EndOfStreamException(); throw new EndOfStreamException();
} }
await _fileCompactor.WriteAllBytesAsync(filePath, decompressedFile.ToArray(), CancellationToken.None).ConfigureAwait(false); tmpFileStream.Close();
_fileCompactor.RenameAndCompact(filePath, tmpPath);
PersistFileToStorage(fileHash, filePath, fileLengthBytes); PersistFileToStorage(fileHash, filePath, fileLengthBytes);
} }
catch (EndOfStreamException) catch (EndOfStreamException)
@@ -351,7 +374,15 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
foreach (var fr in fileReplacement) foreach (var fr in fileReplacement)
Logger.LogWarning(" - {h}: {x}", fr.Hash, fr.GamePaths[0]); Logger.LogWarning(" - {h}: {x}", fr.Hash, fr.GamePaths[0]);
} }
finally
{
if (File.Exists(tmpPath))
File.Delete(tmpPath);
} }
}));
}
Task.WaitAll([..tasks], CancellationToken.None);
} }
catch (EndOfStreamException) catch (EndOfStreamException)
{ {
@@ -363,6 +394,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
} }
finally finally
{ {
Task.WaitAll([..tasks], CancellationToken.None);
_orchestrator.ReleaseDownloadSlot(); _orchestrator.ReleaseDownloadSlot();
if (fileBlockStream != null) if (fileBlockStream != null)
await fileBlockStream.DisposeAsync().ConfigureAwait(false); await fileBlockStream.DisposeAsync().ConfigureAwait(false);
@@ -384,18 +416,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private void PersistFileToStorage(string fileHash, string filePath, long? compressedSize = null) private void PersistFileToStorage(string fileHash, string filePath, long? compressedSize = null)
{ {
var fi = new FileInfo(filePath);
Func<DateTime> RandomDayInThePast()
{
DateTime start = new(1995, 1, 1, 1, 1, 1, DateTimeKind.Local);
Random gen = new();
int range = (DateTime.Today - start).Days;
return () => start.AddDays(gen.Next(range));
}
fi.CreationTime = RandomDayInThePast().Invoke();
fi.LastAccessTime = DateTime.Today;
fi.LastWriteTime = RandomDayInThePast().Invoke();
try try
{ {
var entry = _fileDbManager.CreateCacheEntry(filePath); var entry = _fileDbManager.CreateCacheEntry(filePath);
@@ -404,6 +424,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
Logger.LogError("Hash mismatch after extracting, got {hash}, expected {expectedHash}, deleting file", entry.Hash, fileHash); Logger.LogError("Hash mismatch after extracting, got {hash}, expected {expectedHash}, deleting file", entry.Hash, fileHash);
File.Delete(filePath); File.Delete(filePath);
_fileDbManager.RemoveHashedFile(entry.Hash, entry.PrefixedFilePath); _fileDbManager.RemoveHashedFile(entry.Hash, entry.PrefixedFilePath);
entry = null;
} }
if (entry != null) if (entry != null)
entry.CompressedSize = compressedSize; entry.CompressedSize = compressedSize;