mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-16 21:35:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d7adf3944 | ||
|
|
fb7399adf8 | ||
|
|
6ebbffdda1 | ||
|
|
2c35333cac | ||
|
|
fe5e38b216 | ||
|
|
9fd7d8b62a | ||
|
|
a9aa73033f | ||
|
|
73dfca0bc0 | ||
|
|
cdc9b6b011 | ||
|
|
bbdd365a28 | ||
|
|
ec19ce6561 | ||
|
|
9e62860d8d |
@@ -20,5 +20,7 @@ namespace Daybreak.Configuration
|
||||
public string ProtectedPassword { get; set; }
|
||||
[JsonProperty("AddressBarReadonly")]
|
||||
public bool AddressBarReadonly { get; set; } = true;
|
||||
[JsonProperty("ExperimentalFeatures")]
|
||||
public ExperimentalFeatures ExperimentalFeatures { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Daybreak.Configuration
|
||||
{
|
||||
public sealed class ExperimentalFeatures
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Daybreak.Services.Configuration;
|
||||
using Daybreak.Services.Credentials;
|
||||
using Daybreak.Services.Logging;
|
||||
using Daybreak.Services.Screenshots;
|
||||
using Daybreak.Services.Updater;
|
||||
using Daybreak.Services.ViewManagement;
|
||||
using Daybreak.Views;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
@@ -29,7 +30,7 @@ namespace Daybreak.Configuration
|
||||
serviceProducer.RegisterSingleton<IScreenshotProvider, ScreenshotProvider>();
|
||||
serviceProducer.RegisterSingleton<IConfigurationManager, ConfigurationManager>();
|
||||
serviceProducer.RegisterSingleton<IBloogumClient, BloogumClient>();
|
||||
serviceProducer.RegisterSingleton<CoreWebView2Environment, CoreWebView2Environment>((sp) => TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null)));
|
||||
serviceProducer.RegisterSingleton<IApplicationUpdater, ApplicationUpdater>();
|
||||
}
|
||||
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
|
||||
{
|
||||
@@ -37,6 +38,7 @@ namespace Daybreak.Configuration
|
||||
|
||||
applicationLifetimeProducer.RegisterService<ILoggingDatabase>();
|
||||
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
|
||||
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
|
||||
}
|
||||
public static void RegisterViews(IViewProducer viewProducer)
|
||||
{
|
||||
@@ -44,6 +46,8 @@ namespace Daybreak.Configuration
|
||||
|
||||
viewProducer.RegisterView<MainView>();
|
||||
viewProducer.RegisterView<SettingsView>();
|
||||
viewProducer.RegisterView<AskUpdateView>();
|
||||
viewProducer.RegisterView<UpdateView>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"></wv2:WebView2>
|
||||
<Grid Grid.Row="1" Background="#80808080">
|
||||
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></wv2:WebView2>
|
||||
<Grid Grid.Row="1" Background="#80808080" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
@@ -51,15 +52,32 @@
|
||||
Grid.Column="1" IsReadOnly="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=OneWay}" Background="Transparent"
|
||||
BorderThickness="1" VerticalAlignment="Center" VerticalContentAlignment="Center"
|
||||
BorderBrush="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
PreviewKeyDown="TextBox_PreviewKeyDown"></TextBox>
|
||||
PreviewKeyDown="TextBox_PreviewKeyDown"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></TextBox>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<local:StarGlyph Height="30" Width="30" Margin="5"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="StarGlyph_Clicked" x:Name="FavoriteButton"></local:StarGlyph>
|
||||
<local:MaximizeButton Height="30" Width="30" Margin="5"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="MaximizeButton_Clicked"></local:MaximizeButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.RowSpan="2" Background="Gray"
|
||||
Visibility="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay,Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Foreground="White" Text="Browser not supported." FontSize="26" TextWrapping="Wrap"></TextBlock>
|
||||
<TextBlock Foreground="White" Text="Download the Evergreen Bootstrapper from here:" FontSize="26" TextWrapping="Wrap" />
|
||||
<TextBox Background="Transparent" BorderBrush="Transparent" BorderThickness="0"
|
||||
Text="https://go.microsoft.com/fwlink/p/?LinkId=2124703"
|
||||
IsReadOnly="True" Margin="0, 0, 0, 30" FontSize="22" Foreground="Blue"
|
||||
PreviewMouseLeftButtonDown="Hyperlink_PreviewMouseLeftButtonDown" Cursor="Hand"
|
||||
TextWrapping="Wrap"></TextBox>
|
||||
<TextBlock Foreground="White" Text="Restart after the installation." FontSize="26" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using Daybreak.Launch;
|
||||
using Daybreak.Services.Configuration;
|
||||
using Daybreak.Services.Logging;
|
||||
using Daybreak.Utils;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
@@ -14,16 +18,20 @@ namespace Daybreak.Controls
|
||||
/// </summary>
|
||||
public partial class ChromiumBrowserWrapper : UserControl
|
||||
{
|
||||
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
|
||||
|
||||
public readonly static DependencyProperty AddressProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
|
||||
public readonly static DependencyProperty FavoriteAddressProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(FavoriteAddress));
|
||||
public readonly static DependencyProperty NavigatingProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(Navigating));
|
||||
public readonly static DependencyProperty AddressBarReadonlyProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(AddressBarReadonly));
|
||||
public readonly static DependencyProperty BrowserSupportedProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(BrowserSupported), new PropertyMetadata(true));
|
||||
|
||||
public event EventHandler<string> FavoriteUriChanged;
|
||||
public event EventHandler MaximizeClicked;
|
||||
|
||||
private readonly CoreWebView2Environment coreWebView2Environment;
|
||||
private readonly IConfigurationManager configurationManager;
|
||||
private readonly ILogger logger;
|
||||
private CoreWebView2Environment coreWebView2Environment;
|
||||
|
||||
public string Address
|
||||
{
|
||||
@@ -45,12 +53,18 @@ namespace Daybreak.Controls
|
||||
get => this.GetTypedValue<bool>(AddressBarReadonlyProperty);
|
||||
private set => this.SetTypedValue<bool>(AddressBarReadonlyProperty, value);
|
||||
}
|
||||
public bool BrowserSupported
|
||||
{
|
||||
get => this.GetTypedValue<bool>(BrowserSupportedProperty);
|
||||
private set => this.SetTypedValue<bool>(BrowserSupportedProperty, value);
|
||||
}
|
||||
|
||||
public ChromiumBrowserWrapper()
|
||||
{
|
||||
this.coreWebView2Environment = Launcher.ApplicationServiceManager.GetService<CoreWebView2Environment>();
|
||||
this.configurationManager = Launcher.ApplicationServiceManager.GetService<IConfigurationManager>();
|
||||
this.logger = Launcher.ApplicationServiceManager.GetService<ILogger>();
|
||||
this.InitializeComponent();
|
||||
this.InitializeEnvironment();
|
||||
this.InitializeBrowser();
|
||||
}
|
||||
|
||||
@@ -68,13 +82,51 @@ namespace Daybreak.Controls
|
||||
this.InitializeBrowser();
|
||||
}
|
||||
|
||||
private async void InitializeBrowser()
|
||||
private void InitializeEnvironment()
|
||||
{
|
||||
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
|
||||
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
|
||||
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
|
||||
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
|
||||
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
|
||||
try
|
||||
{
|
||||
this.coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
|
||||
this.BrowserSupported = true;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
this.logger.LogWarning($"Browser initialization failed. Details: {e}");
|
||||
this.BrowserSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeBrowser()
|
||||
{
|
||||
if (this.BrowserSupported is true)
|
||||
{
|
||||
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
|
||||
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
|
||||
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
|
||||
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
|
||||
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async void RetryInitializeButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.InitializeEnvironment();
|
||||
try
|
||||
{
|
||||
await this.InitializeBrowser();
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.BrowserSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Hyperlink_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (Uri.TryCreate(BrowserDownloadLink, UriKind.Absolute, out var uri))
|
||||
{
|
||||
Process.Start("explorer.exe", uri.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
|
||||
<Version>0.1.3</Version>
|
||||
<Version>0.3.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Daybreak.Services.Bloogum;
|
||||
using Daybreak.Services.Screenshots;
|
||||
using Daybreak.Services.Updater;
|
||||
using Daybreak.Services.ViewManagement;
|
||||
using Daybreak.Views;
|
||||
using Pepa.Wpf.Utilities;
|
||||
@@ -26,6 +27,7 @@ namespace Daybreak.Launch
|
||||
private readonly IViewManager viewManager;
|
||||
private readonly IScreenshotProvider screenshotProvider;
|
||||
private readonly IBloogumClient bloogumClient;
|
||||
private readonly IApplicationUpdater applicationUpdater;
|
||||
private readonly CancellationTokenSource cancellationToken = new();
|
||||
|
||||
public string CreditText
|
||||
@@ -37,18 +39,20 @@ namespace Daybreak.Launch
|
||||
public MainWindow(
|
||||
IViewManager viewManager,
|
||||
IScreenshotProvider screenshotProvider,
|
||||
IBloogumClient bloogumClient)
|
||||
IBloogumClient bloogumClient,
|
||||
IApplicationUpdater applicationUpdater)
|
||||
{
|
||||
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
|
||||
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
|
||||
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
|
||||
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.viewManager.ShowView<MainView>();
|
||||
this.SetupImageCycle();
|
||||
this.CheckForUpdates();
|
||||
}
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
@@ -145,6 +149,19 @@ namespace Daybreak.Launch
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckForUpdates()
|
||||
{
|
||||
var updateAvailable = await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
|
||||
if (updateAvailable)
|
||||
{
|
||||
this.viewManager.ShowView<AskUpdateView>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.viewManager.ShowView<MainView>();
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetAverageColor(BitmapSource bitmap)
|
||||
{
|
||||
var format = bitmap.Format;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Daybreak.Models
|
||||
{
|
||||
public enum ExecutionPolicies
|
||||
{
|
||||
AllSigned,
|
||||
Bypass,
|
||||
Default,
|
||||
RemoteSigned,
|
||||
Restricted,
|
||||
Undefined,
|
||||
Unrestricted
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Daybreak.Models
|
||||
{
|
||||
public sealed class UpdateStatus : INotifyPropertyChanged
|
||||
{
|
||||
public static readonly UpdateStep StartingStep = new("Starting");
|
||||
public static readonly UpdateStep CheckingLatestVersion = new("Checking latest version");
|
||||
public static UpdateStep Downloading(double progress) => new DownloadUpdateStep("Downloading", progress);
|
||||
public static readonly UpdateStep DownloadFinished = new("Download finished. Application will restart in order to apply the update.");
|
||||
|
||||
private UpdateStep currentStep = StartingStep;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public UpdateStep CurrentStep
|
||||
{
|
||||
get => this.currentStep;
|
||||
set
|
||||
{
|
||||
this.currentStep = value;
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateStep
|
||||
{
|
||||
public string Name { get; }
|
||||
internal UpdateStep(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
}
|
||||
public class DownloadUpdateStep : UpdateStep
|
||||
{
|
||||
internal DownloadUpdateStep(string name, double progress) : base(name)
|
||||
{
|
||||
this.Progress = progress;
|
||||
}
|
||||
|
||||
public double Progress { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using Daybreak.Configuration;
|
||||
using Daybreak.Exceptions;
|
||||
using Daybreak.Services.Logging;
|
||||
using Daybreak.Utils;
|
||||
using System;
|
||||
using System.Extensions;
|
||||
using System.IO;
|
||||
|
||||
namespace Daybreak.Services.Configuration
|
||||
@@ -11,9 +13,11 @@ namespace Daybreak.Services.Configuration
|
||||
private const string ConfigName = "Daybreak.config.json";
|
||||
|
||||
private ApplicationConfiguration applicationConfiguration;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public ConfigurationManager()
|
||||
public ConfigurationManager(ILogger logger)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
||||
try
|
||||
{
|
||||
var serializedConfig = File.ReadAllText(ConfigName);
|
||||
@@ -21,7 +25,8 @@ namespace Daybreak.Services.Configuration
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new FatalException("Failed to load application configuration. See inner exception for details", e);
|
||||
this.logger.LogWarning($"No configuration detected. Loading default configuration. Details: {e}");
|
||||
this.applicationConfiguration = new ApplicationConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Services.Logging;
|
||||
using Daybreak.Utils;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Daybreak.Services.Updater
|
||||
{
|
||||
public sealed class ApplicationUpdater : IApplicationUpdater
|
||||
{
|
||||
private const string ExecutionPolicyKey = "ExecutionPolicy";
|
||||
private const string UpdatedKey = "Updating";
|
||||
private const string RegistryKey = "Daybreak";
|
||||
private const string ExtractAndRunPs1 = "ExtractAndRun.ps1";
|
||||
private const string TempFile = "tempfile.zip";
|
||||
private const string VersionTag = "{VERSION}";
|
||||
private const string InputFileTag = "{INPUTFILE}";
|
||||
private const string OutputPathTag = "{OUTPUTPATH}";
|
||||
private const string ExecutionPolicyTag = "{EXECUTIONPOLICY}";
|
||||
private const string ProcessIdTag = "{PROCESSID}";
|
||||
private const string Url = "https://github.com/AlexMacocian/Daybreak/releases/latest";
|
||||
private const string DownloadUrl = $"https://github.com/AlexMacocian/Daybreak/releases/download/v{VersionTag}/Daybreakv{VersionTag}.zip";
|
||||
private const string GetExecutionPolicyCommand = "Get-ExecutionPolicy -Scope CurrentUser";
|
||||
private const string SetExecutionPolicyCommand = $"Set-ExecutionPolicy {ExecutionPolicyTag} -Scope CurrentUser";
|
||||
private const string WaitCommand = $"Wait-Process -Id {ProcessIdTag}";
|
||||
private const string ExtractCommandTemplate = $"Expand-Archive -Path '{InputFileTag}' -DestinationPath '{OutputPathTag}' -Force";
|
||||
private const string RunClientCommand = @".\Daybreak.exe";
|
||||
private const string RemoveTempFile = $"Remove-item {TempFile}";
|
||||
private const string RemovePs1 = $"Remove-item {ExtractAndRunPs1}";
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly HttpClient httpClient = new();
|
||||
|
||||
public string CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
public ApplicationUpdater(ILogger logger)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadUpdate(UpdateStatus updateStatus)
|
||||
{
|
||||
updateStatus.CurrentStep = UpdateStatus.CheckingLatestVersion;
|
||||
var latestVersion = (await this.GetLatestVersion()).ExtractValue();
|
||||
if (latestVersion is null)
|
||||
{
|
||||
this.logger.LogWarning("Failed to retrieve latest version. Aborting update");
|
||||
return false;
|
||||
}
|
||||
|
||||
using var downloadLatestResponse = await this.httpClient.GetAsync(
|
||||
DownloadUrl.Replace(VersionTag, latestVersion));
|
||||
|
||||
if (downloadLatestResponse.IsSuccessStatusCode is false)
|
||||
{
|
||||
this.logger.LogWarning("Failed to download latest version. Aborting udpate");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.LogInformation("Beginning update download");
|
||||
var downloadStream = await downloadLatestResponse.Content.ReadAsStreamAsync();
|
||||
var fileStream = File.OpenWrite(TempFile);
|
||||
var downloadSize = (double)downloadStream.Length;
|
||||
var buffer = new byte[1024];
|
||||
var length = 0;
|
||||
double downloaded = 0;
|
||||
var tickTime = DateTime.Now;
|
||||
while (downloadStream.CanRead && (length = await downloadStream.ReadAsync(buffer)) > 0)
|
||||
{
|
||||
downloaded += length;
|
||||
await fileStream.WriteAsync(buffer, 0, length);
|
||||
if ((DateTime.Now - tickTime).TotalMilliseconds > 50)
|
||||
{
|
||||
tickTime = DateTime.Now;
|
||||
updateStatus.CurrentStep = UpdateStatus.Downloading(downloaded / downloadSize);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus.CurrentStep = UpdateStatus.Downloading(1);
|
||||
updateStatus.CurrentStep = UpdateStatus.DownloadFinished;
|
||||
fileStream.Close();
|
||||
this.logger.LogInformation("Downloaded update");
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAvailable()
|
||||
{
|
||||
var version = string.Join('.', this.CurrentVersion.Split('.').Take(3));
|
||||
var maybeLatestVersion = await this.GetLatestVersion();
|
||||
return maybeLatestVersion.Switch(
|
||||
onSome: latestVersion => string.Compare(version, latestVersion, true) < 0,
|
||||
onNone: () =>
|
||||
{
|
||||
this.logger.LogWarning("Failed to retrieve latest version");
|
||||
return false;
|
||||
}).ExtractValue();
|
||||
}
|
||||
|
||||
public void FinalizeUpdate()
|
||||
{
|
||||
var maybeExecutionPolicy = this.RetrieveExecutionPolicy();
|
||||
maybeExecutionPolicy.DoAny(
|
||||
onNone: () =>
|
||||
{
|
||||
throw new InvalidOperationException("Failed to retrieve execution policy");
|
||||
});
|
||||
|
||||
var executionPolicy = maybeExecutionPolicy.ExtractValue();
|
||||
if (executionPolicy is not ExecutionPolicies.Bypass ||
|
||||
executionPolicy is not ExecutionPolicies.Unrestricted)
|
||||
{
|
||||
this.logger.LogInformation($"Execution policy is set to {executionPolicy}. Setting to {ExecutionPolicies.Bypass}");
|
||||
}
|
||||
|
||||
SaveExecutionPolicyValueToRegistry(executionPolicy);
|
||||
MarkUpdateInRegistry();
|
||||
this.SetExecutionPolicy(ExecutionPolicies.Bypass);
|
||||
this.LaunchExtractor();
|
||||
}
|
||||
|
||||
public void OnStartup()
|
||||
{
|
||||
if (UpdateMarkedInRegistry())
|
||||
{
|
||||
UnmarkUpdateInRegistry();
|
||||
var maybeExecutionPolicy = LoadExecutionPolicyValueFromRegistry();
|
||||
maybeExecutionPolicy.Do(
|
||||
onSome: policy =>
|
||||
{
|
||||
SetExecutionPolicy(policy);
|
||||
},
|
||||
onNone: () =>
|
||||
{
|
||||
throw new InvalidOperationException("Found update marked in registry but no execution policy");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClosing()
|
||||
{
|
||||
}
|
||||
|
||||
private async Task<Optional<string>> GetLatestVersion()
|
||||
{
|
||||
using var response = await this.httpClient.GetAsync(Url);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var versionTag = response.RequestMessage.RequestUri.ToString().Split('/').Last().TrimStart('v');
|
||||
return versionTag;
|
||||
}
|
||||
|
||||
return Optional.None<string>();
|
||||
}
|
||||
|
||||
private Optional<ExecutionPolicies> RetrieveExecutionPolicy()
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell",
|
||||
Arguments = GetExecutionPolicyCommand,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
this.logger.LogInformation("Checking current execution policy");
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
if (!Enum.TryParse(typeof(ExecutionPolicies), output, out var executionPolicy))
|
||||
{
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
this.logger.LogError($"Failed to retrieve current user execution policy. Stdout: {output}. Stderr: {error}");
|
||||
return Optional.None<ExecutionPolicies>();
|
||||
}
|
||||
|
||||
return executionPolicy.Cast<ExecutionPolicies>();
|
||||
}
|
||||
|
||||
private void SetExecutionPolicy(ExecutionPolicies executionPolicy)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell",
|
||||
Arguments = SetExecutionPolicyCommand.Replace(ExecutionPolicyTag, executionPolicy.ToString()),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
this.logger.LogInformation($"Setting execution policy to {executionPolicy}");
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
if (!string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
throw new InvalidOperationException($"Failed to set execution policy to {executionPolicy}. Stdout: {output}. Stderr: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchExtractor()
|
||||
{
|
||||
File.WriteAllLines(ExtractAndRunPs1, new List<string>()
|
||||
{
|
||||
WaitCommand.Replace(ProcessIdTag, Environment.ProcessId.ToString()),
|
||||
ExtractCommandTemplate
|
||||
.Replace(InputFileTag, Path.GetFullPath(TempFile))
|
||||
.Replace(OutputPathTag, Directory.GetCurrentDirectory()),
|
||||
RemoveTempFile,
|
||||
RemovePs1,
|
||||
RunClientCommand
|
||||
});
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = $@"{Directory.GetCurrentDirectory()}\{ExtractAndRunPs1}",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
WindowStyle = ProcessWindowStyle.Maximized,
|
||||
WorkingDirectory = Directory.GetCurrentDirectory()
|
||||
},
|
||||
};
|
||||
this.logger.LogInformation("Created extractor script. Attempting to launch powershell");
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create and start powershell script");
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkUpdateInRegistry()
|
||||
{
|
||||
var homeRegistryKey = GetOrCreateHomeKey();
|
||||
homeRegistryKey.SetValue(UpdatedKey, true);
|
||||
homeRegistryKey.Close();
|
||||
}
|
||||
|
||||
private static void UnmarkUpdateInRegistry()
|
||||
{
|
||||
var homeRegistryKey = GetOrCreateHomeKey();
|
||||
homeRegistryKey.SetValue(UpdatedKey, false);
|
||||
homeRegistryKey.Close();
|
||||
}
|
||||
|
||||
private static bool UpdateMarkedInRegistry()
|
||||
{
|
||||
var homeRegistryKey = GetOrCreateHomeKey();
|
||||
var update = homeRegistryKey.GetValue(UpdatedKey);
|
||||
homeRegistryKey.Close();
|
||||
if (update is string updateString)
|
||||
{
|
||||
if (bool.TryParse(updateString, out var updateValue))
|
||||
{
|
||||
return updateValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Found update value {updateString} in registry");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SaveExecutionPolicyValueToRegistry(ExecutionPolicies executionPolicy)
|
||||
{
|
||||
var homeRegistryKey = GetOrCreateHomeKey();
|
||||
homeRegistryKey.SetValue(ExecutionPolicyKey, executionPolicy.ToString());
|
||||
homeRegistryKey.Close();
|
||||
}
|
||||
|
||||
private static Optional<ExecutionPolicies> LoadExecutionPolicyValueFromRegistry()
|
||||
{
|
||||
var homeRegistryKey = GetOrCreateHomeKey();
|
||||
var executionPolicy = homeRegistryKey.GetValue(ExecutionPolicyKey);
|
||||
homeRegistryKey.Close();
|
||||
|
||||
if (executionPolicy is null)
|
||||
{
|
||||
return Optional.None<ExecutionPolicies>();
|
||||
}
|
||||
else if (executionPolicy is string executionPolicyString)
|
||||
{
|
||||
if (Enum.TryParse<ExecutionPolicies>(executionPolicyString, out var executionPolicyValue))
|
||||
{
|
||||
return executionPolicyValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Found execution policy with value {executionPolicy}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Found execution policy of type {executionPolicy.GetType()}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static RegistryKey GetOrCreateHomeKey()
|
||||
{
|
||||
var homeRegistryKey = Registry.CurrentUser.OpenSubKey("Software", true).OpenSubKey(RegistryKey, true);
|
||||
if (homeRegistryKey is null)
|
||||
{
|
||||
homeRegistryKey = Registry.CurrentUser.OpenSubKey("Software", true).CreateSubKey(RegistryKey, true);
|
||||
}
|
||||
|
||||
return homeRegistryKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Services.ApplicationLifetime;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Daybreak.Services.Updater
|
||||
{
|
||||
public interface IApplicationUpdater : IApplicationLifetimeService
|
||||
{
|
||||
string CurrentVersion { get; }
|
||||
void FinalizeUpdate();
|
||||
Task<bool> UpdateAvailable();
|
||||
Task<bool> DownloadUpdate(UpdateStatus updateStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<UserControl x:Class="Daybreak.Views.AskUpdateView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Views"
|
||||
mc:Ignorable="d"
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Width="400" Height="200"
|
||||
Background="White">
|
||||
<TextBlock Text="An update has been detected. Do you wish to update?" FontSize="20" TextWrapping="Wrap"
|
||||
Foreground="Black"></TextBlock>
|
||||
<controls:OpaqueButton Text="No" Foreground="Black" TransparentBackground="Gray" BackgroundOpacity="0.2" Width="50" Height="30"
|
||||
FontSize="16" HorizontalAlignment="Center" Margin="0, 0, 80, 0"
|
||||
Clicked="NoButton_Clicked"></controls:OpaqueButton>
|
||||
<controls:OpaqueButton Text="Yes" Foreground="Black" TransparentBackground="Gray" BackgroundOpacity="0.2" Width="50" Height="30"
|
||||
FontSize="16" HorizontalAlignment="Center" Margin="80, 0, 0, 0"
|
||||
Clicked="YesButton_Clicked"></controls:OpaqueButton>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,42 @@
|
||||
using Daybreak.Services.Logging;
|
||||
using Daybreak.Services.Updater;
|
||||
using Daybreak.Services.ViewManagement;
|
||||
using Daybreak.Utils;
|
||||
using System.Extensions;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AskUpdateView.xaml
|
||||
/// </summary>
|
||||
public partial class AskUpdateView : UserControl
|
||||
{
|
||||
private readonly IApplicationUpdater applicationUpdater;
|
||||
private readonly ILogger logger;
|
||||
private readonly IViewManager viewManager;
|
||||
|
||||
public AskUpdateView(
|
||||
IApplicationUpdater applicationUpdater,
|
||||
ILogger logger,
|
||||
IViewManager viewManager)
|
||||
{
|
||||
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
|
||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
||||
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void NoButton_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
this.logger.LogInformation("User declined update");
|
||||
this.viewManager.ShowView<MainView>();
|
||||
}
|
||||
|
||||
private void YesButton_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
this.logger.LogInformation("User accepted update");
|
||||
this.viewManager.ShowView<UpdateView>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<UserControl x:Class="Daybreak.Views.UpdateView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Views"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
Loaded="UpdateView_Loaded"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Background="White" MinHeight="200" MinWidth="400">
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=Description, Mode=OneWay}" Margin="10, 0, 10, 10" Foreground="Black"
|
||||
TextWrapping="Wrap"></TextBlock>
|
||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding ElementName=_this, Path=ProgressValue, Mode=OneWay}" Width="300" Height="20"></ProgressBar>
|
||||
<controls:OpaqueButton Text="Ok" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="30" Height="20"
|
||||
IsEnabled="{Binding ElementName=_this, Path=ContinueButtonEnabled, Mode=OneWay}"
|
||||
Clicked="OpaqueButton_Clicked" Foreground="Black"></controls:OpaqueButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,102 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Services.Logging;
|
||||
using Daybreak.Services.Updater;
|
||||
using Daybreak.Services.ViewManagement;
|
||||
using Daybreak.Utils;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
|
||||
namespace Daybreak.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for UpdateView.xaml
|
||||
/// </summary>
|
||||
public partial class UpdateView : UserControl
|
||||
{
|
||||
public readonly static DependencyProperty DescriptionProperty =
|
||||
DependencyPropertyExtensions.Register<UpdateView, string>(nameof(Description));
|
||||
public readonly static DependencyProperty ProgressValueProperty =
|
||||
DependencyPropertyExtensions.Register<UpdateView, double>(nameof(ProgressValue));
|
||||
public readonly static DependencyProperty ContinueButtonEnabledProperty =
|
||||
DependencyPropertyExtensions.Register<UpdateView, bool>(nameof(ContinueButtonEnabled));
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly IViewManager viewManager;
|
||||
private readonly IApplicationUpdater applicationUpdater;
|
||||
private readonly UpdateStatus updateStatus = new();
|
||||
|
||||
private bool success = false;
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => this.GetTypedValue<string>(DescriptionProperty);
|
||||
set => this.SetValue(DescriptionProperty, value);
|
||||
}
|
||||
public double ProgressValue
|
||||
{
|
||||
get => this.GetTypedValue<double>(ProgressValueProperty);
|
||||
set => this.SetValue(ProgressValueProperty, value);
|
||||
}
|
||||
public bool ContinueButtonEnabled
|
||||
{
|
||||
get => this.GetTypedValue<bool>(ContinueButtonEnabledProperty);
|
||||
set => this.SetValue(ContinueButtonEnabledProperty, value);
|
||||
}
|
||||
|
||||
public UpdateView(
|
||||
IApplicationUpdater applicationUpdater,
|
||||
ILogger logger,
|
||||
IViewManager viewManager)
|
||||
{
|
||||
this.applicationUpdater = applicationUpdater;
|
||||
this.logger = logger;
|
||||
this.viewManager = viewManager;
|
||||
this.updateStatus.PropertyChanged += UpdateStatus_PropertyChanged;
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void UpdateStatus_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
this.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (this.updateStatus.CurrentStep is UpdateStatus.DownloadUpdateStep downloadUpdateStep)
|
||||
{
|
||||
this.ProgressValue = downloadUpdateStep.Progress * 100;
|
||||
}
|
||||
|
||||
this.Description = this.updateStatus.CurrentStep.Name;
|
||||
});
|
||||
}
|
||||
|
||||
private async void UpdateView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.logger.LogInformation("Starting update procedure");
|
||||
var success = await applicationUpdater.DownloadUpdate(updateStatus).ConfigureAwait(true);
|
||||
if (success is false)
|
||||
{
|
||||
this.logger.LogError("Update procedure failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.success= true;
|
||||
this.logger.LogInformation("Downloaded update");
|
||||
}
|
||||
|
||||
this.ContinueButtonEnabled = true;
|
||||
}
|
||||
|
||||
private void OpaqueButton_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if (this.success)
|
||||
{
|
||||
this.applicationUpdater.FinalizeUpdate();
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.viewManager.ShowView<MainView>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
# Daybreak
|
||||
Custom client for Guildwars.
|
||||
Requires standalone version https://developer.microsoft.com/microsoft-edge/webview2.
|
||||
Requires webview2 runtime https://go.microsoft.com/fwlink/p/?LinkId=2124703.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Features
|
||||
Automatically detect if guildwars is running or not. Includes the ability to launch guildwars from the client.
|
||||
|
||||
Reference in New Issue
Block a user