using ArcaneLibs;
using ArcaneLibs.Extensions;
namespace Rhea.Nar;
/// Serialization class for uncompressed NAR streams
public class NarWriter
{
/// Serialises a given path to a NAR stream
///
/// An enumerator of chunks of the stream, in order
public IEnumerable GetNarStreamContents(string path)
{
yield return NarConsts.Header;
foreach (var chunk in GetNarStreamContentsInner(path)) yield return chunk;
}
private IEnumerable GetNarStreamContentsInner(string path)
{
FileInfo fe = new FileInfo(path);
if (string.IsNullOrWhiteSpace(fe.LinkTarget))
{
if (fe.Attributes.HasFlag(FileAttributes.Directory))
foreach (var chunk in GetNarDirectoryContents(new DirectoryInfo(path)))
yield return chunk;
else
foreach (var chunk in GetNarFileContents(fe))
yield return chunk;
}
else
foreach (var chunk in GetNarSymlinkContents(fe))
yield return chunk;
}
private IEnumerable GetNarStreamContentsInner(FileSystemInfo fsi)
{
if (string.IsNullOrWhiteSpace(fsi.LinkTarget))
{
if (fsi is DirectoryInfo di)
foreach (var chunk in GetNarDirectoryContents(di))
yield return chunk;
else
foreach (var chunk in GetNarFileContents(fsi as FileInfo ?? throw new Exception(fsi.GetType().Name + " != FileInfo")))
yield return chunk;
}
else yield return GetNarSymlinkContents(fsi);
}
private IEnumerable GetNarDirectoryContents(DirectoryInfo de)
{
yield return NarConsts.LeftParen;
yield return CombinedNarConsts.TypeDirectory;
foreach (var fe in FileUtils.EnumerateDirectory(de.FullName).OrderBy(x => x.Name, StringComparer.Ordinal))
{
yield return CombinedNarConsts.EntryWithName;
yield return GetLengthPrefixedPadded(fe.Name.AsBytes().ToArray());
yield return NarConsts.Node;
foreach (var chunk in GetNarStreamContentsInner(fe))
yield return chunk;
yield return NarConsts.RightParen;
}
yield return NarConsts.RightParen;
}
private byte[] GetNarSymlinkContents(FileSystemInfo fe)
{
return
[
..NarConsts.LeftParen,
..CombinedNarConsts.SymlinkWithTarget,
..GetLengthPrefixedPadded(fe.LinkTarget!.AsBytes().ToArray()),
..NarConsts.RightParen
];
}
private IEnumerable GetNarFileContents(FileInfo fe)
{
yield return NarConsts.LeftParen;
yield return CombinedNarConsts.TypeRegular;
if (
fe.UnixFileMode.HasFlag(UnixFileMode.UserExecute)
|| fe.UnixFileMode.HasFlag(UnixFileMode.GroupExecute)
|| fe.UnixFileMode.HasFlag(UnixFileMode.OtherExecute)
)
{
yield return CombinedNarConsts.ExecutableWithPadding;
}
yield return NarConsts.Contents;
yield return BitConverter.GetBytes((ulong)fe.Length);
using (var fs = File.OpenRead(fe.FullName))
{
int chunkSize = ushort.MaxValue;
long remaining = fs.Length;
byte[] buf = new byte[chunkSize];
while (remaining > 0)
{
int read;
remaining -= read = fs.Read(buf, 0, chunkSize);
yield return buf[..read];
}
}
// var targetLength = Math.Ceiling(fe.Length / 8d) * 8UL;
var targetLength = ((fe.Length + 7) / 8) * 8;
var padSize = (int)(targetLength - fe.Length);
yield return new byte[padSize];
yield return NarConsts.RightParen;
}
#region Utility methods
internal static byte[] GetLengthPrefixedPadded(ReadOnlySpan data)
{
byte[] unpadded = [..BitConverter.GetBytes((ulong)data.Length), .. data];
// var targetLength = Math.Ceiling(unpadded.Length / 8d) * 8;
// Array.Resize(ref unpadded, (int)targetLength);
var targetLength = ((unpadded.Length + 7) / 8) * 8;
Array.Resize(ref unpadded, targetLength);
return unpadded;
}
#endregion
}