Skip to content

Commit

Permalink
format
Browse files Browse the repository at this point in the history
  • Loading branch information
qdraw committed Sep 10, 2024
1 parent 0a34113 commit 98b2e62
Showing 1 changed file with 102 additions and 91 deletions.
193 changes: 102 additions & 91 deletions starsky/starsky.foundation.storage/ArchiveFormats/TarBal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,117 +8,128 @@
using starsky.foundation.platform.Helpers;
using starsky.foundation.storage.Interfaces;

namespace starsky.foundation.storage.ArchiveFormats
namespace starsky.foundation.storage.ArchiveFormats;

[SuppressMessage("Usage",
"CA1835:Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync")]
public sealed class TarBal
{
[SuppressMessage("Usage", "CA1835:Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync")]
public sealed class TarBal
private readonly char _pathSeparator;
private readonly IStorage _storage;

public TarBal(IStorage storage, char pathSeparator = '/')
{
_storage = storage;
_pathSeparator = pathSeparator;
}

/// <summary>
/// Extracts a <i>.tar.gz</i> archive stream to the specified directory.
/// </summary>
/// <param name="stream">The <i>.tar.gz</i> to decompress and extract.</param>
/// <param name="outputDir">Output directory to write the files.</param>
/// <param name="cancellationToken">cancellationToken</param>
public async Task ExtractTarGz(Stream stream, string outputDir,
CancellationToken cancellationToken)
{
private readonly IStorage _storage;
private readonly char _pathSeparator;
// A GZipStream is not seekable, so copy it first to a MemoryStream
using var gzip = new GZipStream(stream, CompressionMode.Decompress);
const int chunk = 4096;
using var memStr = new MemoryStream();
int read;
var buffer = new byte[chunk];

public TarBal(IStorage storage, char pathSeparator = '/')
while ( ( read = await gzip.ReadAsync(buffer, 0, buffer.Length, cancellationToken) ) > 0 )
{
_storage = storage;
_pathSeparator = pathSeparator;
await memStr.WriteAsync(buffer, 0, read, cancellationToken);
}

/// <summary>
/// Extracts a <i>.tar.gz</i> archive stream to the specified directory.
/// </summary>
/// <param name="stream">The <i>.tar.gz</i> to decompress and extract.</param>
/// <param name="outputDir">Output directory to write the files.</param>
/// <param name="cancellationToken">cancellationToken</param>
public async Task ExtractTarGz(Stream stream, string outputDir, CancellationToken cancellationToken)
{
// A GZipStream is not seekable, so copy it first to a MemoryStream
using var gzip = new GZipStream(stream, CompressionMode.Decompress);
const int chunk = 4096;
using var memStr = new MemoryStream();
int read;
var buffer = new byte[chunk];
gzip.Close();
memStr.Seek(0, SeekOrigin.Begin);
await ExtractTar(memStr, outputDir, cancellationToken);
}

while ( ( read = await gzip.ReadAsync(buffer, 0, buffer.Length, cancellationToken) ) > 0 )
/// <summary>
/// Extracts a <c>tar</c> archive to the specified directory.
/// </summary>
/// <param name="stream">The <i>.tar</i> to extract.</param>
/// <param name="outputDir">Output directory to write the files.</param>
/// <param name="cancellationToken">cancel token</param>
public async Task ExtractTar(Stream stream, string outputDir,
CancellationToken cancellationToken)
{
var buffer = new byte[100];
var longFileName = string.Empty;
while ( true )
{
await stream.ReadAsync(buffer, 0, 100, cancellationToken);
var name = string.IsNullOrEmpty(longFileName)
? Encoding.ASCII.GetString(buffer).Trim('\0')
: longFileName; //Use longFileName if we have one read
if ( string.IsNullOrWhiteSpace(name) )
{
await memStr.WriteAsync(buffer, 0, read, cancellationToken);
break;
}

gzip.Close();
memStr.Seek(0, SeekOrigin.Begin);
await ExtractTar(memStr, outputDir, cancellationToken);
}
stream.Seek(24, SeekOrigin.Current);
await stream.ReadAsync(buffer, 0, 12, cancellationToken);
var size = Convert.ToInt64(Encoding.UTF8.GetString(buffer, 0, 12).Trim('\0').Trim(), 8);
stream.Seek(20, SeekOrigin.Current); //Move head to typeTag byte
var typeTag = stream.ReadByte();
stream.Seek(355L, SeekOrigin.Current); //Move head to beginning of data (byte 512)

/// <summary>
/// Extracts a <c>tar</c> archive to the specified directory.
/// </summary>
/// <param name="stream">The <i>.tar</i> to extract.</param>
/// <param name="outputDir">Output directory to write the files.</param>
/// <param name="cancellationToken">cancel token</param>
public async Task ExtractTar(Stream stream, string outputDir, CancellationToken cancellationToken)
{
var buffer = new byte[100];
var longFileName = string.Empty;
while ( true )
if ( typeTag == 'L' )
{
await stream.ReadAsync(buffer, 0, 100, cancellationToken);
var name = string.IsNullOrEmpty(longFileName) ? Encoding.ASCII.GetString(buffer).Trim('\0') : longFileName; //Use longFileName if we have one read
if ( string.IsNullOrWhiteSpace(name) )
break;
stream.Seek(24, SeekOrigin.Current);
await stream.ReadAsync(buffer, 0, 12, cancellationToken);
var size = Convert.ToInt64(Encoding.UTF8.GetString(buffer, 0, 12).Trim('\0').Trim(), 8);
stream.Seek(20, SeekOrigin.Current); //Move head to typeTag byte
var typeTag = stream.ReadByte();
stream.Seek(355L, SeekOrigin.Current); //Move head to beginning of data (byte 512)

if ( typeTag == 'L' )
{
//We have a long file name
longFileName = await CreateLongFileName(size, stream, cancellationToken);
}
else
{
//We have a normal file or directory
longFileName = string.Empty; //Reset longFileName if current entry is not indicating one
await CreateFileOrDirectory(outputDir, name, size, stream, cancellationToken);
}

//Move head to next 512 byte block
var pos = stream.Position;
var offset = 512 - ( pos % 512 );
if ( offset == 512 )
offset = 0;
//We have a long file name
longFileName = await CreateLongFileName(size, stream, cancellationToken);
}
else
{
//We have a normal file or directory
longFileName =
string.Empty; //Reset longFileName if current entry is not indicating one
await CreateFileOrDirectory(outputDir, name, size, stream, cancellationToken);
}

stream.Seek(offset, SeekOrigin.Current);
//Move head to next 512 byte block
var pos = stream.Position;
var offset = 512 - pos % 512;
if ( offset == 512 )
{
offset = 0;
}
}

private static async Task<string> CreateLongFileName(long size, Stream stream, CancellationToken cancellationToken)
{
//If Type Tag is 'L' we have a filename that is longer than the 100 bytes reserved for it in the header.
//We read it here and save it temporarily as it will be the file name of the next block where the actual data is
var buf = new byte[size];
await stream.ReadAsync(buf, 0, buf.Length, cancellationToken);
return Encoding.ASCII.GetString(buf).Trim('\0');
stream.Seek(offset, SeekOrigin.Current);
}
}

private async Task CreateFileOrDirectory(string outputDir, string name, long size, Stream stream, CancellationToken cancellationToken)
{
var output = $"{outputDir}{_pathSeparator}{name}";
private static async Task<string> CreateLongFileName(long size, Stream stream,
CancellationToken cancellationToken)
{
//If Type Tag is 'L' we have a filename that is longer than the 100 bytes reserved for it in the header.
//We read it here and save it temporarily as it will be the file name of the next block where the actual data is
var buf = new byte[size];
await stream.ReadAsync(buf, 0, buf.Length, cancellationToken);
return Encoding.ASCII.GetString(buf).Trim('\0');
}

if ( !_storage.ExistFolder(FilenamesHelper.GetParentPath(output)) )
{
_storage.CreateDirectory(FilenamesHelper.GetParentPath(output));
}
private async Task CreateFileOrDirectory(string outputDir, string name, long size,
Stream stream, CancellationToken cancellationToken)
{
var output = $"{outputDir}{_pathSeparator}{name}";

if ( !name.EndsWith('/') ) // Directories are zero size and don't need anything written
{
var str = new MemoryStream();
var buf = new byte[size];
await stream.ReadAsync(buf, 0, buf.Length, cancellationToken);
str.Write(buf, 0, buf.Length);
_storage.WriteStreamOpenOrCreate(str, output);
}
if ( !_storage.ExistFolder(FilenamesHelper.GetParentPath(output)) )
{
_storage.CreateDirectory(FilenamesHelper.GetParentPath(output));
}

if ( !name.EndsWith('/') ) // Directories are zero size and don't need anything written
{
var str = new MemoryStream();
var buf = new byte[size];
await stream.ReadAsync(buf, 0, buf.Length, cancellationToken);
str.Write(buf, 0, buf.Length);
_storage.WriteStreamOpenOrCreate(str, output);
}
}
}

0 comments on commit 98b2e62

Please sign in to comment.