Skip to content

Commit

Permalink
Check executables for fixes and updates (Closes #763) (#772)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexMacocian authored Jul 7, 2024
1 parent 6fd1dc0 commit 360963d
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 15 deletions.
1 change: 0 additions & 1 deletion Daybreak/Controls/CircularLoadingWidget.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,6 @@
<BeginStoryboard x:Name="ProgressAnimation_BeginStoryboard" Storyboard="{StaticResource ProgressAnimation}"/>
</EventTrigger>
</UserControl.Triggers>

<Viewbox>
<Canvas x:Name="LayoutRoot" VerticalAlignment="Top" Height="88" Width="88">
<Grid Width="10.734" Height="10.004" Canvas.Left="38.614" Canvas.Top="0.331">
Expand Down
35 changes: 33 additions & 2 deletions Daybreak/Controls/Templates/GuildwarsPathTemplate.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
xmlns:converters="clr-namespace:Daybreak.Converters"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
xmlns:glyphs="clr-namespace:Daybreak.Controls.Glyphs"
Unloaded="UserControl_Unloaded"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"></converters:InverseBooleanConverter>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
<converters:BooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" TriggerValue="True" IsHidden="False"/>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" TriggerValue="False" IsHidden="False"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
Expand All @@ -34,10 +38,37 @@
ToolTip="Path to Gw.exe"/>
</Grid>
<WrapPanel Grid.Column="1">
<Grid Margin="5"
Height="30"
Width="30"
Visibility="{Binding ElementName=_this, Path=CheckingVersion, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Text="{Binding ElementName=_this, Path=UpdateProgress, Mode=OneWay}"
FontSize="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
<local:CircularLoadingWidget Width="30" />
</Grid>
<Grid Margin="5"
Visibility="{Binding ElementName=_this, Path=CheckingVersion, Mode=OneWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<Grid Visibility="{Binding ElementName=_this, Path=NoUpdateResult, Mode=OneWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<glyphs:GoodGlyph Height="30" Width="30" Foreground="{StaticResource MahApps.Brushes.Accent}"
Visibility="{Binding ElementName=_this, Path=UpToDate, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<buttons:BackButton Height="30" Width="30"
Visibility="{Binding ElementName=_this, Path=UpToDate, Mode=OneWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
RenderTransformOrigin="0.5 0.5"
ToolTip="Update executable"
Clicked="UpdateButton_Clicked">
<buttons:BackButton.RenderTransform>
<RotateTransform Angle="270"/>
</buttons:BackButton.RenderTransform>
</buttons:BackButton>
</Grid>
</Grid>
<buttons:FilePickerButton Width="30" Height="30" VerticalAlignment="Top" Margin="5"
Clicked="FilePickerGlyph_Clicked"
ToolTip="Open path dialog"/>
<buttons:BinButton Grid.Column="2" Height="30" Width="30" VerticalAlignment="Top" Margin="5"
<buttons:BinButton Height="30" Width="30" VerticalAlignment="Top" Margin="5"
Clicked="BinButton_Clicked"
ToolTip="Remove entry"/>
</WrapPanel>
Expand Down
150 changes: 147 additions & 3 deletions Daybreak/Controls/Templates/GuildwarsPathTemplate.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
using Daybreak.Models;
using Daybreak.Controls.Buttons;
using Daybreak.Models;
using Daybreak.Models.Progress;
using Daybreak.Models.Versioning;
using Daybreak.Services.GuildWars;
using Daybreak.Services.Navigation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Win32;
using System;
using System.Core.Extensions;
using System.Data;
using System.Extensions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
Expand All @@ -15,16 +25,57 @@ namespace Daybreak.Controls;
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class GuildwarsPathTemplate : UserControl
{
private readonly IGuildWarsInstaller guildWarsInstaller;

private CancellationTokenSource? tokenSource;

public event EventHandler? RemoveClicked;

[GenerateDependencyProperty]
private string path = string.Empty;
private bool noUpdateResult;
[GenerateDependencyProperty]
private bool checkingVersion;
[GenerateDependencyProperty]
private bool upToDate;
[GenerateDependencyProperty]
private string updateProgress = string.Empty;

public GuildwarsPathTemplate()
public GuildwarsPathTemplate() :
this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IGuildWarsInstaller>())
{
}

public GuildwarsPathTemplate(
IGuildWarsInstaller guildWarsInstaller)
{
this.guildWarsInstaller = guildWarsInstaller.ThrowIfNull();
this.InitializeComponent();
}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DataContextProperty)
{
if (e.OldValue is ExecutablePath oldPath)
{
oldPath.PropertyChanged -= this.ExecutablePath_PropertyChanged;
}

if (e.NewValue is ExecutablePath newPath)
{
newPath.PropertyChanged += this.ExecutablePath_PropertyChanged;
}

this.CheckExecutable();
}
}

private void ExecutablePath_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}

private void BinButton_Clicked(object sender, EventArgs e)
{
this.RemoveClicked?.Invoke(this, e);
Expand All @@ -42,6 +93,99 @@ private void FilePickerGlyph_Clicked(object sender, EventArgs e)
if (filePicker.ShowDialog() is true)
{
this.DataContext.As<ExecutablePath>()!.Path = filePicker.FileName;
this.CheckExecutable();
}
}

private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
this.tokenSource?.Dispose();
}

private void CheckExecutable()
{
if (this.DataContext is not ExecutablePath executablePath)
{
return;
}

this.tokenSource?.Dispose();
this.tokenSource = new CancellationTokenSource();
new TaskFactory().StartNew(async () =>
{
await this.Dispatcher.InvokeAsync(() => this.CheckingVersion = true);
await this.Dispatcher.InvokeAsync(() => this.NoUpdateResult = false);
if (await this.guildWarsInstaller.GetLatestVersionId(this.tokenSource.Token) is not int latestVersion)
{
await this.Dispatcher.InvokeAsync(() => this.CheckingVersion = false);
await this.Dispatcher.InvokeAsync(() => this.NoUpdateResult = true);
return;
}

if (await this.guildWarsInstaller.GetVersionId(executablePath.Path, this.tokenSource.Token) is not int version)
{
await this.Dispatcher.InvokeAsync(() => this.CheckingVersion = false);
await this.Dispatcher.InvokeAsync(() => this.NoUpdateResult = false);
await this.Dispatcher.InvokeAsync(() => this.UpToDate = false);
return;
}

await this.Dispatcher.InvokeAsync(() => this.CheckingVersion = false);
await this.Dispatcher.InvokeAsync(() => this.NoUpdateResult = false);
await this.Dispatcher.InvokeAsync(() => this.UpToDate = version == latestVersion);
}, this.tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}

private async void UpdateButton_Clicked(object sender, EventArgs e)
{
if (this.DataContext is not ExecutablePath path)
{
return;
}

if (sender is not BackButton updateButton)
{
return;
}

await this.Dispatcher.InvokeAsync(() => updateButton.IsEnabled = false);
await this.Dispatcher.InvokeAsync(() => this.CheckingVersion = true);
await this.Dispatcher.InvokeAsync(() => this.NoUpdateResult = false);
this.tokenSource?.Dispose();
this.tokenSource = new CancellationTokenSource();
var status = new GuildwarsInstallationStatus();
status.PropertyChanged += this.UpdateStatus_PropertyChanged;
try
{
var result = await this.guildWarsInstaller.UpdateGuildwars(path.Path, status, this.tokenSource.Token);
await this.Dispatcher.InvokeAsync(() => this.CheckingVersion = false);
await this.Dispatcher.InvokeAsync(() => this.NoUpdateResult = false);
await this.Dispatcher.InvokeAsync(() => this.UpToDate = result);
}
finally
{
status.PropertyChanged -= this.UpdateStatus_PropertyChanged;
}
}

private void UpdateStatus_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (sender is not GuildwarsInstallationStatus status)
{
return;
}

if (status.CurrentStep is DownloadStatus.DownloadProgressStep progressStep)
{
// TODO: Kinda hacky way to display a continuous progress widget
if (progressStep is GuildwarsInstallationStatus.UnpackingProgressStep)
{
this.Dispatcher.Invoke(() => this.UpdateProgress = $"{(int)(50 + (progressStep.Progress * 50))}%");
}
else
{
this.Dispatcher.Invoke(() => this.UpdateProgress = $"{(int)(progressStep.Progress * 50)}%");
}
}
}
}
2 changes: 1 addition & 1 deletion Daybreak/Models/Progress/DownloadStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal DownloadStep(string name) : base(name)
}
}

public sealed class DownloadProgressStep : DownloadStep
public class DownloadProgressStep : DownloadStep
{
public TimeSpan? ETA { get; set; }

Expand Down
10 changes: 9 additions & 1 deletion Daybreak/Models/Progress/GuildwarsInstallationStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public sealed class GuildwarsInstallationStatus : DownloadStatus
public static readonly LoadStatus StartingExecutable = new GuildwarsInstallationStep("Starting Guildwars. Finish the installation process and close the installer");
public static readonly LoadStatus UpdateFinished = new GuildwarsInstallationStep("Update has finished", true);
public static readonly LoadStatus Failed = new GuildwarsInstallationStep("Operation failed. Please check logs for details", true);
public static DownloadProgressStep Unpacking(double progress, TimeSpan? eta) => new("Unpacking", progress, eta);
public static UnpackingProgressStep Unpacking(double progress, TimeSpan? eta) => new(progress, eta);

public GuildwarsInstallationStatus()
{
Expand All @@ -24,4 +24,12 @@ internal GuildwarsInstallationStep(string name, bool final = false) : base(name)
this.Final = final;
}
}

public sealed class UnpackingProgressStep : DownloadProgressStep
{
public UnpackingProgressStep(double progress, TimeSpan? eta)
: base("Unpacking", progress, eta)
{
}
}
}
11 changes: 4 additions & 7 deletions Daybreak/Services/Guildwars/IntegratedGuildwarsInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,11 @@ private async Task<bool> UpdateGuildwarsInternal(string exePath, GuildwarsInstal
maybeContext = context;
var tempName = Path.Combine(StagingFolder, CompressedTempExeName.Replace(VersionPlaceholder, manifest.LatestExe.ToString()));
var cacheName = Path.Combine(StagingFolder, UncompressedTempExeName.Replace(VersionPlaceholder, manifest.LatestExe.ToString()));
if (File.Exists(cacheName))
if (File.Exists(cacheName) &&
await this.GetVersionId(cacheName, cancellationToken) is int cacheVersion &&
cacheVersion == manifest.LatestExe)
{
var fileInfo = new FileInfo(cacheName);
var fileResponse = await guildWarsClient.GetFileResponse(context, manifest.LatestExe, 0, cancellationToken);
if (fileInfo.Length == fileResponse.SizeDecompressed)
{
return cacheName;
}
return cacheName;
}

(var downloadResult, var expectedFinalSize) = await this.DownloadCompressedExecutable(tempName, guildWarsClient, context, manifest, installationStatus, cancellationToken);
Expand Down

0 comments on commit 360963d

Please sign in to comment.