HELL COMMIT

Commit sins to add all code into repo
This commit is contained in:
Robert Morrison 2023-05-28 15:11:41 +01:00
commit f650e6a3c9
9 changed files with 39534 additions and 0 deletions

38168
.EdgeInstaller.Log Normal file

File diff suppressed because it is too large Load Diff

477
.gitignore vendored Normal file
View File

@ -0,0 +1,477 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk

60
DebArchive.cs Normal file
View File

@ -0,0 +1,60 @@
// 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
*/

66
Package.cs Normal file
View File

@ -0,0 +1,66 @@
using Serilog;
using System.Text.Json.Serialization;
// Defines a Package
sealed class Package
{
public string PackageName { get; private set; }
public Version Version { get; private set; }
public int Size { get; private set; }
public string SHA256 { 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)
{
Log.Information("Creating package from string");
Log.Debug("PackageString : \n{str}", packageString);
// we assume the user is passing only one package.
string[] packageLines = packageString.Split('\n');
Log.Debug("Split result : \n{@spl}", packageLines);
PackageName = packageLines
.Where(line => line.Contains("Package:"))
.First()
.Split(": ")
.Last();
Version = new Version(packageLines
.Where(line => line.Contains("Version:"))
.First()
.Split(": ")
.Last());
Size = int.Parse(
packageLines
.Where(line => line.Contains("Size:"))
.First()
.Split(": ")
.Last());
SHA256 = packageLines
.Where(line => line.Contains("SHA256:"))
.First()
.Split(": ")
.Last();
Filename = packageLines
.Where(line => line.Contains("Filename:"))
.First()
.Split(": ")
.Last();
}
}
/* TODO: capture dependencies and create method to find them.
- This would probably need distro specific lookup tables.
- Also would ideally need code to shell out (at root level)
to perform installs...
*/

475
Program.cs Normal file
View File

@ -0,0 +1,475 @@
//using System.CommandLine;
using Serilog;
using Spectre.Console;
using System.IO.Compression;
using System.Text.Json;
using System.Security.Cryptography;
using System.Text;
namespace EdgeInstaller;
public static class EdgeInstall
{
private static HttpClient _client = new HttpClient();
private static readonly string DataDir =
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"ClosedLess",
"EdgeInstall-Jankmode");
public static async Task<int> Main()
{
// WARN: Hardcoded variables are bad m'kay...
string Arch = "amd64";
// Ensure DataDir exists.
// NOTE: Directory.CreateDirectory will ALWAYS create the entire path
Directory.CreateDirectory(DataDir);
// TODO: wrap this code in foot detection since foot isn't detected properly
// 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()
.MinimumLevel.Debug()
.WriteTo.File(".EdgeInstaller.Log") // Only ever log to the logfile
.CreateLogger();
var Release = AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.SpinnerStyle(Style.Parse("green"))
.StartAsync(
"Getting Latest Release data",
_ => GetRelease()
);
string ReleaseString = await Release;
Log.Debug("{rs}", ReleaseString);
AnsiConsole.MarkupLine("[green]Attempting to decode release data now[/]");
ReleaseData data = getReleaseData(ReleaseString);
RenderReleaseTable(data);
Arch = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Choose Architecture")
.PageSize(10)
.MoreChoicesText("[grey]Scroll for more...[/]")
.AddChoices(data.Architectures)
);
// create a directory for the chosen architecture
// NOTE: we also grab the path here so we can use it later.
var Archdir = Directory.CreateDirectory(Path.Combine(DataDir, Arch)).FullName;
// 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
// OOF semaphores..
bool NeedDownload = true;
Log.Information("Reached Package download Stage");
if (File.Exists(Path.Combine(Archdir, "Packages.gz")))
{
Log.Information("Checking for release.json");
// we need to check the release since we have an existing package.gz
string JSONstring = File.ReadAllText(Path.Combine(DataDir, "release.json"));
ReleaseData? oldData = JsonSerializer.Deserialize<ReleaseData>(JSONstring);
if (oldData is not null)
{
// if equal then our data is the newest available
if (DateTime.Compare(oldData.Date, data.Date) == 0)
{
Log.Information("Our packages.gz is up to date");
NeedDownload = false;
}
}
}
if (NeedDownload)
{
PackageFile gzFile =
data.PackageFiles
.Where(
file =>
file.filename.Contains(".gz") &&
file.filename.Contains(Arch)) // ensure we only fetch for the chosen architecture
.First();
if (gzFile is null)
{
throw new NullReferenceException("OOPS gzfile null");
}
string URL = "http://packages.microsoft.com/repos/edge/dists/stable/" + gzFile.filename;
string filename = await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new RemainingTimeColumn(),
new SpinnerColumn(),
})
.StartAsync<string>(async ctx =>
{
var task = ctx.AddTask(URL, new ProgressTaskSettings { AutoStart = false });
return await downloadFile(URL, Archdir, task);
});
string JSONdata = JsonSerializer.Serialize<ReleaseData>(data);
File.WriteAllText(Path.Combine(DataDir, "release.json"), JSONdata);
if (!ValidateChecksum(filename, gzFile.Checksum))
{
File.Delete(filename);
Environment.Exit(1);
}
}
string PackagesFile = Path.Combine(Archdir, "Packages.gz");
string PackagesString = GetPackagesString(PackagesFile);
AnsiConsole.MarkupLineInterpolated($"Read {PackagesFile} see log for contents");
Log.Debug("{packagesString}", PackagesString);
List<Package> packages = new();
StringBuilder sb = new();
foreach (var line in PackagesString.Split('\n'))
{
if (line == "")
{
string PackageString = sb.ToString();
sb.Clear(); // ensure we clear the StringBuilder when we are done with it.
try
{ // Just in case we somehow get a broken Package
Package p = new Package(PackageString);
packages.Add(p);
}
catch (Exception e)
{
AnsiConsole.MarkupLine("[red]Error:[/] Could not create package from string");
AnsiConsole.MarkupLine("[yellow i]see log for more details[/]");
Log.Error("Could not create Package from passed string:\n{string}", PackageString);
Log.Error(e, "Exception thrown");
}
}
sb.AppendLine(line);
}
/* INFO: This LINQ query allows sorting.
* Pay special attention to the usage of the version comparer
*/
packages = packages
.OrderBy(package => package.PackageName)
.ThenByDescending(package => package.Version).ToList();
// Print any packages we have
var Variant = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Choose variant")
.PageSize(4)
.AddChoices(new[] {
"stable",
"beta",
"dev",
"all"
})
);
List<Package> VariantPackages = new();
if (Variant == "" || Variant == "all")
{
VariantPackages = packages;
}
else
{
VariantPackages = packages
.Where(package =>
package.PackageName.Contains(Variant))
.ToList();
}
RenderPackagesTable(VariantPackages);
var Package = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Choose Package to Install")
.PageSize(5)
.AddChoices(VariantPackages.ConvertAll(pack => pack.PackageName).Distinct())
);
var ChosenPackages = packages
.Where(package =>
package.PackageName == Package)
.ToList();
Package LatestVersion = ChosenPackages.OrderByDescending(pack => pack.Version).First();
RenderPackageAsTable(LatestVersion);
return 0;
}
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 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![/]");
break;
}
task.Increment(read);
await fileStream.WriteAsync(buffer, 0, read);
}
}
}
return fileLocation;
}
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:
- Parse package to find latest version
- Check version
- Offer update
- Make SENSIBLE backup
- Do install
= if install fails revert backup
*/
}

9
Readme.md Normal file
View File

@ -0,0 +1,9 @@
# Edge-Install (_Extra Jank Edition_)
A dumb package manager, soon to be outfitted with the ability to fully
extract .deb packages.
## TODO:
- Add archive extraction code
- Add install functionality
- Refactor some classes to make them neater

134
ReleaseData.cs Normal file
View File

@ -0,0 +1,134 @@
// 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;
using System.Text.Json.Serialization;
namespace EdgeInstaller;
sealed class ReleaseData
{
public DateTime Date { get; private set; }
public List<string> Architectures { 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)
{
Log.Information("Creating new ReleaseData object");
if (Release == "")
{
throw new ArgumentException(
message: "Empty release provided to ReleaseData",
paramName: "Release");
}
Architectures = new();
PackageFiles = new();
// Pull release apart
List<string> ReleaseLines = Release.Split("\n", StringSplitOptions.RemoveEmptyEntries).ToList();
Log.Debug("ReleaseLines = {@releaselines}", ReleaseLines);
// Safely find and parse the Date field
string DateLine =
ReleaseLines.Where(
Line => Line.Contains("Date:"))
.FirstOrDefault("").Substring(5);
Log.Debug("DateLine = {DateLine}", DateLine);
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
string ArchitecturesLine =
ReleaseLines.Where(
Line => Line.Contains("Architectures:"))
.FirstOrDefault("");
Log.Debug("Arch line: {archline}", ArchitecturesLine);
if (ArchitecturesLine == "")
{
throw _lineFormatException("architectures");
}
Architectures = ArchitecturesLine
.Substring(ArchitecturesLine.IndexOf(":") + 1) // We only want items after the label
.Split(" ", StringSplitOptions.RemoveEmptyEntries) // Space delimited list
.ToList(); // Convert to the superior collection type
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
// First split the Lines to only get the file data
List<string> SHA256FileData = ReleaseLines.Where((value, 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<PackageFile> 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: I do this since empty gz files have a size
// 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
{
if (PackageFiles.Where(file => file.filename.Contains(arch)).Count() == 0)
{
Architectures.Remove(arch);
}
}
}
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);

127
Version.cs Normal file
View File

@ -0,0 +1,127 @@
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;
}
}

18
edge-install.csproj Normal file
View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>edge_install</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
<PackageReference Include="System.Commandline" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
</Project>