diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a21db0..bcde64f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,8 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-- `ArReader` and `ArWriter` for Unix archives
-- `TarReader` and `TarWriter` for TAR archives
- `.AddRange()` for collections
- `.AddIfNotNull()` for collections
- `DomainComparer` ordering alphabetically from TLD to sub-domain
diff --git a/src/AMWD.Common/Packing/Ar/ArFileInfo.cs b/src/AMWD.Common/Packing/Ar/ArFileInfo.cs
deleted file mode 100644
index f0cbc38..0000000
--- a/src/AMWD.Common/Packing/Ar/ArFileInfo.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-
-namespace AMWD.Common.Packing.Ar
-{
- ///
- /// Represents the file information saved in the archive.
- ///
- [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- public class ArFileInfo
- {
- ///
- /// Gets or sets the file name.
- ///
- public string FileName { get; set; }
-
- ///
- /// Gets or sets the file size in bytes.
- ///
- public long FileSize { get; set; }
-
- ///
- /// Gets or sets the timestamp of the last modification.
- ///
- public DateTime ModifyTime { get; set; }
-
- ///
- /// Gets or sets the user id.
- ///
- public int UserId { get; set; }
-
- ///
- /// Gets or sets the group id.
- ///
- public int GroupId { get; set; }
-
- ///
- /// Gets or sets the access mode in decimal (not octal!).
- ///
- ///
- /// To see the octal representation use Convert.ToString(Mode, 8).
- ///
- public int Mode { get; set; }
- }
-
- internal class ArFileInfoExtended : ArFileInfo
- {
- public long HeaderPosition { get; set; }
-
- public long DataPosition { get; set; }
- }
-}
diff --git a/src/AMWD.Common/Packing/Ar/ArReader.cs b/src/AMWD.Common/Packing/Ar/ArReader.cs
deleted file mode 100644
index 821b3e6..0000000
--- a/src/AMWD.Common/Packing/Ar/ArReader.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-
-namespace AMWD.Common.Packing.Ar
-{
- ///
- /// Reads UNIX ar (archive) files in the GNU format.
- ///
- public class ArReader
- {
- // Source: http://en.wikipedia.org/wiki/Ar_%28Unix%29
-
- private readonly Stream _inStream;
- private readonly List _files = [];
- private readonly long _streamStartPosition;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The stream to read the archive from.
- public ArReader(Stream inStream)
- {
- if (!inStream.CanRead || !inStream.CanSeek)
- throw new ArgumentException("Stream not readable or seekable", nameof(inStream));
-
- _streamStartPosition = inStream.Position;
- _inStream = inStream;
-
- Initialize();
- }
-
- ///
- /// Returns a list with all filenames of the archive.
- ///
- public IEnumerable GetFileList()
- => _files.Select(fi => fi.FileName).ToList();
-
- ///
- /// Returns the file info of a specific file in the archive.
- ///
- /// The name of the specific file.
- public ArFileInfo GetFileInfo(string fileName)
- {
- return _files
- .Where(fi => fi.FileName == fileName)
- .Select(fi => new ArFileInfo
- {
- FileName = fi.FileName,
- FileSize = fi.FileSize,
- GroupId = fi.GroupId,
- Mode = fi.Mode,
- ModifyTime = fi.ModifyTime,
- UserId = fi.UserId
- })
- .FirstOrDefault();
- }
-
- ///
- /// Reads a file from the archive into a stream.
- ///
- /// The file name in the archive.
- /// The output stream.
- public void ReadFile(string fileName, Stream outStream)
- {
- if (!outStream.CanWrite)
- throw new ArgumentException("Stream not writable", nameof(outStream));
-
- var info = _files.Where(fi => fi.FileName == fileName).FirstOrDefault();
- if (info == null)
- return;
-
- long bytesToRead = info.FileSize;
- byte[] buffer = new byte[1024 * 1024];
-
- _inStream.Seek(info.DataPosition, SeekOrigin.Begin);
- while (bytesToRead > 0)
- {
- int readCount = (int)Math.Min(bytesToRead, buffer.Length);
- _inStream.Read(buffer, 0, readCount);
- outStream.Write(buffer, 0, readCount);
-
- bytesToRead -= readCount;
- }
- _inStream.Seek(_streamStartPosition, SeekOrigin.Begin);
- }
-
- ///
- /// Reads a fie from the archive and saves it to disk.
- ///
- /// The file name in the archive.
- /// The destination path on disk.
- public void ReadFile(string fileName, string destinationPath)
- {
- var info = _files.Where(fi => fi.FileName == fileName).FirstOrDefault();
- if (info == null)
- return;
-
- using (var fs = File.OpenWrite(destinationPath))
- {
- ReadFile(fileName, fs);
- }
- File.SetLastWriteTimeUtc(destinationPath, info.ModifyTime);
- }
-
- private void Initialize()
- {
- // Reset stream
- _inStream.Seek(_streamStartPosition, SeekOrigin.Begin);
-
- // Read header
- string header = ReadAsciiString(8);
- if (header != "!\n")
- throw new FormatException("The file stream is no archive");
-
- // Create file list
- while (_inStream.Position < _inStream.Length)
- {
- var info = ReadFileHeader();
- _files.Add(info);
-
- // Move stream behind file content
- _inStream.Seek(info.FileSize, SeekOrigin.Current);
-
- // Align to even offsets (padded with LF bytes)
- if (_inStream.Position % 2 != 0)
- _inStream.Seek(1, SeekOrigin.Current);
- }
-
- // Reset stream
- _inStream.Seek(_streamStartPosition, SeekOrigin.Begin);
- }
-
- private string ReadAsciiString(int byteCount)
- {
- byte[] buffer = new byte[byteCount];
- _inStream.Read(buffer, 0, byteCount);
- return Encoding.ASCII.GetString(buffer);
- }
-
- private ArFileInfoExtended ReadFileHeader()
- {
- long startPosition = _inStream.Position;
-
- string fileName = ReadAsciiString(16).Trim();
-
- int.TryParse(ReadAsciiString(12).Trim(), out int unixTimestamp);
- int.TryParse(ReadAsciiString(6).Trim(), out int userId);
- int.TryParse(ReadAsciiString(6).Trim(), out int groupId);
- int mode = Convert.ToInt32(ReadAsciiString(8).Trim(), 8);
-
- long.TryParse(ReadAsciiString(10).Trim(), out long fileSize);
-
- // file magic
- byte[] magic = new byte[2];
- _inStream.Read(magic, 0, magic.Length);
-
- if (magic[0] != 0x60 || magic[1] != 0x0A) // `\n
- throw new FormatException("Invalid file magic");
-
- return new ArFileInfoExtended
- {
- HeaderPosition = startPosition,
- DataPosition = _inStream.Position,
- FileName = fileName,
- ModifyTime = DateTimeOffset.FromUnixTimeSeconds(unixTimestamp).DateTime,
- UserId = userId,
- GroupId = groupId,
- Mode = mode,
- FileSize = fileSize
- };
- }
- }
-}
diff --git a/src/AMWD.Common/Packing/Ar/ArWriter.cs b/src/AMWD.Common/Packing/Ar/ArWriter.cs
deleted file mode 100644
index f1c6563..0000000
--- a/src/AMWD.Common/Packing/Ar/ArWriter.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-
-namespace AMWD.Common.Packing.Ar
-{
- ///
- /// Writes UNIX ar (archive) files in the GNU format.
- ///
- ///
- /// Copied from: DotnetMakeDeb
- ///
- public class ArWriter
- {
- // Source: http://en.wikipedia.org/wiki/Ar_%28Unix%29
-
- private readonly Stream _outStream;
-
- ///
- /// Initialises a new instance of the class.
- ///
- /// The stream to write the archive to.
- public ArWriter(Stream outStream)
- {
- if (!outStream.CanWrite)
- throw new ArgumentException("Stream not writable", nameof(outStream));
-
- _outStream = outStream;
- Initialize();
- }
-
- ///
- /// Writes a file from disk to the archive.
- ///
- /// The name of the file to copy.
- /// The user ID of the file in the archive.
- /// The group ID of the file in the archive.
- /// The mode of the file in the archive (decimal).
- public void WriteFile(string fileName, int userId = 0, int groupId = 0, int mode = 33188 /* 0100644 */)
- {
- var fi = new FileInfo(fileName);
-
- using var fs = File.OpenRead(fileName);
- WriteFile(fs, fi.Name, fi.LastWriteTimeUtc, userId, groupId, mode);
- }
-
- ///
- /// Writes a file from a Stream to the archive.
- ///
- /// The stream to read the file contents from.
- /// The name of the file in the archive.
- /// The last modification time of the file in the archive.
- /// The user ID of the file in the archive.
- /// The group ID of the file in the archive.
- /// The mode of the file in the archive (decimal).
- public void WriteFile(Stream stream, string fileName, DateTime modifyTime, int userId = 0, int groupId = 0, int mode = 33188 /* 0100644 */)
- {
- // Write file header
- WriteFileHeader(fileName, modifyTime, userId, groupId, mode, stream.Length);
-
- // Write file contents
- stream.CopyTo(_outStream);
-
- // Align to even offsets, pad with LF bytes
- if ((_outStream.Position % 2) != 0)
- {
- byte[] bytes = [0x0A];
- _outStream.Write(bytes, 0, 1);
- }
- }
-
- ///
- /// Writes the archive header.
- ///
- private void Initialize()
- {
- WriteAsciiString("!\n");
- }
-
- ///
- /// Writes a file header.
- ///
- private void WriteFileHeader(string fileName, DateTime modifyTime, int userId, int groupId, int mode, long fileSize)
- {
- // File name
- if (fileName.Length > 16)
- throw new ArgumentException("Long file names are not supported.");
-
- WriteAsciiString(fileName.PadRight(16, ' '));
-
- // File modification timestamp
- long unixTime = ((DateTimeOffset)DateTime.SpecifyKind(modifyTime, DateTimeKind.Utc)).ToUnixTimeSeconds();
- WriteAsciiString(unixTime.ToString().PadRight(12, ' '));
-
- // User ID
- if (userId >= 0)
- {
- WriteAsciiString(userId.ToString().PadRight(6, ' '));
- }
- else
- {
- WriteAsciiString(" ");
- }
-
- // Group ID
- if (groupId >= 0)
- {
- WriteAsciiString(groupId.ToString().PadRight(6, ' '));
- }
- else
- {
- WriteAsciiString(" ");
- }
-
- // File mode
- if (mode >= 0)
- {
- WriteAsciiString(Convert.ToString(mode, 8).PadRight(8, ' '));
- }
- else
- {
- WriteAsciiString(" ");
- }
-
- // File size in bytes
- if (fileSize < 0 || 10000000000 <= fileSize)
- throw new ArgumentOutOfRangeException(nameof(fileSize), "Invalid file size."); // above 9.32 GB
-
- WriteAsciiString(fileSize.ToString().PadRight(10, ' '));
-
- // File magic
- byte[] bytes = [0x60, 0x0A];
- _outStream.Write(bytes, 0, 2);
- }
-
- ///
- /// Writes a string using ASCII encoding.
- ///
- /// The string to write to the output stream.
- private void WriteAsciiString(string str)
- {
- byte[] bytes = Encoding.ASCII.GetBytes(str);
- _outStream.Write(bytes, 0, bytes.Length);
- }
- }
-}
diff --git a/test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs b/test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs
deleted file mode 100644
index eb507e5..0000000
--- a/test/AMWD.Common.Tests/Packing/Ar/ArReaderTest.cs
+++ /dev/null
@@ -1,353 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using AMWD.Common.Packing.Ar;
-
-namespace AMWD.Common.Tests.Packing.Ar
-{
- [TestClass]
- public class ArReaderTest
- {
- private readonly DateTime _fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc);
-
- private Dictionary _files;
-
- private MemoryStream _inStream;
-
- [TestInitialize]
- public void Initialize()
- {
- _files = new Dictionary
- {
- {
- "abcd.tmp",
- new ArFileInfo
- {
- FileName = "abcd.tmp",
- FileSize = 14,
- GroupId = 456,
- Mode = 33188,
- ModifyTime = _fixedDateTime,
- UserId = 123
- }
- },
- {
- "efgh.tmp",
- new ArFileInfo
- {
- FileName = "efgh.tmp",
- FileSize = 14,
- GroupId = 456,
- Mode = 33188,
- ModifyTime = _fixedDateTime,
- UserId = 123
- }
- },
- {
- "ijkl.tmp",
- new ArFileInfo
- {
- FileName = "ijkl.tmp",
- FileSize = 13,
- GroupId = 456,
- Mode = 33188,
- ModifyTime = _fixedDateTime,
- UserId = 123
- }
- }
- };
-
- _inStream = new MemoryStream();
- _inStream.Write(Encoding.ASCII.GetBytes("!\n"));
-
- foreach (var file in _files)
- {
- int unixSeconds = (int)file.Value.ModifyTime.Subtract(DateTime.UnixEpoch).TotalSeconds;
-
- _inStream.Write(Encoding.ASCII.GetBytes($"{file.Key,-16}{unixSeconds,-12}123 456 100644 {file.Value.FileSize,-10}`\n"));
- _inStream.Write(Encoding.UTF8.GetBytes(new string('a', (int)file.Value.FileSize)));
- if (file.Value.FileSize % 2 != 0)
- _inStream.Write(Encoding.ASCII.GetBytes("\n"));
- }
-
- _inStream.Seek(0, SeekOrigin.Begin);
- }
-
- [TestCleanup]
- public void Cleanup()
- {
- _inStream.Dispose();
- _inStream = null;
- }
-
- [TestMethod]
- public void ShouldInitializeArchive()
- {
- // Arrange
- _inStream.Dispose();
- _inStream = new MemoryStream();
- _inStream.Write(Encoding.ASCII.GetBytes("!\n"));
- _inStream.Seek(0, SeekOrigin.Begin);
-
- // Act
- var reader = new ArReader(_inStream);
-
- // Assert
- Assert.IsNotNull(reader);
- Assert.IsFalse(reader.GetFileList().Any());
- }
-
- [TestMethod]
- public void ShouldInitializeWithFiles()
- {
- // Arrange
-
- // Act
- var reader = new ArReader(_inStream);
-
- // Assert
- Assert.IsNotNull(reader);
- Assert.IsTrue(reader.GetFileList().Any());
- }
-
- [TestMethod]
- public void ShouldListFileNames()
- {
- // Arrange
- var reader = new ArReader(_inStream);
-
- // Act
- var fileList = reader.GetFileList().ToList();
-
- // Assert
- Assert.IsNotNull(reader);
- Assert.AreEqual(_files.Count, fileList.Count);
-
- foreach (string name in _files.Keys)
- Assert.IsTrue(fileList.Contains(name));
- }
-
- [TestMethod]
- public void ShouldReturnValidFileInfo()
- {
- // Arrange
- var infos = new List();
- var reader = new ArReader(_inStream);
-
- // Act
- foreach (string name in _files.Keys)
- infos.Add(reader.GetFileInfo(name));
-
- // Assert
- Assert.IsNotNull(reader);
- Assert.AreEqual(_files.Count, infos.Count);
-
- foreach (var expected in _files.Values)
- {
- var actual = infos.Single(fi => fi.FileName == expected.FileName);
-
- Assert.AreEqual(expected.FileName, actual.FileName);
- Assert.AreEqual(expected.FileSize, actual.FileSize);
- Assert.AreEqual(expected.GroupId, actual.GroupId);
- Assert.AreEqual(expected.Mode, actual.Mode);
- Assert.AreEqual(expected.ModifyTime, actual.ModifyTime);
- Assert.AreEqual(expected.UserId, actual.UserId);
- }
- }
-
- [TestMethod]
- public void ShouldReturnValidFileContent()
- {
- // Arrange
- var contents = new Dictionary();
- var reader = new ArReader(_inStream);
-
- // Act
- foreach (string name in _files.Keys)
- {
- using var ms = new MemoryStream();
- reader.ReadFile(name, ms);
- ms.Seek(0, SeekOrigin.Begin);
-
- contents.Add(name, Encoding.UTF8.GetString(ms.ToArray()));
- }
-
- // Assert
- Assert.IsNotNull(reader);
- Assert.AreEqual(_files.Count, contents.Count);
-
- foreach (var expected in _files.Values)
- {
- string content = contents[expected.FileName];
-
- if (expected.FileSize % 2 != 0)
- Assert.AreEqual(13, content.Length);
- else
- Assert.AreEqual(14, content.Length);
-
- Assert.AreEqual(new string('a', (int)expected.FileSize), content);
- }
- }
-
- [TestMethod]
- public void ShouldWriteFileToDisk()
- {
- // Arrange
- string tmpFile = Path.GetTempFileName();
- var reader = new ArReader(_inStream);
-
- try
- {
- // Act
- using var ms = new MemoryStream();
-
- reader.ReadFile("abcd.tmp", ms);
- reader.ReadFile("abcd.tmp", tmpFile);
-
- // Assert
- CollectionAssert.AreEqual(ms.ToArray(), File.ReadAllBytes(tmpFile));
- }
- finally
- {
- File.Delete(tmpFile);
- }
- }
-
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public void ShouldThrowExceptionOnMissingRead()
- {
- // Arrange
- using var stream = new OverrideStream();
- stream.CanReadOR = false;
- stream.CanSeekOR = true;
- stream.CanWriteOR = true;
-
- // Act
- var reader = new ArReader(stream);
-
- // Assert - ArgumentException
- Assert.Fail();
- }
-
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public void ShouldThrowExceptionOnMissingSeek()
- {
- // Arrange
- using var stream = new OverrideStream();
- stream.CanReadOR = true;
- stream.CanSeekOR = false;
- stream.CanWriteOR = true;
-
- // Act
- var reader = new ArReader(stream);
-
- // Assert - ArgumentException
- Assert.Fail();
- }
-
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public void ShouldThrowExceptionOnMissingWrite()
- {
- // Arrange
- using var stream = new OverrideStream();
- stream.CanReadOR = true;
- stream.CanSeekOR = true;
- stream.CanWriteOR = false;
-
- var reader = new ArReader(_inStream);
-
- // Act
- reader.ReadFile("abcd.tmp", stream);
-
- // Assert - ArgumentException
- Assert.Fail();
- }
-
- [TestMethod]
- [ExpectedException(typeof(FormatException))]
- public void ShouldThrowExceptionOnInvalidArchive()
- {
- // Arrange
- _inStream.Seek(8, SeekOrigin.Begin);
-
- // Act
- _ = new ArReader(_inStream);
-
- // Assert - FormatException
- Assert.Fail();
- }
-
- [TestMethod]
- [ExpectedException(typeof(FormatException))]
- public void ShouldThrowExceptionOnInvalidMagic()
- {
- // Arrange
- _inStream.Seek(0, SeekOrigin.End);
- _inStream.Write(Encoding.ASCII.GetBytes($"{"foo.bar",-16}{"123456789",-12}123 456 100644 {"0",-10}´\n"));
- _inStream.Seek(0, SeekOrigin.Begin);
-
- // Act
- _ = new ArReader(_inStream);
-
- // Assert - FormatException
- Assert.Fail();
- }
-
- [TestMethod]
- public void ShouldWriteNothingToStreamForMissingFile()
- {
- // Arrange
- var reader = new ArReader(_inStream);
-
- // Act
- using var ms = new MemoryStream();
-
- reader.ReadFile("foo.bar", ms);
- ms.Seek(0, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(0, ms.Length);
- }
-
- [TestMethod]
- public void ShouldWriteNothingToDiskForMissingFile()
- {
- // Arrange
- string tmpFile = Path.GetTempFileName();
- var reader = new ArReader(_inStream);
-
- try
- {
- // Act
- reader.ReadFile("foo.bar", tmpFile);
-
- // Assert
- Assert.AreEqual(0, new FileInfo(tmpFile).Length);
- }
- finally
- {
- File.Delete(tmpFile);
- }
- }
-
- private class OverrideStream : MemoryStream
- {
- public override bool CanWrite => CanWriteOR;
-
- public bool CanWriteOR { get; set; }
-
- public override bool CanSeek => CanSeekOR;
-
- public bool CanSeekOR { get; set; }
-
- public override bool CanRead => CanReadOR;
-
- public bool CanReadOR { get; set; }
- }
- }
-}
diff --git a/test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs b/test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs
deleted file mode 100644
index 74be7bd..0000000
--- a/test/AMWD.Common.Tests/Packing/Ar/ArWriterTest.cs
+++ /dev/null
@@ -1,304 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using AMWD.Common.Packing.Ar;
-
-namespace AMWD.Common.Tests.Packing.Ar
-{
- [TestClass]
- public class ArWriterTest
- {
- private readonly DateTime _fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc);
-
- private readonly Dictionary _files = [];
-
- private MemoryStream _outStream;
-
- [TestInitialize]
- public void Initialize()
- {
- _files.Clear();
- for (int i = 0; i < 3; i++)
- {
- var (filePath, content) = GenerateTestFile();
- _files.Add(filePath, content);
- }
-
- _outStream = new MemoryStream();
- }
-
- [TestCleanup]
- public void Cleanup()
- {
- foreach (var kvp in _files)
- File.Delete(kvp.Key);
-
- _files.Clear();
-
- _outStream.Dispose();
- _outStream = null;
- }
-
- [TestMethod]
- public void ShouldInitializeArchive()
- {
- // Arrange
- byte[] initBytes = new byte[8];
-
- // Act
- _ = new ArWriter(_outStream);
-
- _outStream.Seek(0, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(initBytes.Length, _outStream.Length);
-
- _outStream.Read(initBytes, 0, initBytes.Length);
- CollectionAssert.AreEqual(Encoding.ASCII.GetBytes("!\n"), initBytes);
- }
-
- [TestMethod]
- public void ShouldWriteOneFile()
- {
- // Arrange
- var firstFileKvp = _files.First();
- byte[] headerBytes = new byte[60];
- byte[] contentBytes = new byte[14];
-
- var writer = new ArWriter(_outStream);
-
- // Act
- writer.WriteFile(firstFileKvp.Key, 123, 456);
-
- _outStream.Seek(8, SeekOrigin.Begin); // set behind init bytes
-
- // Assert
- Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
-
- _outStream.Read(headerBytes, 0, headerBytes.Length);
- _outStream.Read(contentBytes, 0, contentBytes.Length);
-
- string header = Encoding.ASCII.GetString(headerBytes);
- string content = Encoding.UTF8.GetString(contentBytes);
-
- Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 100644 14 `\n", header);
- Assert.AreEqual(firstFileKvp.Value, content);
- }
-
- [TestMethod]
- public void ShouldWriteMultipleFiles()
- {
- // Arrange
- var writer = new ArWriter(_outStream);
-
- // Act
- foreach (var kvp in _files)
- writer.WriteFile(kvp.Key, 123, 456);
-
- _outStream.Seek(8, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(8 + 3 * 60 + 3 * 14, _outStream.Length);
-
- foreach (var kvp in _files)
- {
- byte[] headerBytes = new byte[60];
- byte[] contentBytes = new byte[14];
-
- _outStream.Read(headerBytes, 0, headerBytes.Length);
- _outStream.Read(contentBytes, 0, contentBytes.Length);
-
- string header = Encoding.ASCII.GetString(headerBytes);
- string content = Encoding.UTF8.GetString(contentBytes);
-
- Assert.AreEqual($"{Path.GetFileName(kvp.Key),-16}1677666030 123 456 100644 14 `\n", header);
- Assert.AreEqual(kvp.Value, content);
- }
- }
-
- [TestMethod]
- public void ShouldPadToEven()
- {
- // Arrange
- var (filePath, fileContent) = GenerateTestFile(13);
-
- try
- {
- byte[] headerBytes = new byte[60];
- byte[] contentBytes = new byte[14];
-
- var writer = new ArWriter(_outStream);
-
- // Act
- writer.WriteFile(filePath, 123, 456);
-
- _outStream.Seek(8, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
-
- _outStream.Read(headerBytes, 0, headerBytes.Length);
- _outStream.Read(contentBytes, 0, contentBytes.Length);
-
- string header = Encoding.ASCII.GetString(headerBytes);
- string content = Encoding.UTF8.GetString(contentBytes);
-
- Assert.AreEqual($"{Path.GetFileName(filePath),-16}1677666030 123 456 100644 13 `\n", header);
- Assert.AreEqual(fileContent + "\n", content);
- }
- finally
- {
- File.Delete(filePath);
- }
- }
-
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public void ShouldFailOnFileNameTooLong()
- {
- // Arrange
- var (filePath, _) = GenerateTestFile();
- try
- {
- string path = Path.GetDirectoryName(filePath);
- string fileName = Path.GetFileName(filePath);
- fileName = fileName.PadLeft(20, 'a');
-
- File.Move(filePath, Path.Combine(path, fileName));
- filePath = Path.Combine(path, fileName);
-
- var writer = new ArWriter(_outStream);
-
- // Act
- writer.WriteFile(filePath, 123, 456);
-
- // Assert - Exception
- Assert.Fail();
- }
- finally
- {
- File.Delete(filePath);
- }
- }
-
- [TestMethod]
- public void ShouldWriteEmptyOnNegativeUserId()
- {
- // Arrange
- var firstFileKvp = _files.First();
- byte[] headerBytes = new byte[60];
- byte[] contentBytes = new byte[14];
-
- var writer = new ArWriter(_outStream);
-
- // Act
- writer.WriteFile(firstFileKvp.Key, -123, 456);
-
- _outStream.Seek(8, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
-
- _outStream.Read(headerBytes, 0, headerBytes.Length);
- _outStream.Read(contentBytes, 0, contentBytes.Length);
-
- string header = Encoding.ASCII.GetString(headerBytes);
- string content = Encoding.UTF8.GetString(contentBytes);
-
- Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 456 100644 14 `\n", header);
- Assert.AreEqual(firstFileKvp.Value, content);
- }
-
- [TestMethod]
- public void ShouldWriteEmptyOnNegativeGroupId()
- {
- // Arrange
- var firstFileKvp = _files.First();
- byte[] headerBytes = new byte[60];
- byte[] contentBytes = new byte[14];
-
- var writer = new ArWriter(_outStream);
-
- // Act
- writer.WriteFile(firstFileKvp.Key, 123, -456);
-
- _outStream.Seek(8, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
-
- _outStream.Read(headerBytes, 0, headerBytes.Length);
- _outStream.Read(contentBytes, 0, contentBytes.Length);
-
- string header = Encoding.ASCII.GetString(headerBytes);
- string content = Encoding.UTF8.GetString(contentBytes);
-
- Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 100644 14 `\n", header);
- Assert.AreEqual(firstFileKvp.Value, content);
- }
-
- [TestMethod]
- public void ShouldWriteEmptyOnNegativeMode()
- {
- // Arrange
- var firstFileKvp = _files.First();
- byte[] headerBytes = new byte[60];
- byte[] contentBytes = new byte[14];
-
- var writer = new ArWriter(_outStream);
-
- // Act
- writer.WriteFile(firstFileKvp.Key, 123, 456, -1);
-
- _outStream.Seek(8, SeekOrigin.Begin);
-
- // Assert
- Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, _outStream.Length);
-
- _outStream.Read(headerBytes, 0, headerBytes.Length);
- _outStream.Read(contentBytes, 0, contentBytes.Length);
-
- string header = Encoding.ASCII.GetString(headerBytes);
- string content = Encoding.UTF8.GetString(contentBytes);
-
- Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 14 `\n", header);
- Assert.AreEqual(firstFileKvp.Value, content);
- }
-
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public void ShouldFailOnNonWritableStream()
- {
- // Arrange
- using var testStream = new NonWriteStream();
-
- // Act
- _ = new ArWriter(testStream);
-
- // Assert - ArgumentException
- Assert.Fail();
- }
-
- private (string filePath, string content) GenerateTestFile(int length = 14)
- {
- string filePath = Path.GetTempFileName();
- string text = CryptographyHelper.GetRandomString(length);
-
- File.WriteAllText(filePath, text);
- File.SetLastWriteTimeUtc(filePath, _fixedDateTime);
- return (filePath, text);
- }
-
- private class NonWriteStream : MemoryStream
- {
- public NonWriteStream()
- : base()
- { }
-
- public override bool CanWrite => false;
- }
- }
-}