Skip to content

Commit

Permalink
Self app upgrade feature (#12)
Browse files Browse the repository at this point in the history
Enable support for app self upgrade
  • Loading branch information
vgmello authored Nov 14, 2023
1 parent 4e9672d commit 81a233c
Show file tree
Hide file tree
Showing 12 changed files with 554 additions and 21 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ jobs:
type: "zip"
directory: win-output/
path: aws-cred-mgr.exe
filename: "aws-cred-mgr-win-x64.zip"
filename: aws-cred-mgr-${{ github.ref_name }}-win-x64.zip
- name: Create Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
files: win-output/aws-cred-mgr-win-x64.zip
prerelease: ${{ contains(github.ref_name, 'beta') }}
files: win-output/aws-cred-mgr-${{ github.ref_name }}-win-x64.zip
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<RepositoryUrl>https://github.com/ellosoft/aws-cred-mgr</RepositoryUrl>
<PackageProjectUrl>https://github.com/ellosoft/aws-cred-mgr</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<Version>0.0.1</Version>
<Version>0.0.1-alpha-4</Version>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

namespace Ellosoft.AwsCredentialsManager.Infrastructure;

public interface IFileDownloadService
{
Task DownloadFileAsync(HttpClient httpClient, string downloadUrl, Stream destinationStream);
}

public class FileDownloadService : IFileDownloadService
{
public Task DownloadFileAsync(HttpClient httpClient, string downloadUrl, Stream destinationStream)
{
return AnsiConsole.Progress()
.HideCompleted(true)
.AutoClear(true)
.StartAsync(async ctx =>
{
var startingTask = ctx.AddTask("Starting download").IsIndeterminate();

using var httpResponse = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);

httpResponse.EnsureSuccessStatusCode();

startingTask.StopTask();

var totalBytes = httpResponse.Content.Headers.ContentLength ?? throw new InvalidOperationException("Invalid download size");

var downloadTask = ctx.AddTask("Downloading", new ProgressTaskSettings { MaxValue = Convert.ToDouble(totalBytes) });

await using var downloadStream = await httpResponse.Content.ReadAsStreamAsync();

var buffer = new byte[16384];
int bytesRead;

while ((bytesRead = await downloadStream.ReadAsync(buffer)) > 0)
{
await destinationStream.WriteAsync(buffer, 0, bytesRead);
downloadTask.Increment(bytesRead);
}

await destinationStream.FlushAsync();
destinationStream.Position = 0;

downloadTask.StopTask();
});
}
}
142 changes: 142 additions & 0 deletions src/Ellosoft.AwsCredentialsManager/Infrastructure/SemanticVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using System.Diagnostics.CodeAnalysis;

namespace Ellosoft.AwsCredentialsManager.Infrastructure;

public record SemanticVersion : IComparable<SemanticVersion>
{
private Version MainVersion { get; }

private string? PreReleaseLabel { get; }

private int PreReleaseNumber { get; }

private string? BuildData { get; }

public bool IsPreRelease => PreReleaseLabel is not null;

public SemanticVersion(string versionValue)
{
if (!TryParse(versionValue, out var mainVersion, out var preReleaseLabel, out var preReleaseNumber, out var buildData))
throw new ArgumentException($"'{versionValue}' is not a valid SemanticVersion");

MainVersion = mainVersion;
PreReleaseLabel = preReleaseLabel;
PreReleaseNumber = preReleaseNumber;
BuildData = buildData;
}

private SemanticVersion(Version mainVersion, string? preReleaseLabel, int preReleaseNumber, string? buildData)
{
MainVersion = mainVersion;
PreReleaseLabel = preReleaseLabel;
PreReleaseNumber = preReleaseNumber;
BuildData = buildData;
}

public int CompareTo(SemanticVersion? other)
{
if (other is null)
return 1;

var mainVersionComparison = MainVersion.CompareTo(other.MainVersion);

if (mainVersionComparison != 0)
return mainVersionComparison;

var isPreRelease = !string.IsNullOrEmpty(PreReleaseLabel);
var otherIsPreRelease = !string.IsNullOrEmpty(other.PreReleaseLabel);

if (isPreRelease && !otherIsPreRelease)
return -1;

if (!isPreRelease && otherIsPreRelease)
return 1;

var preReleaseLabelComparison = StringComparer.OrdinalIgnoreCase.Compare(PreReleaseLabel, other.PreReleaseLabel);

if (preReleaseLabelComparison != 0)
return preReleaseLabelComparison;

return PreReleaseNumber.CompareTo(other.PreReleaseNumber);
}

public override string ToString()
{
var result = MainVersion.ToString();

if (PreReleaseLabel is not null)
{
result += $"-{PreReleaseLabel}";

if (PreReleaseNumber != 0)
result += $".{PreReleaseNumber}";
}

if (BuildData is not null)
result += $"+{BuildData}";

return result;
}

public static bool operator <(SemanticVersion left, SemanticVersion right) => left.CompareTo(right) < 0;

public static bool operator >(SemanticVersion left, SemanticVersion right) => left.CompareTo(right) > 0;

public static bool operator <=(SemanticVersion left, SemanticVersion right) => left.CompareTo(right) <= 0;

public static bool operator >=(SemanticVersion left, SemanticVersion right) => left.CompareTo(right) >= 0;

public static bool TryParse(string? versionValue, [NotNullWhen(true)] out SemanticVersion? version)
{
version = null;

if (!TryParse(versionValue, out var mainVersion, out var preReleaseLabel, out var preReleaseNumber, out var buildData))
return false;

version = new SemanticVersion(mainVersion, preReleaseLabel, preReleaseNumber, buildData);

return true;
}

private static bool TryParse(string? versionValue,
[NotNullWhen(true)] out Version? mainVersion, out string? preReleaseLabel, out int preReleaseNumber, out string? buildData)
{
mainVersion = null;
preReleaseLabel = null;
preReleaseNumber = 0;
buildData = null;

if (versionValue is null)
return false;

var buildDataIndex = versionValue.IndexOf('+');

if (buildDataIndex > 0)
{
buildData = versionValue[(buildDataIndex + 1)..];
versionValue = versionValue[..buildDataIndex];
}

var preReleaseIndex = versionValue.IndexOf('-');

if (preReleaseIndex > 0)
{
preReleaseLabel = versionValue[(preReleaseIndex + 1)..];
versionValue = versionValue[..preReleaseIndex];

var preReleaseNumberIndex = preReleaseLabel.IndexOf('.');

if (preReleaseNumberIndex > 0)
{
if (!int.TryParse(preReleaseLabel[(preReleaseNumberIndex + 1)..], out preReleaseNumber))
return false;

preReleaseLabel = preReleaseLabel[..preReleaseNumberIndex];
}
}

return Version.TryParse(versionValue, out mainVersion);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using System.Text.Json.Serialization;

namespace Ellosoft.AwsCredentialsManager.Infrastructure.Upgrade.Models;

public class GitHubRelease
{
[JsonPropertyName("name")]
public required string Name { get; set; }

[JsonPropertyName("html_url")]
public required string Url { get; set; }

[JsonPropertyName("prerelease")]
public bool PreRelease { get; set; }

[JsonPropertyName("assets")]
public ICollection<ReleaseAsset> Assets { get; set; } = Array.Empty<ReleaseAsset>();

public class ReleaseAsset
{
[JsonPropertyName("browser_download_url")]
public required string DownloadUrl { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2023 Ellosoft Limited. All rights reserved.

using System.Text.Json.Serialization;

namespace Ellosoft.AwsCredentialsManager.Infrastructure.Upgrade.Models;

[JsonSourceGenerationOptions]
[JsonSerializable(typeof(GitHubRelease))]
[JsonSerializable(typeof(List<GitHubRelease>))]
internal partial class GithubSourceGenerationContext : JsonSerializerContext
{
}
Loading

0 comments on commit 81a233c

Please sign in to comment.