Skip to content
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
51 changes: 44 additions & 7 deletions backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using Windows.Foundation;
using Windows.Management.Deployment;
using Windows.Networking.Connectivity;
using LexCore.Entities;
Expand Down Expand Up @@ -107,13 +108,31 @@ private async Task<UpdateResult> ApplyUpdate(FwLiteRelease latestRelease, bool q
{
logger.LogInformation("Installing new version: {Version}, Current version: {CurrentVersion}", latestRelease.Version, AppVersion.Version);
var packageManager = new PackageManager();
var asyncOperation = packageManager.AddPackageByUriAsync(new Uri(latestRelease.Url),
new AddPackageOptions()
{
DeferRegistrationWhenPackagesAreInUse = true,
ForceUpdateFromAnyVersion = true,
ForceAppShutdown = quitOnUpdate
});

//If this install is associated with an .appinstaller file, it's on the OS update track and we
//must update through the App Installer API. Updating the raw bundle via AddPackageByUriAsync
//would detach it from that track (see App Installer non-store update docs). Installs from a plain
//.msixbundle - how most users are installed today - have no association and take the fallback path.
var appInstallerUri = GetAppInstallerUri();
IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> asyncOperation;
if (appInstallerUri is not null)
{
logger.LogInformation("Updating via App Installer file {AppInstallerUri}", appInstallerUri);
//ForceUpdateFromAnyVersion is controlled by the .appinstaller XML, not these options.
asyncOperation = packageManager.AddPackageByAppInstallerFileAsync(appInstallerUri,
quitOnUpdate ? AddPackageByAppInstallerOptions.ForceTargetAppShutdown : AddPackageByAppInstallerOptions.None,
packageManager.GetDefaultPackageVolume());
}
else
{
asyncOperation = packageManager.AddPackageByUriAsync(new Uri(latestRelease.Url),
new AddPackageOptions()
{
DeferRegistrationWhenPackagesAreInUse = true,
ForceUpdateFromAnyVersion = true,
ForceAppShutdown = quitOnUpdate
});
}
asyncOperation.Progress = (info, progressInfo) =>
{
NotifyInstallProgress(progressInfo.percentage, latestRelease);
Expand Down Expand Up @@ -150,6 +169,24 @@ private void NotifyInstallProgress(uint percentage, FwLiteRelease release)
eventBus.PublishEvent(new AppUpdateProgressEvent(percentage, release));
}

/// <summary>
/// The .appinstaller URI this install is associated with, or null if it wasn't installed via an
/// .appinstaller file (i.e. it's not on the OS update track and should use the direct-bundle path).
/// </summary>
private Uri? GetAppInstallerUri()
{
try
{
return Windows.ApplicationModel.Package.Current.GetAppInstallerInfo()?.Uri;
}
catch (Exception e)
{
//Package.Current throws for unpackaged/portable apps; treat as "not on the track".
logger.LogWarning(e, "Unable to read App Installer info; falling back to direct bundle update");
return null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

public DateTime LastUpdateCheck
{
get => preferences.Get(LastUpdateCheckKey, DateTime.MinValue);
Expand Down
4 changes: 0 additions & 4 deletions backend/LexBoxApi/Controllers/FwLiteReleaseController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ public async Task<ActionResult> DownloadLatest([FromQuery] FwLiteEdition edition
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");
}
Expand Down
17 changes: 10 additions & 7 deletions backend/LexBoxApi/Services/FwLiteReleases/FwLiteReleaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,26 @@ public async ValueTask<string> GenerateAppInstaller(CancellationToken token = de
Version="{version}"
Uri="{windowsRelease.Url}" />
<UpdateSettings>
<OnLaunch
HoursBetweenUpdateChecks="8"
ShowPrompt="true"
UpdateBlocksActivation="false" />
<!-- No OnLaunch: Windows never checks on start, so it can't show an update prompt and doesn't
race the in-app updater. Updates are applied silently by the background task (~every 8h). -->
<ForceUpdateFromAnyVersion>false</ForceUpdateFromAnyVersion>
<AutomaticBackgroundTask />
</UpdateSettings>
</AppInstaller>
""";
}

private static string ConvertVersionToAppInstallerVersion(string version)
//public for testing (like ShouldUpdateToRelease): this MUST match the bundle's manifest identity
//version exactly (CI stamps that from `date +%Y.%-m.%-d` plus a `.1` revision in the MakeAppx /bv
//arg), or the App Installer install fails with an identity mismatch.
public 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
//version is something like v2025-01-17-a62c709c which should be converted to 2025.1.17.1,
//always adding .1 on the end. int.Parse drops leading zeros
return version.Split('-') switch
{
[var year, var month, var day, ..] => $"{year.TrimStart('v')}.{month.TrimStart('0')}.{day.TrimStart('0')}.1",
[var year, var month, var day, ..] =>
$"{int.Parse(year.TrimStart('v'))}.{int.Parse(month)}.{int.Parse(day)}.1",
_ => throw new ArgumentException($"Invalid version {version}")
};
}
Expand Down
10 changes: 10 additions & 0 deletions backend/Testing/LexCore/Services/FwLiteReleaseServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,14 @@ public void ShouldUpdateToReleaseGivesExpectedResult(string appVersion,
var actual = FwLiteReleaseService.ShouldUpdateToRelease(appVersion, latestVersion);
actual.Should().Be(expected, reason);
}

[Theory]
//must match the bundle identity version CI stamps: `date +%Y.%-m.%-d` (no leading zeros) + ".1"
[InlineData("v2025-01-17-a62c709c", "2025.1.17.1")]
[InlineData("v2026-07-06-915ca19d", "2026.7.6.1")]
[InlineData("v2026-10-30-deadbeef", "2026.10.30.1")]
public void ConvertVersionToAppInstallerVersionGivesExpectedResult(string tag, string expected)
{
FwLiteReleaseService.ConvertVersionToAppInstallerVersion(tag).Should().Be(expected);
}
}
Loading
Loading