diff --git a/.gitignore b/.gitignore index 80f4154a..6a1f0613 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ profiler-snapshots/ .DS_Store *.snupkg benchmark-results/ -/.opencode +.opencode/ diff --git a/src/SharpCompress/Common/IEntryExtensions.Async.cs b/src/SharpCompress/Common/IEntryExtensions.Async.cs index 7b42f1c8..4ef697e0 100644 --- a/src/SharpCompress/Common/IEntryExtensions.Async.cs +++ b/src/SharpCompress/Common/IEntryExtensions.Async.cs @@ -9,7 +9,7 @@ internal static partial class IEntryExtensions { extension(IEntry entry) { - public async ValueTask WriteEntryToDirectoryAsync( + internal async ValueTask WriteEntryToDirectoryAsync( string destinationDirectory, ExtractionOptions? options, Func writeAsync, diff --git a/src/SharpCompress/Common/IEntryExtensions.cs b/src/SharpCompress/Common/IEntryExtensions.cs index 77c5207c..d3158f03 100644 --- a/src/SharpCompress/Common/IEntryExtensions.cs +++ b/src/SharpCompress/Common/IEntryExtensions.cs @@ -10,7 +10,7 @@ internal static partial class IEntryExtensions /// /// Extract to specific directory, retaining filename /// - public void WriteEntryToDirectory( + internal void WriteEntryToDirectory( string destinationDirectory, ExtractionOptions? options, Action write diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs index 42f7d439..5ade0c6b 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs @@ -239,7 +239,7 @@ internal partial class Unpack { Header.HeaderSize = 0; - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + if (Inp.InAddr > ReadTop - 7) { if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) { @@ -292,7 +292,7 @@ internal partial class Unpack CancellationToken cancellationToken = default ) { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + if (Inp.InAddr > ReadTop - 16) { if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs index e3352cfc..496b6204 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs @@ -372,7 +372,7 @@ internal partial class Unpack private bool ReadFilter(UnpackFilter Filter) { - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) + if (Inp.InAddr > ReadTop - 16) { if (!UnpReadBuf()) { @@ -762,7 +762,7 @@ internal partial class Unpack { Header.HeaderSize = 0; - if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) + if (Inp.InAddr > ReadTop - 7) { if (!UnpReadBuf()) { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs index cdd584a0..6bc3e1d5 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/unpack_hpp.cs @@ -156,40 +156,24 @@ if (Decoded!=NULL) //struct UnpackFilter internal class UnpackFilter { - public byte Type; - public uint BlockStart; - public uint BlockLength; - public byte Channels; + internal byte Type; + internal uint BlockStart; + internal uint BlockLength; + internal byte Channels; // uint Width; // byte PosR; - public bool NextWindow; -}; - -//struct UnpackFilter30 -internal class UnpackFilter30 -{ - public uint BlockStart; - public uint BlockLength; - public bool NextWindow; - - // Position of parent filter in Filters array used as prototype for filter - // in PrgStack array. Not defined for filters in Filters array. - public uint ParentFilter; - - /*#if !RarV2017_RAR5ONLY - public VM_PreparedProgram Prg; - #endif*/ + internal bool NextWindow; }; internal class AudioVariables // For RAR 2.0 archives only. { - public int K1, + internal int K1, K2, K3, K4, K5; - public int D1, + internal int D1, D2, D3, D4; @@ -369,8 +353,6 @@ internal partial class Unpack #endif*/ private int PPMEscChar; - private readonly byte[] UnpOldTable = new byte[HUFF_TABLE_SIZE30]; - // If we already read decoding tables for Unpack v2,v3,v5. // We should not use a single variable for all algorithm versions, // because we can have a corrupt archive with one algorithm file @@ -379,26 +361,6 @@ internal partial class Unpack private bool TablesRead2, TablesRead5; - // Virtual machine to execute filters code. - /*#if !RarV2017_RAR5ONLY - RarVM VM; - #endif*/ - - // Buffer to read VM filters code. We moved it here from AddVMCode - // function to reduce time spent in BitInput constructor. - private readonly BitInput VMCodeInp = new(true); - - // Filters code, one entry per filter. - private readonly List Filters30 = new(); - - // Filters stack, several entrances of same filter are possible. - private readonly List PrgStack = new(); - - // Lengths of preceding data blocks, one length of one last block - // for every filter. Used to reduce the size required to write - // the data block length if lengths are repeating. - private readonly List OldFilterLengths = new(); - /*#if RarV2017_RAR_SMP // More than 8 threads are unlikely to provide a noticeable gain // for unpacking, but would use the additional memory. diff --git a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs index 8cd82056..a35e05fb 100644 --- a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs +++ b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs @@ -22,8 +22,7 @@ internal class BitInput : IDisposable get => inBit; set => inBit = value; } - public bool ExternalBuffer; - private byte[] _privateBuffer = ArrayPool.Shared.Rent(MAX_SIZE); + private readonly byte[] _privateBuffer = ArrayPool.Shared.Rent(MAX_SIZE); private bool _disposed; /// diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index fdba2fb1..f522434d 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -268,9 +268,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.5, )", - "resolved": "10.0.5", - "contentHash": "A+5ZuQ0f449tM+MQrhf6R9ZX7lYpjk/ODEwLYKrnF6111rtARx8fVsm4YznUnQiKnnXfaXNBqgxmil6RW3L3SA==" + "requested": "[10.0.6, )", + "resolved": "10.0.6", + "contentHash": "QKuvS0LWX4fjFqeDkyM7Kqt8P3wYTiPD4nwU+9y59n0sCiG714fxDgbbN82vDnzq89AF/PiHl92TP2C4aFDUQA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -358,9 +358,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.25, )", - "resolved": "8.0.25", - "contentHash": "sqX4nmBft05ivqKvUT4nxaN8rT3apCLt9SWFkfRrQPwra1zPwFknQAw1lleuMCKOCLvVmOWwrC2iPSm9RiXZUg==" + "requested": "[8.0.26, )", + "resolved": "8.0.26", + "contentHash": "o7/yVssM2r9Wyln2s9edBd5ANZXqdSdBI+g7JqXkyJmXrhs2WsJp25K5yPnYrTgdKBCjKB8bg+O2oew4sgzFaA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.Test/TempDirectory.cs b/tests/SharpCompress.Test/TempDirectory.cs new file mode 100644 index 00000000..e155210f --- /dev/null +++ b/tests/SharpCompress.Test/TempDirectory.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace SharpCompress.Test; + +internal sealed class TempDirectory : IAsyncDisposable +{ + private const int MaxDeleteAttempts = 5; + private static readonly TimeSpan DeleteRetryDelay = TimeSpan.FromMilliseconds(100); + + public TempDirectory(string prefix) + { + Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"{prefix}.{Guid.NewGuid():N}"); + Directory.CreateDirectory(Path); + } + + public string Path { get; } + + public string GetDirectory(string name) + { + var path = System.IO.Path.Combine(Path, name); + Directory.CreateDirectory(path); + return path; + } + + public string CreateDirectory(string name) + { + var path = System.IO.Path.Combine(Path, name, System.IO.Path.GetRandomFileName()); + Directory.CreateDirectory(path); + return path; + } + + public void ResetDirectory(string name) + { + DeleteDirectory(System.IO.Path.Combine(Path, name)); + Directory.CreateDirectory(System.IO.Path.Combine(Path, name)); + } + + public async ValueTask DisposeAsync() + { + for (var attempt = 1; attempt <= MaxDeleteAttempts; attempt++) + { + try + { + DeleteDirectory(Path); + if (Directory.Exists(Path)) + { + throw new IOException( + $"Temp test directory '{Path}' still exists after deletion." + ); + } + + return; + } + catch (Exception ex) + when (IsRetryableDeleteException(ex) && attempt < MaxDeleteAttempts) + { + await Task.Delay(DeleteRetryDelay).ConfigureAwait(false); + } + catch (Exception ex) when (IsRetryableDeleteException(ex)) + { + throw new InvalidOperationException( + $"Failed to clean up temp test directory '{Path}'.", + ex + ); + } + } + + throw new InvalidOperationException($"Temp test directory '{Path}' was not cleaned up."); + } + + private static void DeleteDirectory(string path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path, true); + } + } + + private static bool IsRetryableDeleteException(Exception ex) => + ex is IOException or UnauthorizedAccessException; +} diff --git a/tests/SharpCompress.Test/TestBase.cs b/tests/SharpCompress.Test/TestBase.cs index 0cdd6a07..1c244ac8 100644 --- a/tests/SharpCompress.Test/TestBase.cs +++ b/tests/SharpCompress.Test/TestBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; using SharpCompress.Readers; using Xunit; @@ -31,70 +30,40 @@ public class TestBase : IAsyncDisposable MISC_TEST_FILES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "MiscTest"); } - private readonly Guid _testGuid = Guid.NewGuid(); - private readonly string _testTempDirectory; + private readonly TempDirectory _tempDirectory; protected readonly string SCRATCH_FILES_PATH; protected readonly string SCRATCH2_FILES_PATH; protected TestBase() { - _testTempDirectory = Path.Combine(Path.GetTempPath(), $"SharpCompress.Test.{_testGuid:N}"); - SCRATCH_FILES_PATH = Path.Combine(_testTempDirectory, "Scratch"); - SCRATCH2_FILES_PATH = Path.Combine(_testTempDirectory, "Scratch2"); - - Directory.CreateDirectory(SCRATCH_FILES_PATH); - Directory.CreateDirectory(SCRATCH2_FILES_PATH); + _tempDirectory = new TempDirectory("SharpCompress.Test"); + SCRATCH_FILES_PATH = _tempDirectory.GetDirectory("Scratch"); + SCRATCH2_FILES_PATH = _tempDirectory.GetDirectory("Scratch2"); } - //always use async dispose since we have I/O and sync Dispose doesn't wait when using xunit - public async ValueTask DisposeAsync() - { - await Task.CompletedTask; - DeleteScratchDirectory(_testTempDirectory); - } + // Always use async dispose since we have I/O and sync Dispose doesn't wait when using xunit. + public ValueTask DisposeAsync() => _tempDirectory.DisposeAsync(); public void CleanScratch() { - ResetScratchDirectory(SCRATCH_FILES_PATH); - ResetScratchDirectory(SCRATCH2_FILES_PATH); + _tempDirectory.ResetDirectory("Scratch"); + _tempDirectory.ResetDirectory("Scratch2"); } - private static void ResetScratchDirectory(string path) - { - if (Directory.Exists(path)) - { - Directory.Delete(path, true); - } + protected string CreateScratchDirectory(string name) => + _tempDirectory.CreateDirectory(Path.Combine("Scratch", name)); - Directory.CreateDirectory(path); - } + protected string CreateScratch2Directory(string name) => + _tempDirectory.CreateDirectory(Path.Combine("Scratch2", name)); - private static void DeleteScratchDirectory(string path) - { - if (!Directory.Exists(path)) - { - return; - } + protected string GetScratchPath(params string[] parts) => + CombinePath(SCRATCH_FILES_PATH, parts); - try - { - Directory.Delete(path, true); - } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) - { - throw new InvalidOperationException( - $"Failed to clean up temp test directory '{path}'.", - ex - ); - } + protected string GetScratch2Path(params string[] parts) => + CombinePath(SCRATCH2_FILES_PATH, parts); - if (Directory.Exists(path)) - { - throw new InvalidOperationException( - $"Temp test directory '{path}' was not cleaned up." - ); - } - } + private static string CombinePath(string root, string[] parts) => + parts.Length == 0 ? root : Path.Combine(root, Path.Combine(parts)); public void VerifyFiles() { diff --git a/tests/SharpCompress.Test/packages.lock.json b/tests/SharpCompress.Test/packages.lock.json index ca6ffc35..9a219922 100644 --- a/tests/SharpCompress.Test/packages.lock.json +++ b/tests/SharpCompress.Test/packages.lock.json @@ -309,30 +309,6 @@ } } }, - ".NETFramework,Version=v4.8/win-x86": { - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.AccessControl": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", - "dependencies": { - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - } - }, "net10.0": { "AwesomeAssertions": { "type": "Direct", @@ -545,13 +521,6 @@ "resolved": "8.0.0", "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==" } - }, - "net10.0/win-x86": { - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" - } } } } \ No newline at end of file