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"
|
<Rectangle Height="5" Width="109" Margin="0,-16,0,0" Grid.ColumnSpan="2"
|
||||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||||
Fill="{StaticResource ZuneHorizontalGradientAlt}"/>
|
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}"
|
Foreground="{StaticResource ZuneMediumTextBrush}"
|
||||||
Visibility="{Binding Title, Converter={StaticResource NotNullToVis}}"/>
|
Visibility="{Binding Title, Converter={StaticResource NotNullToVis}}"/>
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
<Button Content="CHECK FOR UPDATES" Click="UpdateCheckButton_Click"
|
<Button Content="CHECK FOR UPDATES" Click="UpdateCheckButton_Click"
|
||||||
Margin="0,8,0,12" Padding="8,0" HorizontalAlignment="Left"
|
Margin="0,8,0,12" Padding="8,0" HorizontalAlignment="Left"
|
||||||
|
IsEnabled="{Binding EnableCheckForUpdates}"
|
||||||
Style="{StaticResource ActionButtonStyle}"/>
|
Style="{StaticResource ActionButtonStyle}"/>
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource ZuneBodyTextBlockStyle}">
|
<TextBlock Style="{StaticResource ZuneBodyTextBlockStyle}">
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Octokit;
|
|
||||||
using Syroot.Windows.IO;
|
using Syroot.Windows.IO;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using ZuneModCore;
|
|
||||||
using ZuneModdingHelper.Messages;
|
using ZuneModdingHelper.Messages;
|
||||||
|
using ZuneModdingHelper.Services;
|
||||||
|
|
||||||
namespace ZuneModdingHelper.Pages
|
namespace ZuneModdingHelper.Pages
|
||||||
{
|
{
|
||||||
@@ -21,15 +18,18 @@ namespace ZuneModdingHelper.Pages
|
|||||||
{
|
{
|
||||||
const string UPDATES_DIALOG_TITLE = "UPDATES";
|
const string UPDATES_DIALOG_TITLE = "UPDATES";
|
||||||
|
|
||||||
private readonly IGitHubClient? _gitHub = Ioc.Default.GetService<IGitHubClient>();
|
private readonly IUpdateService? _updateService = Ioc.Default.GetService<IUpdateService>();
|
||||||
private ReleaseAsset _latestAsset;
|
private UpdateAvailableInfo _updateInfo;
|
||||||
private string _downloadedUpdatePath;
|
private string _downloadedUpdatePath;
|
||||||
|
|
||||||
public AboutPage()
|
public AboutPage()
|
||||||
{
|
{
|
||||||
|
DataContext = this;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool EnableCheckForUpdates => _updateService is not null;
|
||||||
|
|
||||||
private void Link_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
private void Link_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||||
{
|
{
|
||||||
App.OpenInBrowser(e.Uri.AbsoluteUri);
|
App.OpenInBrowser(e.Uri.AbsoluteUri);
|
||||||
@@ -38,7 +38,7 @@ namespace ZuneModdingHelper.Pages
|
|||||||
|
|
||||||
private async void UpdateCheckButton_Click(object sender, System.Windows.RoutedEventArgs e)
|
private async void UpdateCheckButton_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_gitHub is null)
|
if (_updateService is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new ProgressDialogViewModel
|
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new ProgressDialogViewModel
|
||||||
@@ -51,16 +51,8 @@ namespace ZuneModdingHelper.Pages
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get releases list from GitHub
|
_updateInfo = await _updateService.FetchAvailableUpdate();
|
||||||
// Why not use the `releases/latest` endpoint? Good question: https://github.com/octokit/octokit.net/issues/2916
|
if (_updateInfo is null)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send<CloseDialogMessage>();
|
WeakReferenceMessenger.Default.Send<CloseDialogMessage>();
|
||||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
||||||
@@ -73,13 +65,12 @@ namespace ZuneModdingHelper.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Newer version available, prompt user to download
|
// Newer version available, prompt user to download
|
||||||
_latestAsset = latest.Release.Assets[0];
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send<CloseDialogMessage>();
|
WeakReferenceMessenger.Default.Send<CloseDialogMessage>();
|
||||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
||||||
{
|
{
|
||||||
Title = UPDATES_DIALOG_TITLE,
|
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",
|
AffirmativeText = "YES",
|
||||||
NegativeText = "NO",
|
NegativeText = "NO",
|
||||||
ShowAffirmativeButton = true,
|
ShowAffirmativeButton = true,
|
||||||
@@ -107,30 +98,14 @@ namespace ZuneModdingHelper.Pages
|
|||||||
IsIndeterminate = true,
|
IsIndeterminate = true,
|
||||||
ShowAffirmativeButton = false,
|
ShowAffirmativeButton = false,
|
||||||
ShowNegativeButton = false,
|
ShowNegativeButton = false,
|
||||||
Maximum = 100,
|
Maximum = 1.0,
|
||||||
};
|
};
|
||||||
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(prog));
|
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
|
// Ask user to save file
|
||||||
Microsoft.Win32.SaveFileDialog saveFileDialog = new()
|
Microsoft.Win32.SaveFileDialog saveFileDialog = new()
|
||||||
{
|
{
|
||||||
FileName = _latestAsset.Name,
|
FileName = _updateInfo.Name,
|
||||||
InitialDirectory = new KnownFolder(KnownFolderType.Downloads).Path
|
InitialDirectory = new KnownFolder(KnownFolderType.Downloads).Path
|
||||||
};
|
};
|
||||||
bool dialogResult = saveFileDialog.ShowDialog() ?? false;
|
bool dialogResult = saveFileDialog.ShowDialog() ?? false;
|
||||||
@@ -139,12 +114,17 @@ namespace ZuneModdingHelper.Pages
|
|||||||
if (dialogResult)
|
if (dialogResult)
|
||||||
{
|
{
|
||||||
_downloadedUpdatePath = saveFileDialog.FileName;
|
_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
|
WeakReferenceMessenger.Default.Send(new ShowDialogMessage(new DialogViewModel
|
||||||
{
|
{
|
||||||
Title = UPDATES_DIALOG_TITLE,
|
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",
|
AffirmativeText = "YES",
|
||||||
NegativeText = "NO",
|
NegativeText = "NO",
|
||||||
ShowAffirmativeButton = true,
|
ShowAffirmativeButton = true,
|
||||||
@@ -152,8 +132,6 @@ namespace ZuneModdingHelper.Pages
|
|||||||
OnAction = new AsyncRelayCommand<bool>(OnLaunchUpdateDialogResult)
|
OnAction = new AsyncRelayCommand<bool>(OnLaunchUpdateDialogResult)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Delete(downloadedFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnLaunchUpdateDialogResult(bool accepted)
|
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