Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change FwLitePlatform to edition #1402

Merged
merged 3 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/LexBoxApi/Config/FwLiteReleaseConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ namespace LexBoxApi.Config;

public class FwLiteReleaseConfig
{
public Dictionary<FwLitePlatform, FwLitePlatformConfig> Platforms { get; set; } = new();
public Dictionary<FwLiteEdition, FwLiteEditionConfig> Editions { get; set; } = new();
}

public class FwLitePlatformConfig
public class FwLiteEditionConfig
{
[Required]
public required string FileNameRegex { get; init; }
Expand Down
32 changes: 20 additions & 12 deletions backend/LexBoxApi/Controllers/FwLiteReleaseController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Diagnostics;
using LexBoxApi.Config;
using System.Text;
using LexBoxApi.Otel;
using LexBoxApi.Services.FwLiteReleases;
using LexCore.Entities;
Expand All @@ -13,15 +13,23 @@ namespace LexBoxApi.Controllers;
[ApiExplorerSettings(GroupName = LexBoxKernel.OpenApiPublicDocumentName)]
public class FwLiteReleaseController(FwLiteReleaseService releaseService) : ControllerBase
{

[HttpGet("download-latest")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DownloadLatest([FromQuery] FwLitePlatform platform = FwLitePlatform.Windows)
public async Task<ActionResult> DownloadLatest([FromQuery] FwLiteEdition edition = FwLiteEdition.Windows)
{
using var activity = LexBoxActivitySource.Get().StartActivity();
activity?.AddTag(FwLiteReleaseService.FwLitePlatformTag, platform.ToString());
var latestRelease = await releaseService.GetLatestRelease(platform);
activity?.AddTag(FwLiteReleaseService.FwLiteEditionTag, edition.ToString());
if (edition == FwLiteEdition.WindowsAppInstaller)
{
//note this doesn't really work because the github server doesn't return the correct content-type of application/msixbundle
//in order for this to work we would need to proxy the request to github
//but then we would need to support range requests https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
//which is too complicated for now
var appInstallerContent = await releaseService.GenerateAppInstaller();
return File(Encoding.UTF8.GetBytes(appInstallerContent), "application/appinstaller", "FieldWorksLite.appinstaller");
}
var latestRelease = await releaseService.GetLatestRelease(edition);
if (latestRelease is null)
{
activity?.SetStatus(ActivityStatusCode.Error, "Latest release not found");
Expand All @@ -36,26 +44,26 @@ public async Task<ActionResult> DownloadLatest([FromQuery] FwLitePlatform platfo
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async ValueTask<ActionResult<FwLiteRelease>> LatestRelease([FromQuery] FwLitePlatform platform =
FwLitePlatform.Windows, string? appVersion = null)
public async ValueTask<ActionResult<FwLiteRelease>> LatestRelease([FromQuery] FwLiteEdition edition =
FwLiteEdition.Windows, string? appVersion = null)
{
using var activity = LexBoxActivitySource.Get().StartActivity();
activity?.AddTag(FwLiteReleaseService.FwLiteClientVersionTag, appVersion ?? "unknown");
activity?.AddTag(FwLiteReleaseService.FwLitePlatformTag, platform.ToString());
var latestRelease = await releaseService.GetLatestRelease(platform);
activity?.AddTag(FwLiteReleaseService.FwLiteEditionTag, edition.ToString());
var latestRelease = await releaseService.GetLatestRelease(edition);
activity?.AddTag(FwLiteReleaseService.FwLiteReleaseVersionTag, latestRelease?.Version);
if (latestRelease is null) return NotFound();
return latestRelease;
}

[HttpGet("should-update")]
[AllowAnonymous]
public async Task<ActionResult<ShouldUpdateResponse>> ShouldUpdate([FromQuery] string appVersion, [FromQuery] FwLitePlatform platform = FwLitePlatform.Windows)
public async Task<ActionResult<ShouldUpdateResponse>> ShouldUpdate([FromQuery] string appVersion, [FromQuery] FwLiteEdition edition = FwLiteEdition.Windows)
{
using var activity = LexBoxActivitySource.Get().StartActivity();
activity?.AddTag(FwLiteReleaseService.FwLiteClientVersionTag, appVersion);
activity?.AddTag(FwLiteReleaseService.FwLitePlatformTag, platform.ToString());
var response = await releaseService.ShouldUpdate(platform, appVersion);
activity?.AddTag(FwLiteReleaseService.FwLiteEditionTag, edition.ToString());
var response = await releaseService.ShouldUpdate(edition, appVersion);
activity?.AddTag(FwLiteReleaseService.FwLiteReleaseVersionTag, response.Release?.Version);
return response;
}
Expand Down
67 changes: 55 additions & 12 deletions backend/LexBoxApi/Services/FwLiteReleases/FwLiteReleaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ public class FwLiteReleaseService(IHttpClientFactory factory, HybridCache cache,
{
private const string GithubLatestRelease = "GithubLatestRelease";
public const string FwLiteClientVersionTag = "app.fw-lite.client.version";
public const string FwLitePlatformTag = "app.fw-lite.platform";
public const string FwLiteEditionTag = "app.fw-lite.edition";
public const string FwLiteReleaseVersionTag = "app.fw-lite.release.version";

public async ValueTask<FwLiteRelease?> GetLatestRelease(FwLitePlatform platform, CancellationToken token = default)
public async ValueTask<FwLiteRelease?> GetLatestRelease(FwLiteEdition edition, CancellationToken token = default)
{
return await cache.GetOrCreateAsync($"{GithubLatestRelease}|{platform}",
platform,
if (edition == FwLiteEdition.WindowsAppInstaller)
{
throw new ArgumentException("WindowsAppInstaller edition is not supported");
}
return await cache.GetOrCreateAsync($"{GithubLatestRelease}|{edition}",
edition,
FetchLatestReleaseFromGithub,
new HybridCacheEntryOptions() { Expiration = TimeSpan.FromDays(1) },
cancellationToken: token, tags: [GithubLatestRelease]);
}

public async ValueTask<ShouldUpdateResponse> ShouldUpdate(FwLitePlatform platform, string appVersion)
public async ValueTask<ShouldUpdateResponse> ShouldUpdate(FwLiteEdition edition, string appVersion)
{
var latestRelease = await GetLatestRelease(platform);
var latestRelease = await GetLatestRelease(edition);
if (latestRelease is null) return new ShouldUpdateResponse(null);

var shouldUpdateToRelease = ShouldUpdateToRelease(appVersion, latestRelease.Version);
Expand All @@ -42,15 +46,15 @@ public async ValueTask InvalidateReleaseCache()
await cache.RemoveByTagAsync(GithubLatestRelease);
}

private async ValueTask<FwLiteRelease?> FetchLatestReleaseFromGithub(FwLitePlatform platform, CancellationToken token)
private async ValueTask<FwLiteRelease?> FetchLatestReleaseFromGithub(FwLiteEdition edition, CancellationToken token)
{
var platformConfig = config.Value.Platforms.GetValueOrDefault(platform);
if (platformConfig is null)
var editionConfig = config.Value.Editions.GetValueOrDefault(edition);
if (editionConfig is null)
{
throw new ArgumentException($"No config for platform {platform}");
throw new ArgumentException($"No config for edition {edition}");
}
using var activity = LexBoxActivitySource.Get().StartActivity();
activity?.AddTag(FwLitePlatformTag, platform.ToString());
activity?.AddTag(FwLiteEditionTag, edition.ToString());
var response = await factory.CreateClient("Github")
.SendAsync(new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/sillsdev/languageforge-lexbox/releases")
Expand Down Expand Up @@ -83,7 +87,7 @@ public async ValueTask InvalidateReleaseCache()
continue;
}

var releaseAsset = release.Assets.FirstOrDefault(a => platformConfig.FileName.IsMatch(a.Name));
var releaseAsset = release.Assets.FirstOrDefault(a => editionConfig.FileName.IsMatch(a.Name));
if (releaseAsset is not null)
{
activity?.AddTag(FwLiteReleaseVersionTag, release.TagName);
Expand All @@ -96,4 +100,43 @@ public async ValueTask InvalidateReleaseCache()
activity?.AddTag(FwLiteReleaseVersionTag, null);
return null;
}

public async ValueTask<string> GenerateAppInstaller(CancellationToken token = default)
{
var windowsRelease = await GetLatestRelease(FwLiteEdition.Windows, token);
if (windowsRelease is null) throw new InvalidOperationException("Windows release not found");
var version = ConvertVersionToAppInstallerVersion(windowsRelease.Version);
//lang=xml
return $"""
<?xml version="1.0" encoding="utf-8"?>
<AppInstaller
Uri="https://lexbox.org/api/fwlite-release/download-latest?edition=windowsAppInstaller"
Version="{version}"
xmlns="http://schemas.microsoft.com/appx/appinstaller/2021">
<MainBundle
Name="FwLiteDesktop"
Publisher="CN=&quot;Summer Institute of Linguistics, Inc.&quot;, O=&quot;Summer Institute of Linguistics, Inc.&quot;, L=Dallas, S=Texas, C=US"
myieye marked this conversation as resolved.
Show resolved Hide resolved
Version="{version}"
Uri="{windowsRelease.Url}" />
<UpdateSettings>
<OnLaunch
HoursBetweenUpdateChecks="8"
ShowPrompt="true"
UpdateBlocksActivation="false" />
<ForceUpdateFromAnyVersion>false</ForceUpdateFromAnyVersion>
<AutomaticBackgroundTask />
</UpdateSettings>
</AppInstaller>
""";
}

private static string ConvertVersionToAppInstallerVersion(string version)
{
//version is something like v2025-01-17-a62c709c which should be converted to 2025.1.17.1 always adding .1 on the end and trimming zeros
return version.Split('-') switch
{
[var year, var month, var day, ..] => $"{year.TrimStart('v')}.{month.TrimStart('0')}.{day.TrimStart('0')}.1",
_ => throw new ArgumentException($"Invalid version {version}")
};
}
}
2 changes: 1 addition & 1 deletion backend/LexBoxApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
}
},
"FwLiteRelease": {
"Platforms": {
"Editions": {
"Windows": {
// ends with .msixbundle regex
"FileNameRegex": "(?i)\\.msixbundle$"
Expand Down
6 changes: 4 additions & 2 deletions backend/LexCore/Entities/FwLiteRelease.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ namespace LexCore.Entities;
public record FwLiteRelease(string Version, string Url);

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum FwLitePlatform
public enum FwLiteEdition
{
Windows,
Linux,
Android,
// ReSharper disable once InconsistentNaming
iOS,
Mac
Mac,
//not supported for now, see note in FwLiteReleaseController.DownloadLatest
WindowsAppInstaller
}

public record ShouldUpdateResponse(FwLiteRelease? Release)
Expand Down
18 changes: 9 additions & 9 deletions backend/Testing/LexCore/Services/FwLiteReleaseServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public FwLiteReleaseServiceTests()
.AddHttpClient()
.AddOptions<FwLiteReleaseConfig>().Configure(config =>
{
config.Platforms.Add(FwLitePlatform.Windows, new FwLitePlatformConfig() { FileNameRegex = "(?i)\\.msixbundle$" });
config.Platforms.Add(FwLitePlatform.Linux, new FwLitePlatformConfig() { FileNameRegex = "(?i)linux\\.zip$" });
config.Editions.Add(FwLiteEdition.Windows, new FwLiteEditionConfig() { FileNameRegex = "(?i)\\.msixbundle$" });
config.Editions.Add(FwLiteEdition.Linux, new FwLiteEditionConfig() { FileNameRegex = "(?i)linux\\.zip$" });
})
.Services
.AddHybridCache()
Expand All @@ -32,11 +32,11 @@ public FwLiteReleaseServiceTests()
}

[Theory]
[InlineData(FwLitePlatform.Windows)]
[InlineData(FwLitePlatform.Linux)]
public async Task CanGetLatestRelease(FwLitePlatform platform)
[InlineData(FwLiteEdition.Windows)]
[InlineData(FwLiteEdition.Linux)]
public async Task CanGetLatestRelease(FwLiteEdition edition)
{
var latestRelease = await _fwLiteReleaseService.GetLatestRelease(platform);
var latestRelease = await _fwLiteReleaseService.GetLatestRelease(edition);
latestRelease.Should().NotBeNull();
latestRelease.Version.Should().NotBeNullOrEmpty();
latestRelease.Url.Should().NotBeNullOrEmpty();
Expand All @@ -46,7 +46,7 @@ public async Task CanGetLatestRelease(FwLitePlatform platform)
[InlineData("v2024-11-20-d04e9b96")]
public async Task IsConsideredAnOldVersion(string appVersion)
{
var shouldUpdate = await _fwLiteReleaseService.ShouldUpdate(FwLitePlatform.Windows, appVersion);
var shouldUpdate = await _fwLiteReleaseService.ShouldUpdate(FwLiteEdition.Windows, appVersion);
shouldUpdate.Should().NotBeNull();
shouldUpdate.Release.Should().NotBeNull();
shouldUpdate.Update.Should().BeTrue();
Expand All @@ -55,9 +55,9 @@ public async Task IsConsideredAnOldVersion(string appVersion)
[Fact]
public async Task ShouldUpdateWithLatestVersionShouldReturnFalse()
{
var latestRelease = await _fwLiteReleaseService.GetLatestRelease(FwLitePlatform.Windows);
var latestRelease = await _fwLiteReleaseService.GetLatestRelease(FwLiteEdition.Windows);
latestRelease.Should().NotBeNull();
var shouldUpdate = await _fwLiteReleaseService.ShouldUpdate(FwLitePlatform.Windows, latestRelease.Version);
var shouldUpdate = await _fwLiteReleaseService.ShouldUpdate(FwLiteEdition.Windows, latestRelease.Version);
shouldUpdate.Should().NotBeNull();
shouldUpdate.Release.Should().BeNull();
shouldUpdate.Update.Should().BeFalse();
Expand Down
Loading