mirror of
https://github.com/ZuneDev/ZuneModdingHelper.git
synced 2026-07-27 13:12:21 -07:00
Abstract update checker
This commit is contained in:
@@ -41,7 +41,8 @@
|
||||
<Rectangle Height="5" Width="109" Margin="0,-16,0,0" Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Fill="{StaticResource ZuneHorizontalGradientAlt}"/>
|
||||
<TextBlock Text="{Binding Title}" d:Text="WINDOWS LIVE ID" FontSize="28" Margin="0,-10,0,12"
|
||||
<TextBlock Text="{Binding Title}" d:Text="WINDOWS LIVE ID" FontSize="28"
|
||||
Grid.ColumnSpan="2" Margin="0,-10,0,12"
|
||||
Foreground="{StaticResource ZuneMediumTextBrush}"
|
||||
Visibility="{Binding Title, Converter={StaticResource NotNullToVis}}"/>
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
</TextBlock>
|
||||
<Button Content="CHECK FOR UPDATES" Click="UpdateCheckButton_Click"
|
||||
Margin="0,8,0,12" Padding="8,0" HorizontalAlignment="Left"
|
||||
IsEnabled="{Binding EnableCheckForUpdates}"
|
||||
Style="{StaticResource ActionButtonStyle}"/>
|
||||
|
||||
<TextBlock Style="{StaticResource ZuneBodyTextBlockStyle}">
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Octokit;
|
||||
using Syroot.Windows.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Navigation;
|
||||
using ZuneModCore;
|
||||
using ZuneModdingHelper.Messages;
|
||||
using ZuneModdingHelper.Services;
|
||||
|
||||
namespace ZuneModdingHelper.Pages
|
||||
{
|
||||
@@ -21,15 +18,18 @@ namespace ZuneModdingHelper.Pages
|
||||
{
|
||||
const string UPDATES_DIALOG_TITLE = "UPDATES";
|
||||
|
||||
private readonly IGitHubClient? _gitHub = Ioc.Default.GetService<IGitHubClient>();
|
||||
private ReleaseAsset _latestAsset;
|
||||
private readonly IUpdateService? _updateService = Ioc.Default.GetService<IUpdateService>();
|
||||
private UpdateAvailableInfo _updateInfo;
|
||||
private string _downloadedUpdatePath;
|
||||
|
||||
public AboutPage()
|
||||
{
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public bool EnableCheckForUpdates => _updateService is not null;
|
||||
|
||||
private void Link_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
App.OpenInBrowser(e.Uri.AbsoluteUri);
|
||||
@@ -38,7 +38,7 @@ namespace ZuneModdingHelper.Pages
|
||||
|
||||
private async void UpdateCheckButton_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
if (_gitHub is null)
|
||||
if (_updateService is null)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new ProgressDialogViewModel
|
||||
@@ -51,16 +51,8 @@ namespace ZuneModdingHelper.Pages
|
||||
|
||||
try
|
||||
{
|
||||
// Get releases list from GitHub
|
||||
// Why not use the `releases/latest` endpoint? Good question: https://github.com/octokit/octokit.net/issues/2916
|
||||
var releases = await _gitHub.Repository.Release.GetAll("ZuneDev", "ZuneModdingHelper");
|
||||
var latest = releases
|
||||
.Select(r => new { Release = r, Version = ReleaseVersion.Parse(r.TagName) })
|
||||
.OrderByDescending(t => t.Version)
|
||||
.ThenBy(t => t.Release.Prerelease)
|
||||
.First();
|
||||
|
||||
if (ReleaseVersion.TryParse(latest.Release.TagName, out var latestVer) || App.Version >= latestVer)
|
||||
_updateInfo = await _updateService.FetchAvailableUpdate();
|
||||
if (_updateInfo is null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<CloseDialogMessage>();
|
||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
||||
@@ -73,13 +65,12 @@ namespace ZuneModdingHelper.Pages
|
||||
}
|
||||
|
||||
// Newer version available, prompt user to download
|
||||
_latestAsset = latest.Release.Assets[0];
|
||||
|
||||
WeakReferenceMessenger.Default.Send<CloseDialogMessage>();
|
||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
||||
{
|
||||
Title = UPDATES_DIALOG_TITLE,
|
||||
Description = $"Release {latest.Release.Name} is available. Would you like to download it now?",
|
||||
Description = $"Release {_updateInfo.Name} is available. Would you like to download it now?",
|
||||
AffirmativeText = "YES",
|
||||
NegativeText = "NO",
|
||||
ShowAffirmativeButton = true,
|
||||
@@ -107,30 +98,14 @@ namespace ZuneModdingHelper.Pages
|
||||
IsIndeterminate = true,
|
||||
ShowAffirmativeButton = false,
|
||||
ShowNegativeButton = false,
|
||||
Maximum = 100,
|
||||
Maximum = 1.0,
|
||||
};
|
||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(prog));
|
||||
|
||||
// Download new version to AppData
|
||||
string downloadedFile = Path.Combine(Path.GetTempPath(), _latestAsset.Name);
|
||||
if (File.Exists(downloadedFile)) File.Delete(downloadedFile);
|
||||
using (var client = new System.Net.WebClient())
|
||||
{
|
||||
client.DownloadProgressChanged += (object sender, System.Net.DownloadProgressChangedEventArgs e) =>
|
||||
{
|
||||
// Update UI with download progress
|
||||
prog.Progress = e.ProgressPercentage;
|
||||
};
|
||||
|
||||
prog.Progress = 0;
|
||||
prog.IsIndeterminate = false;
|
||||
await client.DownloadFileTaskAsync(new Uri(_latestAsset.BrowserDownloadUrl), downloadedFile);
|
||||
}
|
||||
|
||||
// Ask user to save file
|
||||
Microsoft.Win32.SaveFileDialog saveFileDialog = new()
|
||||
{
|
||||
FileName = _latestAsset.Name,
|
||||
FileName = _updateInfo.Name,
|
||||
InitialDirectory = new KnownFolder(KnownFolderType.Downloads).Path
|
||||
};
|
||||
bool dialogResult = saveFileDialog.ShowDialog() ?? false;
|
||||
@@ -139,12 +114,17 @@ namespace ZuneModdingHelper.Pages
|
||||
if (dialogResult)
|
||||
{
|
||||
_downloadedUpdatePath = saveFileDialog.FileName;
|
||||
File.Copy(downloadedFile, _downloadedUpdatePath, true);
|
||||
|
||||
// Download new version to requested folder with progress updates
|
||||
Progress<double> progress = new(p => prog.Progress = p);
|
||||
prog.Progress = 0;
|
||||
prog.IsIndeterminate = false;
|
||||
await _updateService.DownloadUpdate(_updateInfo, _downloadedUpdatePath, progress);
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
||||
{
|
||||
Title = UPDATES_DIALOG_TITLE,
|
||||
Description = "Update downloaded. Would you like to install the new version?",
|
||||
Description = "Update downloaded. Would you like to launch the new version?",
|
||||
AffirmativeText = "YES",
|
||||
NegativeText = "NO",
|
||||
ShowAffirmativeButton = true,
|
||||
@@ -152,8 +132,6 @@ namespace ZuneModdingHelper.Pages
|
||||
OnAction = new AsyncRelayCommand<bool>(OnLaunchUpdateDialogResult)
|
||||
}));
|
||||
}
|
||||
|
||||
File.Delete(downloadedFile);
|
||||
}
|
||||
|
||||
private async Task OnLaunchUpdateDialogResult(bool accepted)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using Octokit;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZuneModCore;
|
||||
|
||||
namespace ZuneModdingHelper.Services;
|
||||
|
||||
public class GitHubReleasesUpdateService(IGitHubClient gitHub) : IUpdateService
|
||||
{
|
||||
public async Task<UpdateAvailableInfo> FetchAvailableUpdate(CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get releases list from GitHub
|
||||
// Why not use the `releases/latest` endpoint? Good question: https://github.com/octokit/octokit.net/issues/2916
|
||||
var releases = await gitHub.Repository.Release.GetAll("ZuneDev", "ZuneModdingHelper");
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var latest = releases
|
||||
.Select(r => new GitHubUpdateAvailableInfo(r))
|
||||
.OrderByDescending(t => t.Version)
|
||||
.ThenBy(t => t.Release.Prerelease)
|
||||
.First();
|
||||
|
||||
if (App.Version < latest.Version)
|
||||
return latest;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task DownloadUpdate(UpdateAvailableInfo u, string downloadedFile, IProgress<double> progress, CancellationToken token = default)
|
||||
{
|
||||
if (u is not GitHubUpdateAvailableInfo update)
|
||||
throw new ArgumentException("update");
|
||||
|
||||
if (File.Exists(downloadedFile))
|
||||
File.Delete(downloadedFile);
|
||||
|
||||
using var client = new System.Net.WebClient();
|
||||
|
||||
client.DownloadProgressChanged += (sender, e) =>
|
||||
{
|
||||
// Update caller with download progress
|
||||
progress.Report(e.ProgressPercentage / 100d);
|
||||
};
|
||||
|
||||
// TODO: Select appropriate asset
|
||||
var asset = update.Release.Assets[0];
|
||||
|
||||
await client.DownloadFileTaskAsync(new Uri(asset.BrowserDownloadUrl), downloadedFile);
|
||||
}
|
||||
|
||||
private record GitHubUpdateAvailableInfo(string Name, ReleaseVersion Version, DateTimeOffset? ReleasedAt, Release Release)
|
||||
: UpdateAvailableInfo(Name, Version, ReleasedAt)
|
||||
{
|
||||
public GitHubUpdateAvailableInfo(Release release)
|
||||
: this(release.Name, ReleaseVersion.Parse(release.TagName), release.PublishedAt, release)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZuneModCore;
|
||||
|
||||
namespace ZuneModdingHelper.Services;
|
||||
|
||||
public interface IUpdateService
|
||||
{
|
||||
Task<UpdateAvailableInfo> FetchAvailableUpdate(CancellationToken token = default);
|
||||
|
||||
Task DownloadUpdate(UpdateAvailableInfo update, string destinationDirectory, IProgress<double> progress, CancellationToken token = default);
|
||||
}
|
||||
|
||||
public record UpdateAvailableInfo(string Name, ReleaseVersion Version, DateTimeOffset? ReleasedAt);
|
||||
Reference in New Issue
Block a user