Another bad commit
Lots of changes here... - Removed code using RecursivExtractor due to bad usage of /tmp Note: the code that used RecursiveExtractor may not have been in the previous commit - Created _functioning_ implementation of DebUnpacker - Restructured Project layout. TODO: - Possibly rewrite how the DebUnpacker works to have the file contents part of an entry. - Further restructuring and refactoring to make the code a little neater. - Add code for the final unpack steps Un-XZ -> untar -> write to disk - Add code to install - Add code for modified post-install pre-remove etc.. This is kinda needed to ensure proper system integration of new packages.
This commit is contained in:
parent
f650e6a3c9
commit
60e06b2144
|
|
@ -1,60 +0,0 @@
|
||||||
// Experimental code to open and dissect a `.deb` archive
|
|
||||||
|
|
||||||
static class DebArchive
|
|
||||||
{
|
|
||||||
const string DPKG_AR_MAGIC = "!<arch>\n";
|
|
||||||
const string DPKG_AR_FMAG = "`\n";
|
|
||||||
|
|
||||||
|
|
||||||
private static dpkg_ar dpkg_ar_fdopen(string filename, FileInfo fi)
|
|
||||||
{
|
|
||||||
return new dpkg_ar(
|
|
||||||
filename,
|
|
||||||
fi.UnixFileMode,
|
|
||||||
fi.Length,
|
|
||||||
fi.LastWriteTime,
|
|
||||||
fi);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static dpkg_ar dpkg_ar_open(string filename)
|
|
||||||
{
|
|
||||||
FileInfo fi = new FileInfo(filename);
|
|
||||||
|
|
||||||
return dpkg_ar_fdopen(filename, fi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class dpkg_ar
|
|
||||||
{
|
|
||||||
string name;
|
|
||||||
UnixFileMode mode;
|
|
||||||
long size;
|
|
||||||
DateTime time;
|
|
||||||
FileInfo fi;
|
|
||||||
|
|
||||||
public dpkg_ar(string name,
|
|
||||||
UnixFileMode mode,
|
|
||||||
long size,
|
|
||||||
DateTime time,
|
|
||||||
FileInfo fi)
|
|
||||||
{
|
|
||||||
this.name = name;
|
|
||||||
this.mode = mode;
|
|
||||||
this.size = size;
|
|
||||||
this.time = time;
|
|
||||||
this.fi = fi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO:
|
|
||||||
- Read the sourcecode for dpkg and find the c# equivalents
|
|
||||||
to extract an archive.
|
|
||||||
- If neccessary write a parser manually.
|
|
||||||
*/
|
|
||||||
/* NOTE:
|
|
||||||
- dpkg_ar is just FileInfo
|
|
||||||
- We might need to define dpkg_ar_hdr
|
|
||||||
- Pretty sure we can just find line ending in "`\n"
|
|
||||||
and parse that though. Split on (" ")
|
|
||||||
- Should then be able to read the files by streaming `size` bytes from the position after the and marker
|
|
||||||
*/
|
|
||||||
145
DebUnpack.cs
Normal file
145
DebUnpack.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
using System.Text;
|
||||||
|
using Serilog;
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
sealed class DebFile : IDisposable
|
||||||
|
{
|
||||||
|
const int HeaderBytes = 60;
|
||||||
|
const string FileMagic = "!<arch>\n";
|
||||||
|
|
||||||
|
private MemoryStream _fileStream;
|
||||||
|
|
||||||
|
public List<DebEntry> fileEntries { get; private set; }
|
||||||
|
|
||||||
|
public DebFile(Stream fs)
|
||||||
|
{
|
||||||
|
Log.Information("Creating new debFile from stream");
|
||||||
|
fileEntries = new();
|
||||||
|
if (fs is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Position = 0;
|
||||||
|
|
||||||
|
// read the first 8 bytes and check for the signature
|
||||||
|
byte[] magicBuffer = new byte[8];
|
||||||
|
|
||||||
|
fs.Read(magicBuffer);
|
||||||
|
Log.Debug("Magic = {magicBuffer}", Encoding.ASCII.GetString(magicBuffer));
|
||||||
|
if (Encoding.ASCII.GetString(magicBuffer) != FileMagic)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(message: "Magic Fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Position = 0;
|
||||||
|
_fileStream = new();
|
||||||
|
fs.CopyTo(_fileStream);
|
||||||
|
|
||||||
|
fs.Dispose();
|
||||||
|
// if we got here then we must have a proper archive.
|
||||||
|
// We shall now get the files that are inside.
|
||||||
|
|
||||||
|
// from OUR copy of the stream we seek to after the magic byte
|
||||||
|
_fileStream.Position = 8;
|
||||||
|
while (true) // beware making an infinite loop
|
||||||
|
{
|
||||||
|
// Read a header
|
||||||
|
byte[] header = new byte[60];
|
||||||
|
var read = _fileStream.Read(header);
|
||||||
|
// If nothing was read we have hit the end
|
||||||
|
if (read < 60) // also handle broken headers
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Otherwise process as a header
|
||||||
|
var entry = new DebEntry(header, _fileStream.Position);
|
||||||
|
fileEntries.Add(entry);
|
||||||
|
|
||||||
|
// Using the obtained size seek forwards to the next potential header
|
||||||
|
_fileStream.Seek(entry.sizeBytes, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool getFile(DebEntry file, out Stream output)
|
||||||
|
{
|
||||||
|
// If we are given a file that isn't in this archive we exit nicely
|
||||||
|
if (!fileEntries.Contains(file))
|
||||||
|
{
|
||||||
|
Log.Error("The given file entry is not valid for this DebFile");
|
||||||
|
output = Stream.Null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we have a file that is in the archive
|
||||||
|
// grab the metadata we need
|
||||||
|
long offset = file.offset;
|
||||||
|
long size = file.sizeBytes;
|
||||||
|
// stream the file to the output stream
|
||||||
|
_fileStream.Seek(offset, SeekOrigin.Begin);
|
||||||
|
byte[] fileBuffer = new byte[size];
|
||||||
|
|
||||||
|
var count = _fileStream.Read(fileBuffer);
|
||||||
|
if (count != size)
|
||||||
|
{
|
||||||
|
Log.Error("Didn't read the correct amount of data from stream (expected:{size} got:{count})", size, count);
|
||||||
|
output = Stream.Null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = new MemoryStream(fileBuffer); // convert the read buffer to a stream
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{ // make sure we can clean up our own mess
|
||||||
|
_fileStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class DebEntry
|
||||||
|
{
|
||||||
|
// Escape sequnces are used in this string read as "`\n"
|
||||||
|
const string HeaderMagic = "\x60\n";
|
||||||
|
|
||||||
|
public string name { get; private set; }
|
||||||
|
public long mtime { get; private set; }
|
||||||
|
public int mode { get; private set; }
|
||||||
|
public int gid { get; private set; }
|
||||||
|
public int uid { get; private set; }
|
||||||
|
public long sizeBytes { get; private set; }
|
||||||
|
public long offset { get; private set; } // where does the file start
|
||||||
|
|
||||||
|
public DebEntry(byte[] buffer, long Position)
|
||||||
|
{
|
||||||
|
Log.Debug("Reading header at position {p}", Position - 60); // since we are being passed a header we take 60 to get the header position
|
||||||
|
if (buffer is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.Length < 60)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("buffer too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
name = Encoding.ASCII.GetString(buffer[0..16]);
|
||||||
|
mtime = long.Parse(Encoding.ASCII.GetString(buffer[16..28]));
|
||||||
|
mode = int.Parse(Encoding.ASCII.GetString(buffer[28..34]));
|
||||||
|
gid = int.Parse(Encoding.ASCII.GetString(buffer[34..40]));
|
||||||
|
uid = int.Parse(Encoding.ASCII.GetString(buffer[40..48]));
|
||||||
|
sizeBytes = long.Parse(Encoding.ASCII.GetString(buffer[48..58]));
|
||||||
|
string magic = Encoding.ASCII.GetString(buffer[58..^0]);
|
||||||
|
|
||||||
|
offset = Position;
|
||||||
|
|
||||||
|
Log.Debug("Header Magic = {magic}", magic);
|
||||||
|
Log.Debug("{@this}", this);
|
||||||
|
if (magic != HeaderMagic)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("magic failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
Installer/Installer.cs
Normal file
20
Installer/Installer.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
// This class is what will do the heavy lifting.
|
||||||
|
// the UI will all be handled seperately
|
||||||
|
public class Installer
|
||||||
|
{
|
||||||
|
|
||||||
|
private static readonly string DataDir =
|
||||||
|
Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"ClosedLess",
|
||||||
|
"EdgeInstall-Jankmode");
|
||||||
|
|
||||||
|
|
||||||
|
public Installer(HttpClient client)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
16
Package/Package.JsonConstructor.cs
Normal file
16
Package/Package.JsonConstructor.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public sealed partial class Package
|
||||||
|
{
|
||||||
|
[JsonConstructor] // NOTE: without this it would be impossible to deserialise a Package
|
||||||
|
private Package(string PackageName, Version Version, int Size, string SHA256, string Filename)
|
||||||
|
{
|
||||||
|
this.PackageName = PackageName;
|
||||||
|
this.Version = Version;
|
||||||
|
this.Size = Size;
|
||||||
|
this.SHA256 = SHA256;
|
||||||
|
this.Filename = Filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
// Defines a Package
|
// Defines a Package
|
||||||
|
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
sealed class Package
|
sealed partial class Package
|
||||||
{
|
{
|
||||||
|
|
||||||
public string PackageName { get; private set; }
|
public string PackageName { get; private set; }
|
||||||
|
|
@ -12,16 +12,6 @@ sealed class Package
|
||||||
public string SHA256 { get; private set; }
|
public string SHA256 { get; private set; }
|
||||||
public string Filename { get; private set; }
|
public string Filename { get; private set; }
|
||||||
|
|
||||||
[JsonConstructor] // NOTE: without this it would be impossible to deserialise a Package
|
|
||||||
private Package(string PackageName, Version Version, int Size, string SHA256, string Filename)
|
|
||||||
{
|
|
||||||
this.PackageName = PackageName;
|
|
||||||
this.Version = Version;
|
|
||||||
this.Size = Size;
|
|
||||||
this.SHA256 = SHA256;
|
|
||||||
this.Filename = Filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Package(string packageString)
|
public Package(string packageString)
|
||||||
{
|
{
|
||||||
Log.Information("Creating package from string");
|
Log.Information("Creating package from string");
|
||||||
|
|
@ -61,6 +51,4 @@ sealed class Package
|
||||||
|
|
||||||
/* TODO: capture dependencies and create method to find them.
|
/* TODO: capture dependencies and create method to find them.
|
||||||
- This would probably need distro specific lookup tables.
|
- This would probably need distro specific lookup tables.
|
||||||
- Also would ideally need code to shell out (at root level)
|
|
||||||
to perform installs...
|
|
||||||
*/
|
*/
|
||||||
126
Program.Downloaders.cs
Normal file
126
Program.Downloaders.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
using Spectre.Console;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public static partial class EdgeInstall
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
private static async Task<string> GetRelease()
|
||||||
|
{
|
||||||
|
string releaseURL = "dists/stable/Release";
|
||||||
|
using HttpResponseMessage response = await _client.GetAsync(releaseURL);
|
||||||
|
await Task.Delay(5000);
|
||||||
|
Log.Information("Getting release from {url}", response.Headers.Location);
|
||||||
|
response.EnsureSuccessStatusCode(); //WARN: this can throw an exception, Please wrap in try/catch or rewrite function
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
Log.Debug("{body}", body);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<string> downloadFile(string URL, string DestinationDirectory)
|
||||||
|
{
|
||||||
|
|
||||||
|
Uri file = new Uri(URL);
|
||||||
|
|
||||||
|
string fileLocation = Path.Combine(DestinationDirectory, file.Segments.Last());
|
||||||
|
|
||||||
|
using HttpResponseMessage response = await _client.GetAsync(URL);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
await Task.Delay(2000);
|
||||||
|
|
||||||
|
var fileData = response.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
using var fileStream = new FileStream(fileLocation, FileMode.OpenOrCreate);
|
||||||
|
await response.Content.CopyToAsync(fileStream);
|
||||||
|
|
||||||
|
return fileLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This overload of downloadFile allows you to get the progess
|
||||||
|
static async Task<string> downloadFile(string URL, string DestinationDirectory, ProgressTask task)
|
||||||
|
{
|
||||||
|
Uri file = new Uri(URL, UriKind.RelativeOrAbsolute); // allow this to work regardless of relativity
|
||||||
|
|
||||||
|
if (!file.IsAbsoluteUri && _client.BaseAddress is not null)
|
||||||
|
{ // Ego te absolvo
|
||||||
|
file = new Uri(_client.BaseAddress, URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileLocation = Path.Combine(DestinationDirectory, file.Segments.Last());
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = await _client.GetAsync(URL, HttpCompletionOption.ResponseHeadersRead))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
|
||||||
|
task.MaxValue = response.Content.Headers.ContentLength ?? 0;
|
||||||
|
task.StartTask();
|
||||||
|
await Task.Delay(2000);
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Starting download of [u]{URL}[/] ({task.MaxValue} Bytes)");
|
||||||
|
|
||||||
|
using (var contentStream = await response.Content.ReadAsStreamAsync())
|
||||||
|
using (var fileStream = File.Open(fileLocation, FileMode.OpenOrCreate))
|
||||||
|
{
|
||||||
|
var buffer = new byte[downloadBufferBYTES];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
await Task.Delay(downloadDelayMS);
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Download of [u]{URL}[/] [green b]Complete![/]");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
task.Increment(read);
|
||||||
|
|
||||||
|
await fileStream.WriteAsync(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This overload of downloadFile allows you to get the progess
|
||||||
|
static async Task<MemoryStream> downloadStream(string URL, ProgressTask task)
|
||||||
|
{
|
||||||
|
MemoryStream outputStream = new();
|
||||||
|
using (HttpResponseMessage response = await _client.GetAsync(URL, HttpCompletionOption.ResponseHeadersRead))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
|
||||||
|
task.MaxValue = response.Content.Headers.ContentLength ?? 0;
|
||||||
|
task.StartTask();
|
||||||
|
await Task.Delay(2000);
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Starting download of [u]{URL}[/] to memory ({task.MaxValue} Bytes)");
|
||||||
|
|
||||||
|
int bufferSize = (int)Math.Round(task.MaxValue / 100);
|
||||||
|
using (var contentStream = await response.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
var buffer = new byte[bufferSize];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
await Task.Delay(downloadDelayMS);
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Download of [u]{URL}[/] [green b]Complete![/]");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
task.Increment(read);
|
||||||
|
|
||||||
|
await outputStream.WriteAsync(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Program.HelperFunctions.cs
Normal file
67
Program.HelperFunctions.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
using Serilog;
|
||||||
|
using Spectre.Console;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public static partial class EdgeInstall
|
||||||
|
{
|
||||||
|
private static string GetPackagesString(string filepath)
|
||||||
|
{
|
||||||
|
using (var CompressedStream = File.Open(filepath, FileMode.Open))
|
||||||
|
using (var decompressor = new GZipStream(CompressedStream, CompressionMode.Decompress))
|
||||||
|
using (var streamReader = new StreamReader(decompressor))
|
||||||
|
{
|
||||||
|
return streamReader.ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HACK: Spectre.Console uses fixed regex to detect terminal capabilites
|
||||||
|
* This function tests for known working terminals that are currently not
|
||||||
|
* detected by AnsiDetector
|
||||||
|
*/
|
||||||
|
private static void BUGFIX_ansiDetection()
|
||||||
|
{
|
||||||
|
Regex[] regexes = {
|
||||||
|
new("foot")
|
||||||
|
};
|
||||||
|
AnsiConsole.Profile.Capabilities.Ansi = true;
|
||||||
|
AnsiConsole.Profile.Capabilities.Legacy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ValidateChecksum(FileInfo file, string checksum)
|
||||||
|
{
|
||||||
|
using (FileStream fs = file.Open(FileMode.Open))
|
||||||
|
{
|
||||||
|
return ValidateChecksum(fs, checksum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ValidateChecksum(Stream stream, string checksum)
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
using (SHA256 mySHA256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
byte[] hashValue = mySHA256.ComputeHash(stream);
|
||||||
|
if (hashValue is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("Hash be null??");
|
||||||
|
}
|
||||||
|
Log.Debug("Computed hash = {hash}", hashValue);
|
||||||
|
byte[] storedHash = Convert.FromHexString(checksum);
|
||||||
|
Log.Debug("Stored Hash = {hash}", storedHash);
|
||||||
|
|
||||||
|
Log.Debug("Compare result = {result}", hashValue.SequenceEqual(storedHash));
|
||||||
|
return (storedHash.SequenceEqual(hashValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Version GetCurrentVersion()
|
||||||
|
{
|
||||||
|
|
||||||
|
return new("0.0.0.0-0");
|
||||||
|
}
|
||||||
|
}
|
||||||
150
Program.Renderers.cs
Normal file
150
Program.Renderers.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public static partial class EdgeInstall
|
||||||
|
{
|
||||||
|
|
||||||
|
static void RenderPackageAsTable(Package package)
|
||||||
|
{
|
||||||
|
Table Package = new Table()
|
||||||
|
.Centered()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.HideHeaders()
|
||||||
|
.Title("Package To Install");
|
||||||
|
|
||||||
|
AnsiConsole.Live(Package)
|
||||||
|
.AutoClear(false)
|
||||||
|
.Start(context =>
|
||||||
|
{
|
||||||
|
Package.AddColumns("", "");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(250);
|
||||||
|
|
||||||
|
Package.AddRow("[bold yellow]Package Name : [/]", $"{package.PackageName}");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
Package.AddRow("[bold yellow]Version : [/]", $"{package.Version.ToString()}");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
Package.AddRow("[bold yellow]Size : [/]", $"{package.Size.ToString()}B");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
Package.AddRow("[bold yellow]SHA256 : [/]", $"{package.SHA256}");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
Package.AddRow("[bold yellow]Filename : [/]", $"{package.Filename}");
|
||||||
|
context.Refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderPackagesTable(List<Package> Packages)
|
||||||
|
{
|
||||||
|
Table PackageTable = new Table()
|
||||||
|
.Centered()
|
||||||
|
.Expand()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.Title("Packages");
|
||||||
|
|
||||||
|
AnsiConsole.Live(PackageTable)
|
||||||
|
.AutoClear(false)
|
||||||
|
.Start(context =>
|
||||||
|
{
|
||||||
|
PackageTable
|
||||||
|
.AddColumn("[bold]PackageName[/]")
|
||||||
|
.AddColumn("[bold]Version[/]")
|
||||||
|
.AddColumn("[bold]Size (bytes)[/]")
|
||||||
|
.AddColumn("[bold]SHA256[/]")
|
||||||
|
.AddColumn("[bold]Filename[/]");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(500);
|
||||||
|
|
||||||
|
foreach (var pack in Packages)
|
||||||
|
{
|
||||||
|
PackageTable.AddRow(
|
||||||
|
FormatPackageName(pack.PackageName),
|
||||||
|
$"[i blue]{pack.Version.ToString()}[/]",
|
||||||
|
$"[grey]{pack.Size.ToString()}[/][b]B[/]",
|
||||||
|
pack.SHA256,
|
||||||
|
pack.Filename);
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static string FormatPackageName(string name)
|
||||||
|
{
|
||||||
|
if (name.Contains("dev"))
|
||||||
|
{
|
||||||
|
return $"[red]{name}[/]";
|
||||||
|
}
|
||||||
|
if (name.Contains("beta"))
|
||||||
|
{
|
||||||
|
return $"[yellow]{name}[/]";
|
||||||
|
}
|
||||||
|
if (name.Contains("stable"))
|
||||||
|
{
|
||||||
|
return $"[green]{name}[/]";
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void RenderReleaseTable(ReleaseData data)
|
||||||
|
{
|
||||||
|
// Lets output this ReleaseData now
|
||||||
|
Table ReleaseTable = new Table()
|
||||||
|
.Centered()
|
||||||
|
.Border(TableBorder.Rounded)
|
||||||
|
.Expand()
|
||||||
|
.Title("Release Data");
|
||||||
|
|
||||||
|
AnsiConsole.Live(ReleaseTable)
|
||||||
|
.AutoClear(false)
|
||||||
|
.Start(context =>
|
||||||
|
{
|
||||||
|
ReleaseTable.AddColumn("[bold]Field[/]");
|
||||||
|
ReleaseTable.AddColumn("[bold]Value[/]");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(500);
|
||||||
|
|
||||||
|
ReleaseTable.AddRow("[bold yellow]Date:[/]", $"[blue]{data.Date:R}[/]");
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(500);
|
||||||
|
|
||||||
|
Table archs = new Table()
|
||||||
|
.Centered()
|
||||||
|
.Border(TableBorder.None)
|
||||||
|
.HideFooters()
|
||||||
|
.HideHeaders()
|
||||||
|
.AddColumn(new TableColumn(Text.Empty));
|
||||||
|
ReleaseTable.AddRow(new Markup("[bold yellow]Architectures:[/]"), archs);
|
||||||
|
foreach (var arch in data.Architectures)
|
||||||
|
{
|
||||||
|
archs.AddRow(arch);
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
Table PackageFilesTable = new Table()
|
||||||
|
.Centered()
|
||||||
|
.Border(TableBorder.Square)
|
||||||
|
.BorderColor(Color.Red)
|
||||||
|
.AddColumn("[bold]Filename[/]")
|
||||||
|
.AddColumn("[bold]Checksum[/]")
|
||||||
|
.AddColumn("[bold]Size[/]");
|
||||||
|
ReleaseTable.AddRow(
|
||||||
|
new Markup("[bold yellow]Package Files:[/]"),
|
||||||
|
PackageFilesTable);
|
||||||
|
context.Refresh();
|
||||||
|
foreach (var PackageFile in data.PackageFiles)
|
||||||
|
{
|
||||||
|
PackageFilesTable.AddRow(PackageFile.filename, PackageFile.Checksum, PackageFile.size.ToString());
|
||||||
|
context.Refresh();
|
||||||
|
Thread.Sleep(250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
405
Program.cs
405
Program.cs
|
|
@ -1,15 +1,22 @@
|
||||||
//using System.CommandLine;
|
//using System.CommandLine;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace EdgeInstaller;
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
public static class EdgeInstall
|
public static partial class EdgeInstall
|
||||||
{
|
{
|
||||||
|
// NOTE: tweaking these values can be used to make downloads look more "fancy"
|
||||||
|
// A.K.A make downloads take longer so the progress bar appears for a little longer
|
||||||
|
private static readonly int downloadDelayMS = 0;
|
||||||
|
private static readonly int downloadBufferBYTES = 10240;
|
||||||
|
|
||||||
|
// The default architecture to get
|
||||||
|
// likely will go unused until proper cli interface is added
|
||||||
|
private static readonly string _DefaultArch = "amd64";
|
||||||
|
|
||||||
private static HttpClient _client = new HttpClient();
|
private static HttpClient _client = new HttpClient();
|
||||||
|
|
||||||
private static readonly string DataDir =
|
private static readonly string DataDir =
|
||||||
|
|
@ -18,23 +25,19 @@ public static class EdgeInstall
|
||||||
"ClosedLess",
|
"ClosedLess",
|
||||||
"EdgeInstall-Jankmode");
|
"EdgeInstall-Jankmode");
|
||||||
|
|
||||||
|
|
||||||
public static async Task<int> Main()
|
public static async Task<int> Main()
|
||||||
{
|
{
|
||||||
// WARN: Hardcoded variables are bad m'kay...
|
_client.BaseAddress = new Uri("https://packages.microsoft.com/repos/edge/");
|
||||||
string Arch = "amd64";
|
|
||||||
|
|
||||||
// Ensure DataDir exists.
|
// Ensure DataDir exists.
|
||||||
// NOTE: Directory.CreateDirectory will ALWAYS create the entire path
|
// NOTE: Directory.CreateDirectory will ALWAYS create the entire path
|
||||||
Directory.CreateDirectory(DataDir);
|
Directory.CreateDirectory(DataDir);
|
||||||
|
|
||||||
// TODO: wrap this code in foot detection since foot isn't detected properly
|
BUGFIX_ansiDetection();
|
||||||
// BUG: Report detection error to Spectre.Console (foot == xterm)
|
|
||||||
// HACK: We have to force this right now for foot to work properly...
|
|
||||||
AnsiConsole.Console.Profile.Capabilities.Ansi = true;
|
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.Debug()
|
.MinimumLevel.Debug()
|
||||||
.WriteTo.File(".EdgeInstaller.Log") // Only ever log to the logfile
|
.WriteTo.File(Path.Combine(DataDir, ".EdgeInstaller.Log")) // Only ever log to the logfile
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
var Release = AnsiConsole.Status()
|
var Release = AnsiConsole.Status()
|
||||||
|
|
@ -49,26 +52,26 @@ public static class EdgeInstall
|
||||||
Log.Debug("{rs}", ReleaseString);
|
Log.Debug("{rs}", ReleaseString);
|
||||||
|
|
||||||
AnsiConsole.MarkupLine("[green]Attempting to decode release data now[/]");
|
AnsiConsole.MarkupLine("[green]Attempting to decode release data now[/]");
|
||||||
ReleaseData data = getReleaseData(ReleaseString);
|
ReleaseData data = new(ReleaseString);
|
||||||
|
|
||||||
RenderReleaseTable(data);
|
RenderReleaseTable(data);
|
||||||
|
|
||||||
Arch = AnsiConsole.Prompt(
|
// only select arch if needed
|
||||||
new SelectionPrompt<string>()
|
string TargetArch = data.Architectures.Count > 1 ? AnsiConsole.Prompt(
|
||||||
.Title("Choose Architecture")
|
new SelectionPrompt<string>()
|
||||||
.PageSize(10)
|
.Title("Choose Architecture")
|
||||||
.MoreChoicesText("[grey]Scroll for more...[/]")
|
.PageSize(5)
|
||||||
.AddChoices(data.Architectures)
|
.MoreChoicesText("[grey]Scroll for more...[/]")
|
||||||
);
|
.AddChoices(data.Architectures)
|
||||||
|
) : data.Architectures.First();
|
||||||
|
|
||||||
// create a directory for the chosen architecture
|
// create a directory for the chosen architecture
|
||||||
// NOTE: we also grab the path here so we can use it later.
|
// NOTE: we also grab the path here so we can use it later.
|
||||||
var Archdir = Directory.CreateDirectory(Path.Combine(DataDir, Arch)).FullName;
|
var Archdir = Directory.CreateDirectory(Path.Combine(DataDir, TargetArch)).FullName;
|
||||||
|
|
||||||
// Check if the Release data we just grabbed is newer than the one that exists.
|
// Check if the Release data we just grabbed is newer than the one that exists.
|
||||||
// NOTE: we only do this if our chosen architecture hasn't been downloaded
|
// NOTE: we only do this if our chosen architecture hasn't been downloaded
|
||||||
|
|
||||||
// OOF semaphores..
|
|
||||||
bool NeedDownload = true;
|
bool NeedDownload = true;
|
||||||
|
|
||||||
Log.Information("Reached Package download Stage");
|
Log.Information("Reached Package download Stage");
|
||||||
|
|
@ -98,34 +101,34 @@ public static class EdgeInstall
|
||||||
.Where(
|
.Where(
|
||||||
file =>
|
file =>
|
||||||
file.filename.Contains(".gz") &&
|
file.filename.Contains(".gz") &&
|
||||||
file.filename.Contains(Arch)) // ensure we only fetch for the chosen architecture
|
file.filename.Contains(TargetArch)) // ensure we only fetch for the chosen architecture
|
||||||
.First();
|
.First();
|
||||||
if (gzFile is null)
|
if (gzFile is null)
|
||||||
{
|
{
|
||||||
throw new NullReferenceException("OOPS gzfile null");
|
throw new NullReferenceException("OOPS gzfile null");
|
||||||
}
|
}
|
||||||
string URL = "http://packages.microsoft.com/repos/edge/dists/stable/" + gzFile.filename;
|
string URL = "dists/stable/" + gzFile.filename;
|
||||||
|
|
||||||
string filename = await AnsiConsole.Progress()
|
string filename = await AnsiConsole.Progress()
|
||||||
.Columns(new ProgressColumn[]
|
.Columns(new ProgressColumn[]
|
||||||
{
|
{
|
||||||
new TaskDescriptionColumn(),
|
new TaskDescriptionColumn(),
|
||||||
new ProgressBarColumn(),
|
new ProgressBarColumn(),
|
||||||
new PercentageColumn(),
|
new PercentageColumn(),
|
||||||
new RemainingTimeColumn(),
|
new RemainingTimeColumn(),
|
||||||
new SpinnerColumn(),
|
new SpinnerColumn(),
|
||||||
})
|
})
|
||||||
.StartAsync<string>(async ctx =>
|
.StartAsync<string>(async ctx =>
|
||||||
{
|
{
|
||||||
var task = ctx.AddTask(URL, new ProgressTaskSettings { AutoStart = false });
|
var task = ctx.AddTask(URL, new ProgressTaskSettings { AutoStart = false });
|
||||||
|
|
||||||
return await downloadFile(URL, Archdir, task);
|
return await downloadFile(URL, Archdir, task);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
string JSONdata = JsonSerializer.Serialize<ReleaseData>(data);
|
string JSONdata = JsonSerializer.Serialize<ReleaseData>(data);
|
||||||
File.WriteAllText(Path.Combine(DataDir, "release.json"), JSONdata);
|
File.WriteAllText(Path.Combine(DataDir, "release.json"), JSONdata);
|
||||||
if (!ValidateChecksum(filename, gzFile.Checksum))
|
if (!ValidateChecksum(new FileInfo(filename), gzFile.Checksum))
|
||||||
{
|
{
|
||||||
File.Delete(filename);
|
File.Delete(filename);
|
||||||
Environment.Exit(1);
|
Environment.Exit(1);
|
||||||
|
|
@ -145,6 +148,10 @@ public static class EdgeInstall
|
||||||
if (line == "")
|
if (line == "")
|
||||||
{
|
{
|
||||||
string PackageString = sb.ToString();
|
string PackageString = sb.ToString();
|
||||||
|
if (PackageString == "")
|
||||||
|
{
|
||||||
|
continue; // The last "package" will be empty
|
||||||
|
}
|
||||||
sb.Clear(); // ensure we clear the StringBuilder when we are done with it.
|
sb.Clear(); // ensure we clear the StringBuilder when we are done with it.
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -171,15 +178,15 @@ public static class EdgeInstall
|
||||||
.ThenByDescending(package => package.Version).ToList();
|
.ThenByDescending(package => package.Version).ToList();
|
||||||
// Print any packages we have
|
// Print any packages we have
|
||||||
var Variant = AnsiConsole.Prompt(
|
var Variant = AnsiConsole.Prompt(
|
||||||
new SelectionPrompt<string>()
|
new SelectionPrompt<string>()
|
||||||
.Title("Choose variant")
|
.Title("Choose variant")
|
||||||
.PageSize(4)
|
.PageSize(4)
|
||||||
.AddChoices(new[] {
|
.AddChoices(new[] {
|
||||||
"stable",
|
"stable",
|
||||||
"beta",
|
"beta",
|
||||||
"dev",
|
"dev",
|
||||||
"all"
|
"all"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -191,19 +198,19 @@ public static class EdgeInstall
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
VariantPackages = packages
|
VariantPackages = packages
|
||||||
.Where(package =>
|
.Where(package =>
|
||||||
package.PackageName.Contains(Variant))
|
package.PackageName.Contains(Variant))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderPackagesTable(VariantPackages);
|
RenderPackagesTable(VariantPackages);
|
||||||
|
|
||||||
|
|
||||||
var Package = AnsiConsole.Prompt(
|
var Package = AnsiConsole.Prompt(
|
||||||
new SelectionPrompt<string>()
|
new SelectionPrompt<string>()
|
||||||
.Title("Choose Package to Install")
|
.Title("Choose Package to Install")
|
||||||
.PageSize(5)
|
.PageSize(5)
|
||||||
.AddChoices(VariantPackages.ConvertAll(pack => pack.PackageName).Distinct())
|
.AddChoices(VariantPackages.ConvertAll(pack => pack.PackageName).Distinct())
|
||||||
);
|
);
|
||||||
|
|
||||||
var ChosenPackages = packages
|
var ChosenPackages = packages
|
||||||
|
|
@ -215,261 +222,77 @@ public static class EdgeInstall
|
||||||
|
|
||||||
RenderPackageAsTable(LatestVersion);
|
RenderPackageAsTable(LatestVersion);
|
||||||
|
|
||||||
return 0;
|
var versionPath = LatestVersion.Filename
|
||||||
}
|
.Replace("pool/main/m/", "")
|
||||||
|
.Replace(".deb", "");
|
||||||
|
|
||||||
static void RenderPackageAsTable(Package package)
|
var versionsDir = Path.Join(Archdir, "Versions");
|
||||||
{
|
// Let's get and unpack the latest version
|
||||||
Table Package = new Table()
|
|
||||||
.Centered()
|
|
||||||
.Border(TableBorder.Rounded)
|
|
||||||
.HideHeaders()
|
|
||||||
.Title("Package To Install");
|
|
||||||
|
|
||||||
AnsiConsole.Live(Package)
|
if (true)//!Directory.Exists(Path.Join(versionsDir, versionPath)))
|
||||||
.AutoClear(false)
|
|
||||||
.Start(context =>
|
|
||||||
{
|
|
||||||
Package.AddColumns("", "");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(250);
|
|
||||||
|
|
||||||
Package.AddRow("[bold yellow]Package Name : [/]", $"{package.PackageName}");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
Package.AddRow("[bold yellow]Version : [/]", $"{package.Version.ToString()}");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
Package.AddRow("[bold yellow]Size : [/]", $"{package.Size.ToString()}B");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
Package.AddRow("[bold yellow]SHA256 : [/]", $"{package.SHA256}");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
Package.AddRow("[bold yellow]Filename : [/]", $"{package.Filename}");
|
|
||||||
context.Refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RenderPackagesTable(List<Package> Packages)
|
|
||||||
{
|
|
||||||
Table PackageTable = new Table()
|
|
||||||
.Centered()
|
|
||||||
.Expand()
|
|
||||||
.Border(TableBorder.Rounded)
|
|
||||||
.Title("Packages");
|
|
||||||
|
|
||||||
AnsiConsole.Live(PackageTable)
|
|
||||||
.AutoClear(false)
|
|
||||||
.Start(context =>
|
|
||||||
{
|
|
||||||
PackageTable
|
|
||||||
.AddColumn("[bold]PackageName[/]")
|
|
||||||
.AddColumn("[bold]Version[/]")
|
|
||||||
.AddColumn("[bold]Size (bytes)[/]")
|
|
||||||
.AddColumn("[bold]SHA256[/]")
|
|
||||||
.AddColumn("[bold]Filename[/]");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
foreach (var pack in Packages)
|
|
||||||
{
|
|
||||||
PackageTable.AddRow(FormatPackageName(pack.PackageName), $"[i blue]{pack.Version.ToString()}[/]", $"[grey]{pack.Size.ToString()}[/][b]B[/]", pack.SHA256, pack.Filename);
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static string FormatPackageName(string name)
|
|
||||||
{
|
|
||||||
if (name.Contains("dev"))
|
|
||||||
{
|
{
|
||||||
return $"[red]{name}[/]";
|
using (MemoryStream debStream =
|
||||||
}
|
await AnsiConsole.Progress()
|
||||||
if (name.Contains("beta"))
|
.Columns(new ProgressColumn[]
|
||||||
{
|
{
|
||||||
return $"[yellow]{name}[/]";
|
new TaskDescriptionColumn(),
|
||||||
}
|
new ProgressBarColumn(),
|
||||||
if (name.Contains("stable"))
|
new PercentageColumn(),
|
||||||
{
|
new RemainingTimeColumn(),
|
||||||
return $"[green]{name}[/]";
|
new SpinnerColumn(),
|
||||||
}
|
})
|
||||||
return name;
|
.StartAsync<MemoryStream>(async ctx =>
|
||||||
}
|
|
||||||
|
|
||||||
static async Task<string> GetRelease()
|
|
||||||
{
|
|
||||||
string releaseURL = "http://packages.microsoft.com/repos/edge/dists/stable/Release";
|
|
||||||
using HttpResponseMessage response = await _client.GetAsync(releaseURL);
|
|
||||||
await Task.Delay(5000);
|
|
||||||
response.EnsureSuccessStatusCode(); // WARNING: this can throw an exception, Please wrap in try/catch or rewrite function
|
|
||||||
|
|
||||||
var body = await response.Content.ReadAsStringAsync();
|
|
||||||
Log.Debug("{body}", body);
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ReleaseData getReleaseData(string release)
|
|
||||||
{
|
|
||||||
return new ReleaseData(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async Task<string> downloadFile(string URL, string DestinationDirectory)
|
|
||||||
{
|
|
||||||
|
|
||||||
Uri file = new Uri(URL);
|
|
||||||
|
|
||||||
string fileLocation = Path.Combine(DestinationDirectory, file.Segments.Last());
|
|
||||||
|
|
||||||
using HttpResponseMessage response = await _client.GetAsync(URL);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
await Task.Delay(2000);
|
|
||||||
|
|
||||||
var fileData = response.Content.ReadAsStreamAsync();
|
|
||||||
|
|
||||||
using var fileStream = new FileStream(fileLocation, FileMode.OpenOrCreate);
|
|
||||||
await response.Content.CopyToAsync(fileStream);
|
|
||||||
|
|
||||||
return fileLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async Task<string> downloadFile(string URL, string DestinationDirectory, ProgressTask task)
|
|
||||||
{
|
|
||||||
Uri file = new Uri(URL);
|
|
||||||
string fileLocation = Path.Combine(DestinationDirectory, file.Segments.Last());
|
|
||||||
|
|
||||||
using (HttpResponseMessage response = await _client.GetAsync(URL, HttpCompletionOption.ResponseHeadersRead))
|
|
||||||
{
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
|
|
||||||
task.MaxValue = response.Content.Headers.ContentLength ?? 0;
|
|
||||||
task.StartTask();
|
|
||||||
await Task.Delay(2000);
|
|
||||||
|
|
||||||
AnsiConsole.MarkupLineInterpolated($"Starting download of [u]{URL}[/] ({task.MaxValue} Bytes)");
|
|
||||||
|
|
||||||
using (var contentStream = await response.Content.ReadAsStreamAsync())
|
|
||||||
using (var fileStream = File.Open(fileLocation, FileMode.OpenOrCreate))
|
|
||||||
{
|
|
||||||
var buffer = new byte[512];
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
|
|
||||||
await Task.Delay(500);
|
|
||||||
if (read == 0)
|
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLineInterpolated($"Download of [u]{URL}[/] [green b]Complete![/]");
|
var task = ctx.AddTask(LatestVersion.Filename, new ProgressTaskSettings { AutoStart = false });
|
||||||
break;
|
|
||||||
|
return await downloadStream(LatestVersion.Filename, task);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!ValidateChecksum(debStream, LatestVersion.SHA256))
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[red b]ERROR:[/] checksum did not validate");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (DebFile debfile = new DebFile(debStream))
|
||||||
|
{
|
||||||
|
foreach (var fileEntry in debfile.fileEntries)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"FileName: {fileEntry.name}");
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"FilePos: {fileEntry.offset}");
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"FileSize: {fileEntry.sizeBytes}");
|
||||||
|
|
||||||
|
// NOTE: This is dumb test code
|
||||||
|
// to verify the files are actually being "extracted" properly
|
||||||
|
|
||||||
|
if (!debfile.getFile(fileEntry, out Stream contents))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we write this to tmp.
|
||||||
|
// WARN: Without deleting these you will fill up your ram
|
||||||
|
|
||||||
|
var temp = Path.GetTempFileName();
|
||||||
|
|
||||||
|
using (var tmpfile = File.Open(temp, FileMode.Open))
|
||||||
|
{
|
||||||
|
contents.CopyTo(tmpfile);
|
||||||
|
contents.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
task.Increment(read);
|
|
||||||
|
|
||||||
await fileStream.WriteAsync(buffer, 0, read);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return fileLocation;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
static bool ValidateChecksum(string path, string checksum)
|
|
||||||
{
|
|
||||||
using (SHA256 mySHA256 = SHA256.Create())
|
|
||||||
{
|
|
||||||
using (FileStream fs = File.Open(path, FileMode.Open))
|
|
||||||
{
|
|
||||||
fs.Position = 0;
|
|
||||||
|
|
||||||
byte[] hashValue = mySHA256.ComputeHash(fs);
|
|
||||||
if (hashValue is null)
|
|
||||||
{
|
|
||||||
throw new NullReferenceException("Hash be null??");
|
|
||||||
}
|
|
||||||
Log.Debug("Computed hash = {hash}", hashValue);
|
|
||||||
byte[] storedHash = Convert.FromHexString(checksum);
|
|
||||||
Log.Debug("Stored Hash = {hash}", storedHash);
|
|
||||||
|
|
||||||
Log.Debug("Compare result = {result}", hashValue.SequenceEqual(storedHash));
|
|
||||||
return (storedHash.SequenceEqual(hashValue));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RenderReleaseTable(ReleaseData data)
|
|
||||||
{
|
|
||||||
// Lets output this ReleaseData now
|
|
||||||
Table ReleaseTable = new Table()
|
|
||||||
.Centered()
|
|
||||||
.Border(TableBorder.Rounded)
|
|
||||||
.Expand()
|
|
||||||
.Title("Release Data");
|
|
||||||
|
|
||||||
AnsiConsole.Live(ReleaseTable)
|
|
||||||
.AutoClear(false)
|
|
||||||
.Start(context =>
|
|
||||||
{
|
|
||||||
ReleaseTable.AddColumn("[bold]Field[/]");
|
|
||||||
ReleaseTable.AddColumn("[bold]Value[/]");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
ReleaseTable.AddRow("[bold yellow]Date:[/]", $"[blue]{data.Date:R}[/]");
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
Table archs = new Table()
|
|
||||||
.Centered()
|
|
||||||
.Border(TableBorder.None)
|
|
||||||
.HideFooters()
|
|
||||||
.HideHeaders()
|
|
||||||
.AddColumn(new TableColumn(Text.Empty));
|
|
||||||
ReleaseTable.AddRow(new Markup("[bold yellow]Architectures:[/]"), archs);
|
|
||||||
foreach (var arch in data.Architectures)
|
|
||||||
{
|
|
||||||
archs.AddRow(arch);
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(250);
|
|
||||||
}
|
|
||||||
|
|
||||||
Table PackageFilesTable = new Table()
|
|
||||||
.Centered()
|
|
||||||
.Border(TableBorder.Square)
|
|
||||||
.BorderColor(Color.Red)
|
|
||||||
.AddColumn("[bold]Filename[/]")
|
|
||||||
.AddColumn("[bold]Checksum[/]")
|
|
||||||
.AddColumn("[bold]Size[/]");
|
|
||||||
ReleaseTable.AddRow(
|
|
||||||
new Markup("[bold yellow]Package Files:[/]"),
|
|
||||||
PackageFilesTable);
|
|
||||||
context.Refresh();
|
|
||||||
foreach (var PackageFile in data.PackageFiles)
|
|
||||||
{
|
|
||||||
PackageFilesTable.AddRow(PackageFile.filename, PackageFile.Checksum, PackageFile.size.ToString());
|
|
||||||
context.Refresh();
|
|
||||||
Thread.Sleep(250);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetPackagesString(string filepath)
|
|
||||||
{
|
|
||||||
using (var CompressedStream = File.Open(filepath, FileMode.Open))
|
|
||||||
using (var decompressor = new GZipStream(CompressedStream, CompressionMode.Decompress))
|
|
||||||
using (var streamReader = new StreamReader(decompressor))
|
|
||||||
{
|
|
||||||
return streamReader.ReadToEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*TODO:
|
/*TODO:
|
||||||
- Parse package to find latest version
|
- Check version
|
||||||
- Check version
|
- Offer update
|
||||||
- Offer update
|
- Make SENSIBLE backup
|
||||||
- Make SENSIBLE backup
|
- Do install
|
||||||
- Do install
|
= if install fails revert backup
|
||||||
= if install fails revert backup
|
*/
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
ReleaseData/ReleaseData.JsonConstructor.cs
Normal file
13
ReleaseData/ReleaseData.JsonConstructor.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public sealed partial class ReleaseData
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
public ReleaseData(DateTime date, List<string> architectures, List<PackageFile> packageFiles)
|
||||||
|
{
|
||||||
|
this.Date = date;
|
||||||
|
this.Architectures = architectures;
|
||||||
|
this.PackageFiles = packageFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,22 +2,15 @@
|
||||||
// Substring should be replaced with a .split(": ")
|
// Substring should be replaced with a .split(": ")
|
||||||
// Also Directly assign to the values instead of using temporary variables.
|
// Also Directly assign to the values instead of using temporary variables.
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
namespace EdgeInstaller;
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
sealed class ReleaseData
|
public sealed partial class ReleaseData
|
||||||
{
|
{
|
||||||
public DateTime Date { get; private set; }
|
public DateTime Date { get; private set; }
|
||||||
public List<string> Architectures { get; private set; }
|
public List<string> Architectures { get; private set; }
|
||||||
public List<PackageFile> PackageFiles { get; private set; }
|
public List<PackageFile> PackageFiles { get; private set; }
|
||||||
|
|
||||||
[JsonConstructor]
|
|
||||||
public ReleaseData(DateTime date, List<string> architectures, List<PackageFile> packageFiles)
|
|
||||||
{
|
|
||||||
this.Date = date;
|
|
||||||
this.Architectures = architectures;
|
|
||||||
this.PackageFiles = packageFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReleaseData(string Release)
|
public ReleaseData(string Release)
|
||||||
{
|
{
|
||||||
|
|
@ -30,31 +23,19 @@ sealed class ReleaseData
|
||||||
paramName: "Release");
|
paramName: "Release");
|
||||||
}
|
}
|
||||||
|
|
||||||
Architectures = new();
|
|
||||||
PackageFiles = new();
|
|
||||||
|
|
||||||
// Pull release apart
|
// Pull release apart
|
||||||
List<string> ReleaseLines = Release.Split("\n", StringSplitOptions.RemoveEmptyEntries).ToList();
|
List<string> ReleaseLines = Release.Split("\n", StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
|
||||||
Log.Debug("ReleaseLines = {@releaselines}", ReleaseLines);
|
Log.Debug("ReleaseLines = {@releaselines}", ReleaseLines);
|
||||||
|
|
||||||
// Safely find and parse the Date field
|
Date = DateTime.Parse(
|
||||||
string DateLine =
|
ReleaseLines.Where(
|
||||||
ReleaseLines.Where(
|
|
||||||
Line => Line.Contains("Date:"))
|
Line => Line.Contains("Date:"))
|
||||||
.FirstOrDefault("").Substring(5);
|
.First()
|
||||||
Log.Debug("DateLine = {DateLine}", DateLine);
|
.Split(": ")
|
||||||
|
.Last()
|
||||||
|
);
|
||||||
if (DateTime.TryParse(DateLine, out DateTime result))
|
|
||||||
{
|
|
||||||
Log.Debug("Extracted date {@date}", result);
|
|
||||||
Date = result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw _lineFormatException("date");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safely find and add the possible architectures
|
// Safely find and add the possible architectures
|
||||||
string ArchitecturesLine =
|
string ArchitecturesLine =
|
||||||
|
|
@ -67,19 +48,25 @@ sealed class ReleaseData
|
||||||
throw _lineFormatException("architectures");
|
throw _lineFormatException("architectures");
|
||||||
}
|
}
|
||||||
Architectures = ArchitecturesLine
|
Architectures = ArchitecturesLine
|
||||||
.Substring(ArchitecturesLine.IndexOf(":") + 1) // We only want items after the label
|
.Split(": ").Last() // We only want items after the label
|
||||||
.Split(" ", StringSplitOptions.RemoveEmptyEntries) // Space delimited list
|
.Split(" ", StringSplitOptions.RemoveEmptyEntries)
|
||||||
.ToList(); // Convert to the superior collection type
|
.ToList();
|
||||||
Log.Debug("Extracted architectures {@architectures}", Architectures);
|
Log.Debug("Extracted architectures {@architectures}", Architectures);
|
||||||
|
|
||||||
// Time to process the package files.. This is gonna be fun..
|
|
||||||
// WARNING: this code is a little hacky
|
/*
|
||||||
// scope for possible optimisation
|
* 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
|
// First split the Lines to only get the file data
|
||||||
List<string> SHA256FileData = ReleaseLines.Where((value, index) =>
|
// NOTE: This is probably the cleanest way to do this.
|
||||||
index > ReleaseLines.IndexOf("SHA256:") && index < ReleaseLines.IndexOf("SHA512:")
|
List<string> SHA256FileData = ReleaseLines.Where((_, index) =>
|
||||||
).ToList();
|
index > ReleaseLines.IndexOf("SHA256:") &&
|
||||||
|
index < ReleaseLines.IndexOf("SHA512:")
|
||||||
|
).ToList();
|
||||||
Log.Debug("SHA256FileData = {@data}", SHA256FileData);
|
Log.Debug("SHA256FileData = {@data}", SHA256FileData);
|
||||||
|
|
||||||
// Now process these lines to get the data we need
|
// Now process these lines to get the data we need
|
||||||
|
|
@ -96,16 +83,19 @@ sealed class ReleaseData
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any empty files (and the .gz version of them)
|
// remove any empty files (and the .gz version of them)
|
||||||
// NOTE: I do this since empty gz files have a size
|
// 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
|
// 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();
|
var emptyFiles = releasePackageFiles
|
||||||
|
.Where(package => package.size == 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var file in emptyFiles)
|
foreach (var file in emptyFiles)
|
||||||
{
|
{
|
||||||
releasePackageFiles.RemoveAll(package =>
|
releasePackageFiles
|
||||||
|
.RemoveAll(package =>
|
||||||
package.filename == file.filename ||
|
package.filename == file.filename ||
|
||||||
package.filename == $"{file.filename}.gz"
|
package.filename == $"{file.filename}.gz"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageFiles = releasePackageFiles;
|
PackageFiles = releasePackageFiles;
|
||||||
|
|
@ -113,7 +103,11 @@ sealed class ReleaseData
|
||||||
// After removing the empty files we need to make sure that we remove the architectures that don't have any files
|
// 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())
|
foreach (var arch in Architectures.ToList())
|
||||||
// WARN: `.ToList` is used again to copy the list so we can iterate and operate
|
/*
|
||||||
|
* 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(arch)).Count() == 0)
|
if (PackageFiles.Where(file => file.filename.Contains(arch)).Count() == 0)
|
||||||
{
|
{
|
||||||
127
Version.cs
127
Version.cs
|
|
@ -1,127 +0,0 @@
|
||||||
using Serilog;
|
|
||||||
// Make a version since it is so much easier to compare when the class handles it.
|
|
||||||
|
|
||||||
sealed class Version : IComparable<Version>, IEquatable<Version>
|
|
||||||
{
|
|
||||||
public int Major { get; private set; }
|
|
||||||
public int Minor { get; private set; }
|
|
||||||
public int Build { get; private set; }
|
|
||||||
public int Patch { get; private set; }
|
|
||||||
public int Subpatch { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
public Version(string versionString)
|
|
||||||
{
|
|
||||||
Log.Information("Creating version from string {versionString}", versionString);
|
|
||||||
if (versionString == "")
|
|
||||||
{
|
|
||||||
throw new ArgumentException("The passed version string was empty", "versionString");
|
|
||||||
}
|
|
||||||
|
|
||||||
var splitVersion = versionString.Split('.');
|
|
||||||
Log.Debug("SplitVersion = {@splitversion}", splitVersion);
|
|
||||||
Major = int.Parse(splitVersion[0]);
|
|
||||||
Minor = int.Parse(splitVersion[1]);
|
|
||||||
Build = int.Parse(splitVersion[2]);
|
|
||||||
|
|
||||||
var splitPatch = splitVersion[3].Split('-');
|
|
||||||
Log.Debug("SplitPatch = {@splitPatch}", splitPatch);
|
|
||||||
Log.Debug("SplitPatch values : [0] = {0}, [1] = {1}", splitPatch[0], splitPatch[1]);
|
|
||||||
Patch = int.Parse(splitPatch[0]);
|
|
||||||
Subpatch = int.Parse(splitPatch[1]);
|
|
||||||
Log.Debug("Version Created: {@version}", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Major}.{Minor}.{Build}.{Patch}-{Subpatch}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo(Version? other)
|
|
||||||
{
|
|
||||||
if (other is null)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// full Equality is easy since everything should be the same.
|
|
||||||
// OPTIM: Doing this here is techinally more effecient than baking it in to the later code
|
|
||||||
// Bools and shit
|
|
||||||
if (this.Major == other.Major &&
|
|
||||||
this.Minor == other.Minor &&
|
|
||||||
this.Build == other.Build &&
|
|
||||||
this.Patch == other.Patch &&
|
|
||||||
this.Subpatch == other.Subpatch)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare major version
|
|
||||||
if (Major != other.Major)
|
|
||||||
return Major.CompareTo(other.Major);
|
|
||||||
|
|
||||||
// Compare minor version
|
|
||||||
if (Minor != other.Minor)
|
|
||||||
return Minor.CompareTo(other.Minor);
|
|
||||||
|
|
||||||
// Compare build number
|
|
||||||
if (Build != other.Build)
|
|
||||||
return Build.CompareTo(other.Build);
|
|
||||||
|
|
||||||
// Compare patch number
|
|
||||||
if (Patch != other.Patch)
|
|
||||||
return Patch.CompareTo(other.Patch);
|
|
||||||
|
|
||||||
// Compare subpatch number
|
|
||||||
return Subpatch.CompareTo(other.Subpatch);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is null || !this.GetType().Equals(obj.GetType()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Equals(obj);
|
|
||||||
// NOTE: For the purposes of a version number same values == same version
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return Major + Minor + Build + Patch + Subpatch;
|
|
||||||
// NOTE: For the purposes of a version number same values == same version
|
|
||||||
// That is why this "hash" code will collide given two "different" versions with the same values
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(Version? other)
|
|
||||||
{
|
|
||||||
if (other is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.CompareTo(other) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator >(Version v1, Version v2)
|
|
||||||
{
|
|
||||||
return v1.CompareTo(v2) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator <(Version v1, Version v2)
|
|
||||||
{
|
|
||||||
return v1.CompareTo(v2) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static bool operator >=(Version v1, Version v2)
|
|
||||||
{
|
|
||||||
return v1.CompareTo(v2) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator <=(Version v1, Version v2)
|
|
||||||
{
|
|
||||||
return v1.CompareTo(v2) <= 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
75
Version/Version.IComparable.cs
Normal file
75
Version/Version.IComparable.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public sealed partial class Version : IComparable<Version>
|
||||||
|
{
|
||||||
|
public int CompareTo(Version? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// full Equality is easy since everything should be the same.
|
||||||
|
// OPTIM: Doing this here is techinally more effecient than baking it in to the later code
|
||||||
|
// Bools and shit
|
||||||
|
if (this.Major == other.Major &&
|
||||||
|
this.Minor == other.Minor &&
|
||||||
|
this.Build == other.Build &&
|
||||||
|
this.Patch == other.Patch &&
|
||||||
|
this.Subpatch == other.Subpatch)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare major version
|
||||||
|
if (Major != other.Major)
|
||||||
|
return Major.CompareTo(other.Major);
|
||||||
|
|
||||||
|
// Compare minor version
|
||||||
|
if (Minor != other.Minor)
|
||||||
|
return Minor.CompareTo(other.Minor);
|
||||||
|
|
||||||
|
// Compare build number
|
||||||
|
if (Build != other.Build)
|
||||||
|
return Build.CompareTo(other.Build);
|
||||||
|
|
||||||
|
// Compare patch number
|
||||||
|
if (Patch != other.Patch)
|
||||||
|
return Patch.CompareTo(other.Patch);
|
||||||
|
|
||||||
|
// Compare subpatch number
|
||||||
|
return Subpatch.CompareTo(other.Subpatch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(Version? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.CompareTo(other) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >(Version v1, Version v2)
|
||||||
|
{
|
||||||
|
return v1.CompareTo(v2) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <(Version v1, Version v2)
|
||||||
|
{
|
||||||
|
return v1.CompareTo(v2) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool operator >=(Version v1, Version v2)
|
||||||
|
{
|
||||||
|
return v1.CompareTo(v2) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <=(Version v1, Version v2)
|
||||||
|
{
|
||||||
|
return v1.CompareTo(v2) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
Version/Version.IEquatable.cs
Normal file
22
Version/Version.IEquatable.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
public sealed partial class Version : IEquatable<Version>
|
||||||
|
{
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj is null || !this.GetType().Equals(obj.GetType()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Equals(obj);
|
||||||
|
// NOTE: For the purposes of a version number same values == same version
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Major + Minor + Build + Patch + Subpatch;
|
||||||
|
// NOTE: For the purposes of a version number same values == same version
|
||||||
|
// That is why this "hash" code will collide given two "different" versions with the same values
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Version/Version.cs
Normal file
41
Version/Version.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using Serilog;
|
||||||
|
// Make a version since it is so much easier to compare when the class handles it.
|
||||||
|
|
||||||
|
namespace EdgeInstaller;
|
||||||
|
|
||||||
|
sealed partial class Version : IEquatable<Version>
|
||||||
|
{
|
||||||
|
public int Major { get; private set; }
|
||||||
|
public int Minor { get; private set; }
|
||||||
|
public int Build { get; private set; }
|
||||||
|
public int Patch { get; private set; }
|
||||||
|
public int Subpatch { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
public Version(string versionString)
|
||||||
|
{
|
||||||
|
Log.Information("Creating version from string {versionString}", versionString);
|
||||||
|
if (versionString == "")
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The passed version string was empty", "versionString");
|
||||||
|
}
|
||||||
|
|
||||||
|
var splitVersion = versionString.Split('.');
|
||||||
|
Log.Debug("SplitVersion = {@splitversion}", splitVersion);
|
||||||
|
Major = int.Parse(splitVersion[0]);
|
||||||
|
Minor = int.Parse(splitVersion[1]);
|
||||||
|
Build = int.Parse(splitVersion[2]);
|
||||||
|
|
||||||
|
var splitPatch = splitVersion[3].Split('-');
|
||||||
|
Log.Debug("SplitPatch = {@splitPatch}", splitPatch);
|
||||||
|
Log.Debug("SplitPatch values : [0] = {0}, [1] = {1}", splitPatch[0], splitPatch[1]);
|
||||||
|
Patch = int.Parse(splitPatch[0]);
|
||||||
|
Subpatch = int.Parse(splitPatch[1]);
|
||||||
|
Log.Debug("Version Created: {@version}", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Major}.{Minor}.{Build}.{Patch}-{Subpatch}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CST.RecursiveExtractor" Version="1.2.13" />
|
||||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Spectre.Console" Version="0.47.0" />
|
<PackageReference Include="Spectre.Console" Version="0.47.0" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user