From 6d20995dbf00b4485c66672eff3a67e83bc6c1ac Mon Sep 17 00:00:00 2001 From: cake Date: Mon, 29 Dec 2025 02:50:49 +0100 Subject: [PATCH] Added decompression gate to decompress files --- .../WebAPI/Files/FileDownloadManager.cs | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/LightlessSync/WebAPI/Files/FileDownloadManager.cs b/LightlessSync/WebAPI/Files/FileDownloadManager.cs index 47774f7..2731619 100644 --- a/LightlessSync/WebAPI/Files/FileDownloadManager.cs +++ b/LightlessSync/WebAPI/Files/FileDownloadManager.cs @@ -28,6 +28,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase private readonly TextureMetadataHelper _textureMetadataHelper; private readonly ConcurrentDictionary _activeDownloadStreams; + private readonly SemaphoreSlim _decompressGate = + new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2)); private volatile bool _disableDirectDownloads; private int _consecutiveDirectDownloadFailures; @@ -522,32 +524,57 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase try { + // sanity check length if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue) throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}"); + // safe cast after check + var len = checked((int)fileLengthBytes); + if (!replacementLookup.TryGetValue(fileHash, out var repl)) { Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash); - // still need to skip bytes: - var skip = checked((int)fileLengthBytes); - fileBlockStream.Position += skip; + fileBlockStream.Seek(len, SeekOrigin.Current); continue; } + // decompress var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension); + Logger.LogTrace("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath); - Logger.LogDebug("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath); - - var len = checked((int)fileLengthBytes); + // read compressed data var compressed = new byte[len]; - await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false); - MungeBuffer(compressed); - var decompressed = LZ4Wrapper.Unwrap(compressed); + if (len == 0) + { + await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty(), ct).ConfigureAwait(false); + PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + continue; + } - await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false); - PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + MungeBuffer(compressed); + + // limit concurrent decompressions + await _decompressGate.WaitAsync(ct).ConfigureAwait(false); + try + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + + // decompress + var decompressed = LZ4Wrapper.Unwrap(compressed); + + Logger.LogTrace("{dlName}: Unwrap {fileHash} took {ms}ms (compressed {c} bytes, decompressed {d} bytes)", + downloadLabel, fileHash, sw.ElapsedMilliseconds, compressed.Length, decompressed?.Length ?? -1); + + // write to file + await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false); + PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + } + finally + { + _decompressGate.Release(); + } } catch (EndOfStreamException) { @@ -605,20 +632,16 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase .. await FilesGetSizes(hashes, ct).ConfigureAwait(false), ]; - Logger.LogDebug("Files with size 0 or less: {files}", - string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); - foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) { if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) _orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); } - CurrentDownloads = downloadFileInfoFromService + CurrentDownloads = [.. downloadFileInfoFromService .Distinct() .Select(d => new DownloadFileTransfer(d)) - .Where(d => d.CanBeTransferred) - .ToList(); + .Where(d => d.CanBeTransferred)]; return CurrentDownloads; }