Skip to content

Commit

Permalink
Download channel selection
Browse files Browse the repository at this point in the history
  • Loading branch information
LucHeart committed May 4, 2024
1 parent ccfba62 commit b6138c6
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 73 deletions.
13 changes: 12 additions & 1 deletion ShockOsc/Config/AppConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
namespace OpenShock.ShockOsc.Config;
using Semver;

namespace OpenShock.ShockOsc.Config;

public sealed class AppConfig
{
public bool CloseToTray { get; set; } = true;

public UpdateChannel UpdateChannel { get; set; } = UpdateChannel.Release;
public SemVersion? LastIgnoredVersion { get; set; } = null;
}

public enum UpdateChannel
{
Release,
PreRelease
}
1 change: 0 additions & 1 deletion ShockOsc/Config/ShockOscConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public sealed class ShockOscConfig
public OpenShockConf OpenShock { get; set; } = new();
public ChatboxConf Chatbox { get; set; } = new();
public IDictionary<Guid, Group> Groups { get; set; } = new Dictionary<Guid, Group>();
public SemVersion? LastIgnoredVersion { get; set; } = null;

public AppConfig App { get; set; } = new();
}
10 changes: 10 additions & 0 deletions ShockOsc/Models/GithubReleaseResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ public class GithubReleaseResponse
{
[JsonPropertyName("tag_name")]
public required string TagName { get; set; }

[JsonPropertyName("id")]
public required ulong Id { get; set; }

[JsonPropertyName("draft")]
public required bool Draft { get; set; }

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

[JsonPropertyName("assets")]
public required ICollection<Asset> Assets { get; set; }

Expand Down
2 changes: 2 additions & 0 deletions ShockOsc/Services/ShockOsc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public async Task FoundVrcClient(IPEndPoint? oscClient)

_logger.LogInformation("Ready");
OsTask.Run(_underscoreConfig.SendUpdateForAll);

_oscClient.SendChatboxMessage($"{_configManager.Config.Chatbox.Prefix} Game Connected");
}

public async Task OnAvatarChange(Dictionary<string, object?> parameters, string avatarId)
Expand Down
150 changes: 119 additions & 31 deletions ShockOsc/Services/Updater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,35 @@
using OpenShock.SDK.CSharp.Updatables;
using OpenShock.ShockOsc.Config;
using OpenShock.ShockOsc.Models;
using OpenShock.ShockOsc.Ui.Utils;
using OpenShock.ShockOsc.Utils;
using Semver;

namespace OpenShock.ShockOsc.Services;

public sealed class Updater
{
private const string GithubLatest = "https://api.github.com/repos/OpenShock/ShockOsc/releases/latest";
private const string GithubReleasesUrl = "https://api.github.com/repos/OpenShock/ShockOsc/releases";
private const string GithubLatest = $"{GithubReleasesUrl}/latest";
private const string SetupFileName = "ShockOSC_Setup.exe"; // OpenShock.ShockOsc.exe

private static readonly HttpClient HttpClient = new();

private readonly string _setupFilePath = Path.Combine(Path.GetTempPath(), SetupFileName);

private static readonly SemVersion CurrentVersion = SemVersion.Parse(typeof(Updater).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion, SemVersionStyles.Strict);
private static readonly SemVersion CurrentVersion = SemVersion.Parse(
typeof(Updater).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion,
SemVersionStyles.Strict);

private Uri? LatestDownloadUrl { get; set; }
private Uri? ReleaseDownloadUrl { get; set; }

private readonly ILogger<Updater> _logger;
private readonly ConfigManager _configManager;


public UpdatableVariable<bool> CheckingForUpdate { get; } = new(false);
public UpdatableVariable<bool> UpdateAvailable { get; } = new(false);
public bool IsPostponed { get; private set; }
public SemVersion? LatestVersion { get; private set; }
public UpdatableVariable<double> DownloadProgress { get; } = new(0);


public Updater(ILogger<Updater> logger, ConfigManager configManager)
Expand All @@ -53,44 +57,41 @@ private static bool TryDeleteFile(string fileName)
}
}

private async Task<(SemVersion, GithubReleaseResponse.Asset)?> GetLatestRelease()
private async Task<(SemVersion, GithubReleaseResponse.Asset)?> GetRelease()
{
_logger.LogInformation("Checking GitHub for updates...");
var updateChannel = _configManager.Config.App.UpdateChannel;
_logger.LogInformation("Checking GitHub for updates on channel {UpdateChannel}", updateChannel);

try
{
var res = await HttpClient.GetAsync(GithubLatest);
if (!res.IsSuccessStatusCode)
var release = updateChannel switch
{
_logger.LogWarning("Failed to get latest version information from GitHub. {StatusCode}",
res.StatusCode);
return null;
}
UpdateChannel.Release => await GetLatestRelease(),
UpdateChannel.PreRelease => await GetPreRelease(),
_ => null
};

var json =
await JsonSerializer.DeserializeAsync<GithubReleaseResponse>(await res.Content.ReadAsStreamAsync());
if (json == null)
if (release == null)
{
_logger.LogWarning("Could not deserialize json");
_logger.LogError("Failed to get latest version information from GitHub");
return null;
}

var tagName = json.TagName;

if (!SemVersion.TryParse(tagName, SemVersionStyles.AllowV, out var version))
if (!SemVersion.TryParse(release.TagName, SemVersionStyles.AllowV, out var version))
{
_logger.LogWarning("Failed to parse version. Value: {Version}", json.TagName);
_logger.LogWarning("Failed to parse version. Value: {Version}", release.TagName);
return null;
}

var asset = json.Assets.FirstOrDefault(x => x.Name.Equals(SetupFileName, StringComparison.InvariantCultureIgnoreCase));
var asset = release.Assets.FirstOrDefault(x =>
x.Name.Equals(SetupFileName, StringComparison.InvariantCultureIgnoreCase));
if (asset == null)
{
_logger.LogWarning("Could not find asset with {@SetupName}. Assets found: {@Assets}", SetupFileName,
json.Assets);
release.Assets);
return null;
}

return (version, asset);
}
catch (Exception e)
Expand All @@ -100,12 +101,88 @@ private static bool TryDeleteFile(string fileName)
}
}

private async Task<GithubReleaseResponse?> GetPreRelease()
{
using var res = await HttpClient.GetAsync(GithubReleasesUrl);
if (!res.IsSuccessStatusCode)
{
_logger.LogWarning("Failed to get latest version information from GitHub. {StatusCode}",
res.StatusCode);
return null;
}

var json =
await JsonSerializer.DeserializeAsync<IEnumerable<GithubReleaseResponse>>(
await res.Content.ReadAsStreamAsync());
if (json == null)
{
_logger.LogWarning("Could not deserialize json");
return null;
}

var listOfValid = new List<(GithubReleaseResponse, SemVersion)>();
foreach (var release in json.Where(x => x.Prerelease))
{
var tagName = release.TagName;
if (!SemVersion.TryParse(tagName, SemVersionStyles.AllowV, out var version))
{
_logger.LogDebug("Failed to parse version. Value: {Version}", tagName);
continue;
}

listOfValid.Add((release, version));
}

var newestPreRelease = listOfValid.OrderByDescending(x => x.Item2).FirstOrDefault();

return newestPreRelease.Item1;
}

private async Task<GithubReleaseResponse?> GetLatestRelease()
{
using var res = await HttpClient.GetAsync(GithubLatest);
if (!res.IsSuccessStatusCode)
{
_logger.LogWarning("Failed to get latest version information from GitHub. {StatusCode}",
res.StatusCode);
return null;
}

var json =
await JsonSerializer.DeserializeAsync<GithubReleaseResponse>(await res.Content.ReadAsStreamAsync());
if (json == null)
{
_logger.LogWarning("Could not deserialize json");
return null;
}

return json;
}

private readonly SemaphoreSlim _updateLock = new(1, 1);

public async Task CheckUpdate()
{
await _updateLock.WaitAsync();

try
{
CheckingForUpdate.Value = true;
await CheckUpdateInternal();
}
finally
{
_updateLock.Release();
CheckingForUpdate.Value = false;
}
}

private async Task CheckUpdateInternal()
{
IsPostponed = false;
UpdateAvailable.Value = false;
var latestVersion = await GetLatestRelease();

var latestVersion = await GetRelease();
if (latestVersion == null)
{
UpdateAvailable.Value = false;
Expand All @@ -123,8 +200,9 @@ public async Task CheckUpdate()

UpdateAvailable.Value = true;
LatestVersion = latestVersion.Value.Item1;
LatestDownloadUrl = latestVersion.Value.Item2.BrowserDownloadUrl;
if (_configManager.Config.LastIgnoredVersion != null && _configManager.Config.LastIgnoredVersion.ComparePrecedenceTo(latestVersion.Value.Item1) >= 0)
ReleaseDownloadUrl = latestVersion.Value.Item2.BrowserDownloadUrl;
if (_configManager.Config.App.LastIgnoredVersion != null &&
_configManager.Config.App.LastIgnoredVersion.ComparePrecedenceTo(latestVersion.Value.Item1) >= 0)
{
_logger.LogInformation(
"ShockOsc is not up to date. Skipping update due to previous postpone");
Expand All @@ -140,7 +218,9 @@ public async Task CheckUpdate()
public async Task DoUpdate()
{
_logger.LogInformation("Starting update...");
if (LatestVersion == null || LatestDownloadUrl == null)

DownloadProgress.Value = 0;
if (LatestVersion == null || ReleaseDownloadUrl == null)
{
_logger.LogError("LatestVersion or LatestDownloadUrl is null. Cannot update");
return;
Expand All @@ -150,12 +230,20 @@ public async Task DoUpdate()

_logger.LogDebug("Downloading new release...");
var sp = Stopwatch.StartNew();
await using (var stream = await HttpClient.GetStreamAsync(LatestDownloadUrl))
var download = await HttpClient.GetAsync(ReleaseDownloadUrl, HttpCompletionOption.ResponseHeadersRead);
var totalBytes = download.Content.Headers.ContentLength ?? 1;

await using (var stream = await download.Content.ReadAsStreamAsync())
{
await using var fStream = new FileStream(_setupFilePath, FileMode.OpenOrCreate);
await stream.CopyToAsync(fStream);
var relativeProgress = new Progress<long>(downloadedBytes =>
DownloadProgress.Value = ((double)downloadedBytes / totalBytes) * 100);

// Use extension method to report progress while downloading
await stream.CopyToAsync(fStream, 81920, relativeProgress);
}

DownloadProgress.Value = 100;
_logger.LogDebug("Downloaded file within {TimeTook}ms", sp.ElapsedMilliseconds);
_logger.LogInformation("Download complete, now restarting to newer application in one second");
await Task.Delay(1000);
Expand Down
2 changes: 1 addition & 1 deletion ShockOsc/ShockOsc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<RootNamespace>OpenShock.ShockOsc</RootNamespace>
<Company>OpenShock</Company>
<AssemblyVersion>2.0.0</AssemblyVersion>
<Version>2.0.0-rc.2</Version>
<Version>2.0.0-rc.3</Version>
<ApplicationIcon>Resources\openshock-icon.ico</ApplicationIcon>
<SelfContained>true</SelfContained>
<Product>ShockOsc</Product>
Expand Down
Loading

0 comments on commit b6138c6

Please sign in to comment.