//using System.CommandLine; using Serilog; using Spectre.Console; using System.Text.Json; using System.Text; namespace EdgeInstaller; 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 readonly string DataDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ClosedLess", "EdgeInstall-Jankmode"); public static async Task Main() { _client.BaseAddress = new Uri("https://packages.microsoft.com/repos/edge/"); // Ensure DataDir exists. // NOTE: Directory.CreateDirectory will ALWAYS create the entire path Directory.CreateDirectory(DataDir); BUGFIX_ansiDetection(); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.File(Path.Combine(DataDir, ".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 = new(ReleaseString); RenderReleaseTable(data); // only select arch if needed string TargetArch = data.Architectures.Count > 1 ? AnsiConsole.Prompt( new SelectionPrompt() .Title("Choose Architecture") .PageSize(5) .MoreChoicesText("[grey]Scroll for more...[/]") .AddChoices(data.Architectures) ) : data.Architectures.First(); // 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, TargetArch)).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 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(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(TargetArch)) // ensure we only fetch for the chosen architecture .First(); if (gzFile is null) { throw new NullReferenceException("OOPS gzfile null"); } string URL = "dists/stable/" + gzFile.filename; string filename = await AnsiConsole.Progress() .Columns(new ProgressColumn[] { new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new RemainingTimeColumn(), new SpinnerColumn(), }) .StartAsync(async ctx => { var task = ctx.AddTask(URL, new ProgressTaskSettings { AutoStart = false }); return await downloadFile(URL, Archdir, task); }); string JSONdata = JsonSerializer.Serialize(data); File.WriteAllText(Path.Combine(DataDir, "release.json"), JSONdata); if (!ValidateChecksum(new FileInfo(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 packages = new(); StringBuilder sb = new(); foreach (var line in PackagesString.Split('\n')) { if (line == "") { 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. 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() .Title("Choose variant") .PageSize(4) .AddChoices(new[] { "stable", "beta", "dev", "all" }) ); List 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() .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); var versionPath = LatestVersion.Filename .Replace("pool/main/m/", "") .Replace(".deb", ""); var versionsDir = Path.Join(Archdir, "Versions"); // Let's get and unpack the latest version if (true)//!Directory.Exists(Path.Join(versionsDir, versionPath))) { using (MemoryStream debStream = await AnsiConsole.Progress() .Columns(new ProgressColumn[] { new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new RemainingTimeColumn(), new SpinnerColumn(), }) .StartAsync(async ctx => { var task = ctx.AddTask(LatestVersion.Filename, new ProgressTaskSettings { AutoStart = false }); 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(); } } } } return 0; } } /*TODO: - Check version - Offer update - Make SENSIBLE backup - Do install = if install fails revert backup */ }