// TODO: Refactor to be more like Package code. This is messy // Substring should be replaced with a .split(": ") // Also Directly assign to the values instead of using temporary variables. using Serilog; namespace EdgeInstaller; public sealed partial class ReleaseData { public DateTime Date { get; private set; } public List Architectures { get; private set; } public List PackageFiles { get; private set; } public ReleaseData(string Release) { Log.Information("Creating new ReleaseData object"); if (Release == "") { throw new ArgumentException( message: "Empty release provided to ReleaseData", paramName: "Release"); } // Pull release apart List ReleaseLines = Release.Split("\n", StringSplitOptions.RemoveEmptyEntries).ToList(); Log.Debug("ReleaseLines = {@releaselines}", ReleaseLines); Date = DateTime.Parse( ReleaseLines.Where( Line => Line.Contains("Date:")) .First() .Split(": ") .Last() ); // Safely find and add the possible architectures string ArchitecturesLine = ReleaseLines.Where( Line => Line.Contains("Architectures:")) .FirstOrDefault(""); Log.Debug("Arch line: {archline}", ArchitecturesLine); if (ArchitecturesLine == "") { throw _lineFormatException("architectures"); } Architectures = ArchitecturesLine .Split(": ").Last() // We only want items after the label .Split(" ", StringSplitOptions.RemoveEmptyEntries) .ToList(); // Cope with files being in binary-arch sometimes. List BinArchs = new(); foreach (var arch in Architectures) { BinArchs.Add("binary-" + arch); } Architectures.AddRange(BinArchs); Log.Debug("Extracted architectures {@architectures}", Architectures); /* * Package files are grabbed here. It is worth noting that empty files * are removed (along with the .gz versions) this avoids displaying impossible * options to the user */ // First split the Lines to only get the file data // NOTE: This is probably the cleanest way to do this. List SHA256FileData = ReleaseLines.Where((_, index) => index > ReleaseLines.IndexOf("SHA256:") && index < ReleaseLines.IndexOf("SHA512:") ).ToList(); Log.Debug("SHA256FileData = {@data}", SHA256FileData); // Now process these lines to get the data we need List releasePackageFiles = new(); foreach (var fileData in SHA256FileData) { Log.Debug("fileData = {filedata}", fileData); string[] splitData = fileData.Split(' ', StringSplitOptions.RemoveEmptyEntries); Log.Debug("splitData = {@splitData}", splitData); string checksum = splitData[0]; int size = int.Parse(splitData[1]); string filename = splitData[2]; releasePackageFiles.Add(new PackageFile(filename, checksum, size)); } // remove any empty files (and the .gz version of them) // NOTE: Empty gz files still have a size due to headers // WARN: The `.ToList` is essential to ensure we COPY the list and not use references to the original items var emptyFiles = releasePackageFiles .Where(package => package.size == 0) .ToList(); foreach (var file in emptyFiles) { releasePackageFiles .RemoveAll(package => package.filename == file.filename || package.filename == $"{file.filename}.gz" ); } PackageFiles = releasePackageFiles; // After removing the empty files we need to make sure that we remove the architectures that don't have any files foreach (var arch in Architectures.ToList()) /* * WARN: `.ToList` is used again to copy the list so we can iterate and operate * on the list at the same time. This could be unsafe, but in this scenario * should never cause any issues. */ { if (PackageFiles.Where(file => file.filename.Contains($"main/{arch}/")).Count() == 0) // match on "/arch/" { Log.Debug("removing /{arch}/", arch); Architectures.Remove(arch); } } if (Architectures.Count == 0) { throw new Exception("Architectures is null"); } } private static FormatException _lineFormatException(string field) { return new FormatException($"Release does not contain a valid {field} line"); } } public record PackageFile(string filename, string Checksum, int size);