Compare commits

...
19 Commits
Author SHA1 Message Date
amacocianandGitHub 71ab3fdefb Post-update actions (#57) 2022-08-19 12:45:07 +00:00
amacocianandGitHub 2f83d6c860 Delete browser cache on update (#56) 2022-08-18 20:17:36 +00:00
amacocianandGitHub 3db4975b48 Make launcher update the installer after an update (#55) 2022-08-18 19:59:08 +00:00
amacocianandGitHub 6a6eea5d02 Implement search bar for builds and skills (#50)
* Add search bars to builds and skills

* Fix search bar not appearing.
Improve search function.

* Increment version

* Rename version check so it can be added to status checks
2022-08-18 21:26:57 +02:00
amacocianandGitHub 8fe9eeb32f Icon downloader bugfixes (#47)
* Fix icon download to properly finish
2022-08-16 19:47:25 +02:00
amacocianandGitHub 4c1025243b Background Icon Downloader (#44)
* Download icons in the background

* Fix icon downloader browser setup
2022-08-16 17:51:35 +02:00
amacocianandGitHub f24eb02ca1 Pipeline to check that version is updated (#43)
* Pipeline to check that version is updated
2022-08-13 00:24:54 +02:00
amacocianandGitHub b01c09491f Download and check icons at startup (#41) 2022-08-12 23:41:22 +02:00
amacocianandGitHub 54ae742899 Increment version (#40) 2022-08-12 13:22:59 +02:00
amacocianandGitHub 4c862463d7 Fix IconRetriever (#38)
Use browser to bypass header issues with http client
2022-08-12 13:15:46 +02:00
amacocianandGitHub b455c89ddc Minor improvements (#36)
Deprecate Microsoft.Xaml.Behaviors.Wpf
Reverse version order in management view
2022-08-04 23:05:55 +02:00
amacocianandGitHub 782b76cd8e Remove admin mode when updating (#31) 2022-08-04 21:59:55 +02:00
amacocianandGitHub 230c2d9457 Setup executable installer (#29)
* Setup executable installer

* Increment update to 0.9.3

* Improve installer
2022-08-04 21:39:17 +02:00
amacocianandGitHub 63531e3b8c Hide console window (#25) 2022-08-04 19:52:54 +02:00
amacocianandGitHub ac430305f4 Update to .net 6 and update dependencies (#24)
* Update to .net 6 and update dependencies
2022-08-04 19:25:53 +02:00
amacocianandGitHub 3d830cfc16 Update dependencies (#23) 2021-07-10 13:39:00 +03:00
amacocianandGitHub b529aa8861 Fix logging on startup when ILogger is null (#22) 2021-07-10 12:45:44 +03:00
amacocianandGitHub 3798daa54e Update dependencies (#21)
* Update dependencies
* Minor nit fixes
* Fix UTs
2021-07-10 12:08:43 +03:00
amacocianandGitHub 624380ed86 Use IOptions instead of IConfigurationManager (#20)
* Use IOptions instead of dependency on IConfigurationManager
2021-06-22 15:48:37 +03:00
85 changed files with 1856 additions and 792 deletions
+7 -2
View File
@@ -52,7 +52,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.202'
dotnet-version: '6.x'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.1
@@ -70,11 +70,16 @@ jobs:
$version = .\Scripts\GetBuildVersion.ps1
echo "::set-env name=Version::$version"
- name: Create publish files
- name: Create publish launcher files
run: dotnet publish .\Daybreak\Daybreak.csproj -c $env:Configuration -r $env:RuntimeIdentifier -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained true -o .\Publish
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Create publish installer files
run: dotnet publish .\Daybreak.Installer\Daybreak.Installer.csproj -c $env:Configuration -r $env:RuntimeIdentifier -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained true -o .\Publish
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Pack publish files
run: |
Write-Host $env
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.202'
dotnet-version: '6.x'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.1
+45
View File
@@ -0,0 +1,45 @@
name: Daybreak Version Check
on:
pull_request:
branches:
- master
jobs:
check_version:
strategy:
matrix:
targetplatform: [x64]
runs-on: windows-latest
env:
Configuration: Release
Solution_Path: Daybreak.sln
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v1
- name: Build Daybreak project
run: dotnet build Daybreak -c $env:Configuration
- name: Set version variable
run: |
$version = .\Scripts\GetBuildVersion.ps1
echo "::set-env name=Version::$version"
- name: Check version difference
run: |
.\Scripts\CompareVersions -currentVersion ${{ env.Version }} -lastVersion ${{ env.LatestReleaseTag }}
env:
LatestReleaseTag: ${{ steps.getLatestTag.outputs.tag }}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+46
View File
@@ -0,0 +1,46 @@
// See https://aka.ms/new-console-template for more information
using System.Diagnostics;
using System.IO.Compression;
const string tempFile = "tempfile.zip";
const string executableName = "Daybreak.exe";
Console.Title = "Daybreak Installer";
Console.WriteLine("Starting installation...");
if (File.Exists(tempFile) is false)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Unable to find launcher package. Aborting installation");
Console.ReadKey();
return;
}
Console.WriteLine("Unpacking files...");
try
{
ZipFile.ExtractToDirectory(tempFile, AppContext.BaseDirectory, true);
}
catch
{
}
Console.WriteLine("Deleting package");
File.Delete(tempFile);
Console.WriteLine("Deleting browser caches");
Directory.Delete("BrowserData", true);
Directory.Delete("Daybreak.exe.WebView2", true);
Console.WriteLine("Launching application");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = executableName
}
};
if (process.Start() is false)
{
Console.WriteLine("Failed to launch application");
Console.ReadKey();
}
+8 -9
View File
@@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.0.0-alpha0002" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0-preview-20220707-01" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,63 @@
using Daybreak.Configuration;
using Daybreak.Services.Configuration;
using Daybreak.Services.Options;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
namespace Daybreak.Tests.Services
{
[TestClass]
public class ApplicationConfigurationOptionsManagerTests
{
private ApplicationConfigurationOptionsManager applicationConfigurationOptionsManager;
[TestInitialize]
public void TestInitialize()
{
var configurationManagerMock = new Mock<IConfigurationManager>();
configurationManagerMock
.Setup(u => u.GetConfiguration())
.Returns(new ApplicationConfiguration());
this.applicationConfigurationOptionsManager = new ApplicationConfigurationOptionsManager(configurationManagerMock.Object);
}
[TestMethod]
public void GetApplicationConfiguration_ReturnsObject()
{
var config = this.applicationConfigurationOptionsManager.GetOptions<ApplicationConfiguration>();
config.Should().NotBeNull();
}
[TestMethod]
public void GetOtherOptions_ThrowsInvalidOperationException()
{
var action = new Action(() =>
{
this.applicationConfigurationOptionsManager.GetOptions<object>();
});
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void UpdateOptions_OnApplicationConfiguration_Succeeds()
{
this.applicationConfigurationOptionsManager.UpdateOptions(new ApplicationConfiguration());
}
[TestMethod]
public void UpdateOptions_OnOthers_ThrowsInvalidOperationException()
{
var action = new Action(() =>
{
this.applicationConfigurationOptionsManager.UpdateOptions(new object());
});
action.Should().Throw<InvalidOperationException>();
}
}
}
@@ -3,10 +3,9 @@ using FluentAssertions;
using LiteDB;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Linq;
using WpfExtended.Logging;
using System.Logging;
namespace Daybreak.Tests.Services
{
+26 -2
View File
@@ -1,12 +1,28 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31005.135
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak", "Daybreak\Daybreak.csproj", "{AA45C2B1-8BD0-466C-9271-699F168905AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.Tests", "Daybreak.Tests\Daybreak.Tests.csproj", "{C911C1C2-6566-419C-89C7-F802EB3FE709}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipelines", "Pipelines", "{067BF93F-E5B2-4E99-886E-039C04F35EAB}"
ProjectSection(SolutionItems) = preProject
.github\workflows\cd.yaml = .github\workflows\cd.yaml
.github\workflows\ci.yaml = .github\workflows\ci.yaml
.github\workflows\version_check.yaml = .github\workflows\version_check.yaml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.Installer", "Daybreak.Installer\Daybreak.Installer.csproj", "{4E2BB805-135D-4F02-8C53-3D8B6876D323}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{41AE8C5D-25E1-4B08-8D65-868552421A63}"
ProjectSection(SolutionItems) = preProject
Scripts\BuildRelease.ps1 = Scripts\BuildRelease.ps1
Scripts\GetBuildVersion.ps1 = Scripts\GetBuildVersion.ps1
Scripts\CompareVersions.ps1 = Scripts\CompareVersions.ps1
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,6 +47,14 @@ Global
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|Any CPU.Build.0 = Release|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x64.ActiveCfg = Release|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x64.Build.0 = Release|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|Any CPU.Build.0 = Release|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|x64.ActiveCfg = Release|Any CPU
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+2 -2
View File
@@ -1,9 +1,9 @@
using System;
using Microsoft.Xaml.Behaviors;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Daybreak.Behaviors
+2 -2
View File
@@ -1,7 +1,7 @@
using System;
using Microsoft.Xaml.Behaviors;
using System;
using System.Extensions;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Daybreak.Behaviors
{
@@ -37,7 +37,5 @@ namespace Daybreak.Configuration
public bool PlaceShortcut { get; set; }
[JsonProperty("AutoCheckUpdate")]
public bool AutoCheckUpdate { get; set; } = true;
[JsonProperty("KeepLocalIconCache")]
public bool KeepLocalIconCache { get; set; } = true;
}
}
@@ -16,6 +16,8 @@ namespace Daybreak.Configuration
public bool LaunchGuildwarsAsCurrentUser { get; set; } = true;
[JsonProperty("CanInterceptKeys")]
public bool CanInterceptKeys { get; set; }
[JsonProperty("DownloadIcons")]
public bool DownloadIcons { get; set; }
[JsonProperty("Macros")]
public List<KeyMacro> Macros { get; set; } = new();
}
+44 -17
View File
@@ -18,9 +18,14 @@ using Microsoft.Extensions.Logging;
using Slim;
using System.Extensions;
using System.Net.Http;
using System.Windows.Extensions.Http;
using LiteDB;
using System.Windows.Extensions;
using Daybreak.Services.Options;
using Daybreak.Models;
using Microsoft.CorrelationVector;
using System.Logging;
using Daybreak.Services.Updater.PostUpdate;
using System.Core.Extensions;
using Daybreak.Services.Updater.PostUpdate.Actions;
namespace Daybreak.Configuration
{
@@ -28,43 +33,57 @@ namespace Daybreak.Configuration
{
public static void RegisterResolvers(IServiceManager serviceManager)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.ThrowIfNull();
serviceManager.RegisterResolver(
new HttpClientResolver()
.WithHttpMessageHandlerFactory((serviceProvider, categoryType) =>
{
var loggerType = typeof(ILogger<>).MakeGenericType(categoryType);
var logger = serviceProvider.GetService(loggerType).As<ILogger>();
var handler = new LoggingHttpMessageHandler(logger) { InnerHandler = new HttpClientHandler() };
return handler;
}));
serviceManager.RegisterHttpFactory((serviceProvider, categoryType) =>
{
var loggerType = typeof(ILogger<>).MakeGenericType(categoryType);
var logger = serviceProvider.GetService(loggerType).As<ILogger>();
var handler = new LoggingHttpMessageHandler(logger) { InnerHandler = new HttpClientHandler() };
return handler;
});
serviceManager.RegisterOptionsManager<ApplicationConfigurationOptionsManager>();
}
public static void RegisterServices(IServiceProducer serviceProducer)
{
serviceProducer.ThrowIfNull(nameof(serviceProducer));
serviceProducer.ThrowIfNull();
serviceProducer.RegisterSingleton<ViewManager>();
serviceProducer.RegisterSingleton<ILogsManager, JsonLogsManager>();
serviceProducer.RegisterSingleton<IDebugLogsWriter, Services.Logging.DebugLogsWriter>();
serviceProducer.RegisterSingleton<ILoggerFactory, LoggerFactory>(sp =>
{
var factory = new LoggerFactory();
factory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
return factory;
});
serviceProducer.RegisterSingleton<ILogsWriter, CompositeLogsWriter>(sp => new CompositeLogsWriter(
sp.GetService<ILogsManager>(),
sp.GetService<IDebugLogsWriter>()));
serviceProducer.RegisterScoped((sp) => new ScopeMetadata(new CorrelationVector()));
serviceProducer.RegisterSingleton<ViewManager>(registerAllInterfaces: true);
serviceProducer.RegisterSingleton<PostUpdateActionManager>(registerAllInterfaces: true);
serviceProducer.RegisterSingleton<IConfigurationManager, ConfigurationManager>();
serviceProducer.RegisterSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
serviceProducer.RegisterSingleton<IMutexHandler, MutexHandler>();
serviceProducer.RegisterSingleton<IShortcutManager, ShortcutManager>();
serviceProducer.RegisterSingleton<IIconBrowser, IconBrowser>();
serviceProducer.RegisterSingleton<IIconDownloader, IconDownloader>();
serviceProducer.RegisterScoped<ICredentialManager, CredentialManager>();
serviceProducer.RegisterScoped<IApplicationLauncher, ApplicationLauncher>();
serviceProducer.RegisterScoped<IScreenshotProvider, ScreenshotProvider>();
serviceProducer.RegisterScoped<IBloogumClient, BloogumClient>();
serviceProducer.RegisterScoped<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterScoped<IBuildTemplateManager, BuildTemplateManager>();
serviceProducer.RegisterScoped<IIconRetriever, IconRetriever>();
serviceProducer.RegisterScoped<IIconCache, IconCache>();
serviceProducer.RegisterScoped<IPrivilegeManager, PrivilegeManager>();
serviceProducer.RegisterScoped<IScreenManager, ScreenManager>();
serviceProducer.RegisterLogWriter<ILogsManager, JsonLogsManager>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
viewProducer.ThrowIfNull(nameof(viewProducer));
viewProducer.ThrowIfNull();
viewProducer.RegisterView<MainView>();
viewProducer.RegisterView<SettingsView>();
@@ -80,6 +99,14 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<ScreenChoiceView>();
viewProducer.RegisterView<VersionManagementView>();
viewProducer.RegisterView<LogsView>();
viewProducer.RegisterView<IconDownloadView>();
}
public static void RegisterPostUpdateActions(IPostUpdateActionProducer postUpdateActionProducer)
{
postUpdateActionProducer.ThrowIfNull();
postUpdateActionProducer.AddPostUpdateAction<RenameInstallerAction>();
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
x:Name="_this"
@@ -1,11 +1,12 @@
using Daybreak.Models.Browser;
using Daybreak.Configuration;
using Daybreak.Models.Browser;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.Threading.Tasks;
@@ -27,23 +28,24 @@ namespace Daybreak.Controls
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
private static CoreWebView2Environment coreWebView2Environment;
public event EventHandler<string> FavoriteUriChanged;
public event EventHandler MaximizeClicked;
public event EventHandler<Build> BuildDecoded;
private IConfigurationManager configurationManager;
private ILiveOptions<ApplicationConfiguration> liveOptions;
private ILogger<ChromiumBrowserWrapper> logger;
private IBuildTemplateManager buildTemplateManager;
private CoreWebView2Environment coreWebView2Environment;
[GenerateDependencyProperty(InitialValue = true)]
private bool canDownloadBuild;
[GenerateDependencyProperty(InitialValue = true)]
private bool canNavigate;
[GenerateDependencyProperty(InitialValue = true)]
private bool controlsEnabled;
[GenerateDependencyProperty]
private bool browserSupported;
[GenerateDependencyProperty(InitialValue = null)]
private bool? browserSupported;
[GenerateDependencyProperty]
private bool addressBarReadonly;
[GenerateDependencyProperty]
@@ -79,12 +81,12 @@ namespace Daybreak.Controls
}
}
public async void InitializeBrowser(
IConfigurationManager configurationManager,
public async Task InitializeBrowser(
ILiveOptions<ApplicationConfiguration> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.configurationManager = configurationManager;
this.liveOptions = liveOptions;
this.buildTemplateManager = buildTemplateManager;
this.logger = logger;
this.InitializeEnvironment();
@@ -98,7 +100,7 @@ namespace Daybreak.Controls
private void InitializeEnvironment()
{
if (this.configurationManager.GetConfiguration().BrowsersEnabled is false)
if (this.liveOptions.Value.BrowsersEnabled is false)
{
this.BrowserSupported = false;
return;
@@ -106,7 +108,11 @@ namespace Daybreak.Controls
try
{
this.coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
if (coreWebView2Environment is null)
{
coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
}
this.BrowserSupported = true;
}
catch(Exception e)
@@ -121,9 +127,10 @@ namespace Daybreak.Controls
if (this.BrowserSupported is true)
{
this.WebBrowser.IsEnabled = true;
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
this.CanDownloadBuild = this.configurationManager.GetConfiguration().ExperimentalFeatures.DynamicBuildLoading;
this.BrowserEnabled = true;
await this.WebBrowser.EnsureCoreWebView2Async(coreWebView2Environment);
this.AddressBarReadonly = this.liveOptions.Value.AddressBarReadonly;
this.CanDownloadBuild = this.liveOptions.Value.ExperimentalFeatures.DynamicBuildLoading;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) =>
{
@@ -196,6 +203,7 @@ namespace Daybreak.Controls
{
this.logger.LogError(e, $"Exception encountered when deserializing {nameof(BrowserPayload)}");
}
if (payload?.Key == BrowserPayload.PayloadKeys.ContextMenu)
{
var contextMenuPayload = args.WebMessageAsJson.Deserialize<BrowserPayload<OnContextMenuPayload>>();
@@ -205,6 +213,11 @@ namespace Daybreak.Controls
return;
}
if (this.buildTemplateManager is null)
{
return;
}
if (this.buildTemplateManager.IsTemplate(maybeTemplate) is false)
{
return;
+35
View File
@@ -0,0 +1,35 @@
<UserControl x:Class="Daybreak.Controls.SearchTextBox"
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.Controls"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid>
<TextBox Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Foreground, Mode=OneWay}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontSize, Mode=OneWay}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontFamily, Mode=OneWay}"
FontStretch="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStretch, Mode=OneWay}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontWeight, Mode=OneWay}"
FontStyle="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStyle, Mode=OneWay}"
TextChanged="TextBox_TextChanged"
Background="Transparent"/>
<TextBlock Margin="5, 0, 0, 0"
Text="Search"
Background="Transparent"
Opacity="0.5"
IsHitTestVisible="False"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontSize, Mode=OneWay}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontFamily, Mode=OneWay}"
FontStretch="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStretch, Mode=OneWay}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontWeight, Mode=OneWay}"
FontStyle="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStyle, Mode=OneWay}"
Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Foreground, Mode=OneWay}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=PlaceholderVisibility, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
</UserControl>
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for SearchTextBox.xaml
/// </summary>
public partial class SearchTextBox : UserControl
{
public event EventHandler<string> TextChanged;
[GenerateDependencyProperty(InitialValue = true)]
private bool placeholderVisibility;
[GenerateDependencyProperty]
private string searchText;
public SearchTextBox()
{
this.InitializeComponent();
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var searchText = e.Source.As<TextBox>().Text;
if (searchText.IsNullOrWhiteSpace())
{
this.PlaceholderVisibility = true;
}
else
{
this.PlaceholderVisibility = false;
}
this.SearchText = searchText;
this.TextChanged?.Invoke(this, searchText);
}
}
}
+26 -9
View File
@@ -4,11 +4,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:interactivity="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
mc:Ignorable="d"
x:Name="_this"
Unloaded="BuildTemplate_Unloaded"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Transparent" MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.RowDefinitions>
@@ -156,14 +157,30 @@
<local:ChromiumBrowserWrapper x:Name="SkillBrowser" ControlsEnabled="False" Width="0" AddressBarReadonly="True" CanNavigate="True"></local:ChromiumBrowserWrapper>
</Grid>
<Grid Grid.Column="1" Grid.RowSpan="6">
<ListView x:Name="SkillsListView" Background="Transparent" Width="0"
ItemsSource="{Binding ElementName=_this, Path=AvailableSkills, Mode=OneWay}" MouseDoubleClick="ListView_MouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid x:Name="SkillListContainer">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<local:SearchTextBox FontSize="20"
Foreground="White"
Background="Transparent"
SearchText="{Binding ElementName=_this, Path=SkillSearchText, Mode=TwoWay}"
TextChanged="SearchTextBox_TextChanged"></local:SearchTextBox>
<ScrollViewer VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Disabled"
Grid.Row="1"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<ListView x:Name="SkillsListView" Background="Transparent"
ItemsSource="{Binding ElementName=_this, Path=AvailableSkills, Mode=OneWay}" MouseDoubleClick="ListView_MouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
</UserControl>
@@ -1,16 +1,20 @@
using Daybreak.Models.Builds;
using Daybreak.Configuration;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.IconRetrieve;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using Utils;
namespace Daybreak.Controls
{
@@ -26,11 +30,15 @@ namespace Daybreak.Controls
private bool suppressBuildChanged = false;
private bool loadedProperties = false;
private IIconBrowser iconBrowser;
private BuildEntry loadedBuild;
private SkillTemplate selectingSkillTemplate;
private CancellationTokenSource cancellationTokenSource = new();
public event EventHandler BuildChanged;
[GenerateDependencyProperty]
private string skillSearchText;
[GenerateDependencyProperty]
private Profession primaryProfession;
[GenerateDependencyProperty]
@@ -59,16 +67,18 @@ namespace Daybreak.Controls
{
this.InitializeComponent();
this.InitializeProperties();
this.DataContextChanged += BuildTemplate_DataContextChanged;
this.DataContextChanged += this.BuildTemplate_DataContextChanged;
}
public void InitializeTemplate(
IIconRetriever iconRetriever,
IConfigurationManager configurationManager,
public async void InitializeTemplate(
IIconCache iconRetriever,
IIconBrowser iconBrowser,
ILiveOptions<ApplicationConfiguration> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.SkillBrowser.InitializeBrowser(configurationManager, buildTemplateManager, logger);
this.iconBrowser = iconBrowser.ThrowIfNull();
await this.SkillBrowser.InitializeBrowser(liveOptions, buildTemplateManager, logger);
this.SkillTemplate0.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate1.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate2.InitializeSkillTemplate(iconRetriever);
@@ -77,6 +87,9 @@ namespace Daybreak.Controls
this.SkillTemplate5.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate6.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate7.InitializeSkillTemplate(iconRetriever);
this.HideSkillListView();
this.HideInfoBrowser();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
@@ -97,6 +110,7 @@ namespace Daybreak.Controls
{
this.loadedBuild.Build.Secondary = this.SecondaryProfession;
}
this.LoadSkills();
this.LoadAttributes();
if (this.suppressBuildChanged is false)
@@ -129,6 +143,11 @@ namespace Daybreak.Controls
}
}
private void BuildTemplate_Unloaded(object sender, RoutedEventArgs e)
{
this.cancellationTokenSource.Cancel();
}
private void InitializeProperties()
{
this.PrimaryProfession = Profession.None;
@@ -192,55 +211,65 @@ namespace Daybreak.Controls
private void LoadSkills()
{
var possibleSkills = Skill.Skills
.Where(s => s.Profession == PrimaryProfession || s.Profession == SecondaryProfession || s.Profession == Profession.None)
.Where(s => s.Profession == this.PrimaryProfession || s.Profession == this.SecondaryProfession || s.Profession == Profession.None)
.Where(s => s != Skill.NoSkill)
.Where(s => this.SkillSearchText.IsNullOrWhiteSpace() ?
true :
StringUtils.MatchesSearchString(s.Name.Replace("\"", "").Replace("!", ""), this.SkillSearchText.Replace("\"", "").Replace("!", "")))
.OrderBy(s => s.Name);
this.AvailableSkills.ClearAnd().AddRange(possibleSkills);
if (this.Skill0.Profession != PrimaryProfession &&
this.Skill0.Profession != SecondaryProfession &&
if (this.Skill0.Profession != this.PrimaryProfession &&
this.Skill0.Profession != this.SecondaryProfession &&
this.Skill0.Profession != Profession.None)
{
this.Skill0 = Skill.NoSkill;
}
if (this.Skill1.Profession != PrimaryProfession &&
this.Skill1.Profession != SecondaryProfession &&
if (this.Skill1.Profession != this.PrimaryProfession &&
this.Skill1.Profession != this.SecondaryProfession &&
this.Skill1.Profession != Profession.None)
{
this.Skill1 = Skill.NoSkill;
}
if (this.Skill2.Profession != PrimaryProfession &&
this.Skill2.Profession != SecondaryProfession &&
if (this.Skill2.Profession != this.PrimaryProfession &&
this.Skill2.Profession != this.SecondaryProfession &&
this.Skill2.Profession != Profession.None)
{
this.Skill2 = Skill.NoSkill;
}
if (this.Skill3.Profession != PrimaryProfession &&
this.Skill3.Profession != SecondaryProfession &&
if (this.Skill3.Profession != this.PrimaryProfession &&
this.Skill3.Profession != this.SecondaryProfession &&
this.Skill3.Profession != Profession.None)
{
this.Skill3 = Skill.NoSkill;
}
if (this.Skill4.Profession != PrimaryProfession &&
this.Skill4.Profession != SecondaryProfession &&
if (this.Skill4.Profession != this.PrimaryProfession &&
this.Skill4.Profession != this.SecondaryProfession &&
this.Skill4.Profession != Profession.None)
{
this.Skill4 = Skill.NoSkill;
}
if (this.Skill5.Profession != PrimaryProfession &&
this.Skill5.Profession != SecondaryProfession &&
if (this.Skill5.Profession != this.PrimaryProfession &&
this.Skill5.Profession != this.SecondaryProfession &&
this.Skill5.Profession != Profession.None)
{
this.Skill5 = Skill.NoSkill;
}
if (this.Skill6.Profession != PrimaryProfession &&
this.Skill6.Profession != SecondaryProfession &&
if (this.Skill6.Profession != this.PrimaryProfession &&
this.Skill6.Profession != this.SecondaryProfession &&
this.Skill6.Profession != Profession.None)
{
this.Skill6 = Skill.NoSkill;
}
if (this.Skill7.Profession != PrimaryProfession &&
this.Skill7.Profession != SecondaryProfession &&
if (this.Skill7.Profession != this.PrimaryProfession &&
this.Skill7.Profession != this.SecondaryProfession &&
this.Skill7.Profession != Profession.None)
{
this.Skill7 = Skill.NoSkill;
@@ -277,7 +306,7 @@ namespace Daybreak.Controls
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
this.SkillListContainer.Width = 0;
}
}
@@ -292,12 +321,12 @@ namespace Daybreak.Controls
private void ShowSkillListView()
{
this.SkillBrowser.Width = 0;
this.SkillsListView.Width = 400;
this.SkillListContainer.Width = 400;
}
private void HideSkillListView()
{
this.SkillsListView.Width = 0;
this.SkillListContainer.Width = 0;
}
private void HelpButtonPrimary_Clicked(object sender, System.EventArgs e)
@@ -307,7 +336,7 @@ namespace Daybreak.Controls
return;
}
this.BrowseToInfo(PrimaryProfession.Name);
this.BrowseToInfo(this.PrimaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
@@ -343,6 +372,7 @@ namespace Daybreak.Controls
var skill = sender.As<SkillTemplate>().DataContext.As<Skill>();
if (skill == Skill.NoSkill)
{
this.SkillSearchText = string.Empty;
this.ShowSkillListView();
this.selectingSkillTemplate = sender.As<SkillTemplate>();
}
@@ -350,9 +380,15 @@ namespace Daybreak.Controls
{
this.BrowseToInfo(skill.Name);
}
e.Handled = true;
}
private void SearchTextBox_TextChanged(object sender, string e)
{
this.LoadSkills();
}
private void SkillTemplate_RemoveClicked(object sender, System.EventArgs e)
{
sender.As<SkillTemplate>().DataContext = Skill.NoSkill;
@@ -368,14 +404,14 @@ namespace Daybreak.Controls
this.selectingSkillTemplate.DataContext = sender.As<ListView>().SelectedItem;
this.HideSkillListView();
this.loadedBuild.Build.Skills[0] = Skill0;
this.loadedBuild.Build.Skills[1] = Skill1;
this.loadedBuild.Build.Skills[2] = Skill2;
this.loadedBuild.Build.Skills[3] = Skill3;
this.loadedBuild.Build.Skills[4] = Skill4;
this.loadedBuild.Build.Skills[5] = Skill5;
this.loadedBuild.Build.Skills[6] = Skill6;
this.loadedBuild.Build.Skills[7] = Skill7;
this.loadedBuild.Build.Skills[0] = this.Skill0;
this.loadedBuild.Build.Skills[1] = this.Skill1;
this.loadedBuild.Build.Skills[2] = this.Skill2;
this.loadedBuild.Build.Skills[3] = this.Skill3;
this.loadedBuild.Build.Skills[4] = this.Skill4;
this.loadedBuild.Build.Skills[5] = this.Skill5;
this.loadedBuild.Build.Skills[6] = this.Skill6;
this.loadedBuild.Build.Skills[7] = this.Skill7;
}
private void ListView_NavigateWithMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
@@ -394,5 +430,16 @@ namespace Daybreak.Controls
sender.As<ListView>().Items.Count;
}
}
private void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
if (sender is not ScrollViewer scrollViewer)
{
return;
}
e.Handled = true;
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
}
}
}
@@ -1,8 +1,9 @@
<UserControl x:Class="Daybreak.Controls.SkillTemplate"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:webview="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
@@ -1,10 +1,6 @@
using Daybreak.Launch;
using Daybreak.Models.Builds;
using Daybreak.Models.Builds;
using Daybreak.Services.IconRetrieve;
using System;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
@@ -23,7 +19,7 @@ namespace Daybreak.Controls
public event EventHandler<RoutedEventArgs> Clicked;
public event EventHandler RemoveClicked;
private IIconRetriever iconRetriever;
private IIconCache iconRetriever;
[GenerateDependencyProperty]
private ImageSource imageSource;
@@ -36,24 +32,28 @@ namespace Daybreak.Controls
this.DataContextChanged += SkillTemplate_DataContextChanged;
}
public void InitializeSkillTemplate(IIconRetriever iconRetriever)
public void InitializeSkillTemplate(IIconCache iconRetriever)
{
this.iconRetriever = iconRetriever;
this.SkillTemplate_DataContextChanged(this, new DependencyPropertyChangedEventArgs(UserControl.DataContextProperty, null, this.DataContext));
}
private void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
private async void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.iconRetriever is null)
{
return;
}
if (e.NewValue is Skill skill)
{
if (skill != Skill.NoSkill)
{
Task.Run(() => this.GetImageStream(skill)).ContinueWith((previousTask) =>
var maybeUri = await this.iconRetriever.GetIconUri(skill).ConfigureAwait(true);
if (maybeUri.ExtractValue() is Uri uri)
{
this.Dispatcher.Invoke(() =>
{
this.ImageSource = this.GetImageSource(previousTask.Result);
});
});
this.ImageSource = new BitmapImage(uri);
}
}
else if (this.ImageSource is not null)
{
@@ -90,32 +90,5 @@ namespace Daybreak.Controls
return false;
}
private async Task<Stream> GetImageStream(Skill skill)
{
if (this.iconRetriever is null)
{
return null;
}
var maybeStream = await this.iconRetriever.GetIcon(skill);
return maybeStream.ExtractValue();
}
private ImageSource GetImageSource(Stream stream)
{
if (stream is null)
{
return null;
}
return this.Dispatcher.Invoke(() =>
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.CacheOption = BitmapCacheOption.OnDemand;
bitmapImage.EndInit();
return bitmapImage;
});
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Extensions;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
@@ -19,7 +20,7 @@ namespace Daybreak.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetVisibility(value);
return this.GetVisibility(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
@@ -29,17 +30,24 @@ namespace Daybreak.Converters
private object GetVisibility(object value)
{
if (!(value is bool))
return DependencyProperty.UnsetValue;
bool objValue = (bool)value;
if (value is not bool)
{
return this.IsHidden ?
Visibility.Hidden :
Visibility.Collapsed;
}
var objValue = value.Cast<bool>();
if ((objValue && TriggerValue && IsHidden) || (!objValue && !TriggerValue && IsHidden))
{
return Visibility.Hidden;
}
if ((objValue && TriggerValue && !IsHidden) || (!objValue && !TriggerValue && !IsHidden))
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
}
+30 -15
View File
@@ -1,35 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<Platforms>AnyCPU</Platforms>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.2.3</Version>
<Version>0.9.5</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
<PackageReference Include="LiteDB" Version="5.0.10" />
<PackageReference Include="LiteDB" Version="5.0.12" />
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.864.35" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1264.42" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NReco.Logging.File" Version="1.1.1" />
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
<PackageReference Include="Slim" Version="1.5.0" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.3.0" />
<PackageReference Include="Slim" Version="1.7.3" />
<PackageReference Include="SystemExtensions.NetCore" Version="1.0.1" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.5.0" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.5.0" />
<PackageReference Include="WpfExtended" Version="0.6.2" />
<PackageReference Include="WpfExtended.SourceGeneration" Version="0.1.1" />
<PackageReference Include="WpfScreenHelper" Version="1.0.0" />
<PackageReference Include="WpfScreenHelper" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
@@ -45,6 +45,9 @@
<Compile Update="Controls\Buttons\CancelButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\IconDownloadView.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -68,10 +71,22 @@
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Views\IconDownloadView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
<Exec Command="echo.&gt;$(Version).version" />
</Target>
<Target Name="RemoveDuplicateAnalyzers" BeforeTargets="CoreCompile">
<!-- Work around https://github.com/dotnet/wpf/issues/6792 -->
<ItemGroup>
<FilteredAnalyzer Include="@(Analyzer-&gt;Distinct())" />
<Analyzer Remove="@(Analyzer)" />
<Analyzer Include="@(FilteredAnalyzer)" />
</ItemGroup>
</Target>
</Project>
+7
View File
@@ -1,5 +1,6 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Updater.PostUpdate;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using Slim;
@@ -33,6 +34,7 @@ namespace Daybreak.Launch
ProjectConfiguration.RegisterServices(this.ServiceManager);
ServiceManager.BuildSingletons();
ProjectConfiguration.RegisterViews(this.ServiceManager.GetService<IViewManager>());
ProjectConfiguration.RegisterPostUpdateActions(this.ServiceManager.GetService<IPostUpdateActionProducer>());
}
protected override bool HandleException(Exception e)
{
@@ -41,6 +43,11 @@ namespace Daybreak.Launch
return false;
}
if (this.logger is null)
{
return false;
}
if (e is FatalException fatalException)
{
this.logger.LogCritical(e, $"{nameof(FatalException)} encountered. Closing application");
+7 -1
View File
@@ -2,7 +2,8 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:webview="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:local="clr-namespace:Daybreak.Launch"
xmlns:wcl="clr-namespace:WCL;assembly=WCL"
mc:Ignorable="d"
@@ -102,5 +103,10 @@
<Grid x:Name="Container" Grid.Row="1">
</Grid>
<wcl:Border OnResize="Border_OnResize" Grid.RowSpan="2" Active="True"></wcl:Border>
<webview:WebView2 x:Name="BackgroundWebView"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="Collapsed"
Grid.Row="1"></webview:WebView2>
</Grid>
</Window>
+23 -20
View File
@@ -1,5 +1,6 @@
using Daybreak.Services.Bloogum;
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Services.Bloogum;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Privilege;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
@@ -7,6 +8,8 @@ using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Pepa.Wpf.Utilities;
using System;
using System.Configuration;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using System.Threading;
@@ -31,11 +34,10 @@ namespace Daybreak.Launch
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly IPrivilegeManager privilegeManager;
private readonly IConfigurationManager configurationManager;
private readonly IIconDownloader iconDownloader;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly CancellationTokenSource cancellationToken = new();
private bool canCheckUpdate = false;
[GenerateDependencyProperty]
private string creditText;
[GenerateDependencyProperty]
@@ -49,31 +51,27 @@ namespace Daybreak.Launch
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater,
IPrivilegeManager privilegeManager,
IConfigurationManager configurationManager)
IIconDownloader iconDownloader,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.viewManager = viewManager.ThrowIfNull();
this.screenshotProvider = screenshotProvider.ThrowIfNull();
this.bloogumClient = bloogumClient.ThrowIfNull();
this.applicationUpdater = applicationUpdater.ThrowIfNull();
this.privilegeManager = privilegeManager.ThrowIfNull();
this.iconDownloader = iconDownloader.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.InitializeComponent();
this.LoadConfiguration();
this.configurationManager.ConfigurationChanged += (s, e) => this.LoadConfiguration();
this.CurrentVersionText = this.applicationUpdater.CurrentVersion.ToString();
this.IsRunningAsAdmin = this.privilegeManager.AdminPrivileges;
}
private void LoadConfiguration()
{
var configuration = this.configurationManager.GetConfiguration();
this.canCheckUpdate = configuration.AutoCheckUpdate;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.SetupImageCycle();
this.CheckForUpdates();
this.SetupBackgroundBrowser();
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@@ -109,6 +107,11 @@ namespace Daybreak.Launch
NativeMethods.SendMessage(new WindowInteropHelper(this).Handle, NativeMethods.WM_SYSCOMMAND, (IntPtr)e, IntPtr.Zero);
}
private void SetupBackgroundBrowser()
{
this.iconDownloader.SetBrowser(this.BackgroundWebView);
}
private void SetupImageCycle()
{
TaskExtensions.RunPeriodicAsync(() => this.Dispatcher.Invoke(() => this.UpdateRandomImage()), TimeSpan.Zero, TimeSpan.FromSeconds(15), this.cancellationToken.Token);
@@ -177,7 +180,7 @@ namespace Daybreak.Launch
private async void CheckForUpdates()
{
var updateAvailable = this.canCheckUpdate is true && await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
var updateAvailable = this.liveOptions.Value.AutoCheckUpdate is true && await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
if (updateAvailable)
{
this.viewManager.ShowView<AskUpdateView>();
@@ -8,6 +8,7 @@ namespace Daybreak.Models.Browser
[JsonConverter(typeof(StringEnumConverter))]
public enum PayloadKeys
{
None,
ContextMenu
}
+6 -5
View File
@@ -143,7 +143,7 @@ namespace Daybreak.Models.Builds
public static Skill Flurry { get; } = new() { Id = 344, Name = "Flurry", Profession = Profession.Warrior };
public static Skill Frenzy { get; } = new() { Id = 346, Name = "Frenzy", Profession = Profession.Warrior };
public static Skill Coward { get; } = new() { Id = 869, Name = "\"Coward!\"", Profession = Profession.Warrior };
public static Skill OnYourKnees { get; } = new() { Id = 906, Name = "On Your Knees!", Profession = Profession.Warrior };
public static Skill OnYourKnees { get; } = new() { Id = 906, Name = "\"On Your Knees!\"", Profession = Profession.Warrior };
public static Skill YoureAllAlone { get; } = new() { Id = 1412, Name = "\"You're All Alone!\"", Profession = Profession.Warrior };
public static Skill FrenziedDefense { get; } = new() { Id = 1700, Name = "Frenzied Defense", Profession = Profession.Warrior };
public static Skill Grapple { get; } = new() { Id = 2011, Name = "Grapple", Profession = Profession.Warrior };
@@ -702,7 +702,7 @@ namespace Daybreak.Models.Builds
public static Skill Echo { get; } = new() { Id = 74, Name = "Echo", Profession = Profession.Mesmer };
public static Skill ArcaneEcho { get; } = new() { Id = 75, Name = "Arcane Echo", Profession = Profession.Mesmer };
public static Skill Epidemic { get; } = new() { Id = 78, Name = "Epidemic", Profession = Profession.Mesmer };
public static Skill LyssasBalance { get; } = new() { Id = 877, Name = "Lyssa's Balance, Profession = Profession.Mesmer" };
public static Skill LyssasBalance { get; } = new() { Id = 877, Name = "Lyssa's Balance", Profession = Profession.Mesmer };
public static Skill SignetofDisenchantment { get; } = new() { Id = 882, Name = "Signet of Disenchantment", Profession = Profession.Mesmer };
public static Skill ShatterStorm { get; } = new() { Id = 933, Name = "Shatter Storm", Profession = Profession.Mesmer };
public static Skill ExpelHexes { get; } = new() { Id = 954, Name = "Expel Hexes", Profession = Profession.Mesmer };
@@ -1053,7 +1053,7 @@ namespace Daybreak.Models.Builds
public static Skill FeastofSouls { get; } = new() { Id = 980, Name = "Feast of Souls", Profession = Profession.Ritualist };
public static Skill RitualLord { get; } = new() { Id = 1217, Name = "Ritual Lord", Profession = Profession.Ritualist };
public static Skill AttunedWasSongkai { get; } = new() { Id = 1220, Name = "Attuned Was Songkai", Profession = Profession.Ritualist };
public static Skill AnguishedWasLingwah { get; } = new() { Id = 1223, Name = "Anguished Was Lingwah, Profession = Profession.Ritualist" };
public static Skill AnguishedWasLingwah { get; } = new() { Id = 1223, Name = "Anguished Was Lingwah", Profession = Profession.Ritualist };
public static Skill ExplosiveGrowth { get; } = new() { Id = 1229, Name = "Explosive Growth", Profession = Profession.Ritualist };
public static Skill BoonofCreation { get; } = new() { Id = 1230, Name = "Boon of Creation", Profession = Profession.Ritualist };
public static Skill SpiritChanneling { get; } = new() { Id = 1231, Name = "Spirit Channeling", Profession = Profession.Ritualist };
@@ -1483,8 +1483,8 @@ namespace Daybreak.Models.Builds
public static Skill VolfenBlessing { get; } = new() { Id = 2379, Name = "Volfen Blessing", Profession = Profession.None };
public static Skill TimeWard { get; } = new() { Id = 3422, Name = "Time Ward", Profession = Profession.Mesmer };
public static Skill SoulTaker { get; } = new() { Id = 3423, Name = "Soul Taker", Profession = Profession.Necromancer };
public static Skill OverTheLimit { get; } = new() { Id = 3424, Name = "Over The Limit", Profession = Profession.Elementalist };
public static Skill JudgementStrike { get; } = new() { Id = 3425, Name = "Judgement Strike", Profession = Profession.Monk };
public static Skill OverTheLimit { get; } = new() { Id = 3424, Name = "Over The Limit", AlternativeName = "Over the Limit", Profession = Profession.Elementalist };
public static Skill JudgementStrike { get; } = new() { Id = 3425, Name = "Judgement Strike", AlternativeName = "Judgment Strike", Profession = Profession.Monk };
public static Skill SevenWeaponsStance { get; } = new() { Id = 3426, Name = "Seven Weapons Stance", Profession = Profession.Warrior };
public static Skill Togetherasone { get; } = new() { Id = 3427, Name = "\"Together as one!\"", Profession = Profession.Ranger };
public static Skill ShadowTheft { get; } = new() { Id = 3428, Name = "Shadow Theft", Profession = Profession.Assassin };
@@ -3022,6 +3022,7 @@ namespace Daybreak.Models.Builds
public Profession Profession { get; private set; }
public string Name { get; private set; }
public int Id { get; private set; }
public string AlternativeName { get; private set; }
private Skill()
{
}
+8
View File
@@ -0,0 +1,8 @@
namespace Daybreak.Models
{
public sealed class IconPayload
{
public string SkillUrl { get; set; }
public string SkillImage { get; set; }
}
}
+11
View File
@@ -0,0 +1,11 @@
using Daybreak.Models.Builds;
namespace Daybreak.Models
{
public sealed class IconRequest
{
public Skill Skill { get; set; }
public string IconBase64 { get; set; }
public bool Finished { get; set; }
}
}
@@ -0,0 +1,78 @@
using System.ComponentModel;
namespace Daybreak.Models.Progress
{
public sealed class IconDownloadStatus : INotifyPropertyChanged
{
public static readonly IconDownloadStep StartingStep = new StartingIconDownloadStep();
public static readonly IconDownloadStep Finished = new FinishedIconDownloadStep();
public static readonly IconDownloadStep BrowserNotSupported = new NotSupportedIconDownloadStep();
public static IconDownloadStep Checking(string iconName, double progress) => new CheckingIconDownloadStep(iconName, progress);
public static IconDownloadStep Downloading(string iconName, double progress) => new DownloadingIconDownloadStep(iconName, progress);
public static IconDownloadStep Stopped(double progress) => new StoppedIconDownloadStep(progress);
private IconDownloadStep currentStep = StartingStep;
public event PropertyChangedEventHandler PropertyChanged;
public IconDownloadStep CurrentStep
{
get => currentStep;
set
{
currentStep = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
public abstract class IconDownloadStep : LoadStatus
{
public IconDownloadStep(string name, double progress) : base(name)
{
this.Progress = progress;
}
}
public class StoppedIconDownloadStep : IconDownloadStep
{
public StoppedIconDownloadStep(double progress) : base("Download stopped", progress)
{
}
}
public class DownloadingIconDownloadStep : IconDownloadStep
{
public DownloadingIconDownloadStep(string skillName, double progress) : base($"Downloading [{skillName}] icon", progress)
{
}
}
public class CheckingIconDownloadStep : IconDownloadStep
{
public CheckingIconDownloadStep(string skillName, double progress) : base($"Checking [{skillName}] icon", progress)
{
}
}
public class NotSupportedIconDownloadStep : IconDownloadStep
{
public NotSupportedIconDownloadStep() : base("Cannot download icons. The WebView2 browser is not supported", 0d)
{
}
}
public class FinishedIconDownloadStep : IconDownloadStep
{
public FinishedIconDownloadStep() : base("Download finished", 100d)
{
}
}
public class StartingIconDownloadStep : IconDownloadStep
{
public StartingIconDownloadStep() : base("Download starting", 0d)
{
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace Daybreak.Models.Progress
{
public abstract class LoadStatus
{
public string Description { get; set; }
public double Progress { get; set; }
public LoadStatus(string description)
{
this.Description = description;
}
}
}
@@ -1,6 +1,6 @@
using System.ComponentModel;
namespace Daybreak.Models
namespace Daybreak.Models.Progress
{
public sealed class UpdateStatus : INotifyPropertyChanged
{
@@ -17,30 +17,26 @@ namespace Daybreak.Models
public UpdateStep CurrentStep
{
get => this.currentStep;
get => currentStep;
set
{
this.currentStep = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
currentStep = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
public class UpdateStep
public class UpdateStep : LoadStatus
{
public string Name { get; }
internal UpdateStep(string name)
public UpdateStep(string name) : base(name)
{
this.Name = name;
}
}
public class DownloadUpdateStep : UpdateStep
{
internal DownloadUpdateStep(string name, double progress) : base(name)
{
this.Progress = progress;
Progress = progress;
}
public double Progress { get; }
}
}
}
+14 -18
View File
@@ -9,49 +9,45 @@ namespace Daybreak.Models
[Serializable]
public sealed class SecureString
{
public static SecureString Empty { get => new SecureString(string.Empty); }
public static SecureString Empty { get => new(string.Empty); }
private byte[] encryptedBytes;
private readonly byte[] key;
private byte[] DecryptedValue
{
get => encryptedBytes.DecryptBytes(key);
set => encryptedBytes = value.EncryptBytes(key);
get => this.encryptedBytes.DecryptBytes(key);
set => this.encryptedBytes = value.EncryptBytes(key);
}
[JsonProperty("value")]
public string Value
{
get
{
return encryptedBytes.DecryptBytes(key).AsString();
return this.encryptedBytes.DecryptBytes(key).AsString();
}
set
{
encryptedBytes = value.AsBytes().EncryptBytes(key);
this.encryptedBytes = value.AsBytes().EncryptBytes(key);
}
}
private SecureString(byte[] value)
{
key = new byte[32];
using (var crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(key);
}
this.key = new byte[32];
using var crypto = RandomNumberGenerator.Create();
crypto.GetBytes(key);
this.DecryptedValue = value;
}
public SecureString(string value)
{
key = new byte[32];
using (var crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(key);
}
this.key = new byte[32];
using var crypto = RandomNumberGenerator.Create();
crypto.GetBytes(key);
this.Value = value;
}
public static implicit operator string(SecureString ss) => ss is null ? string.Empty : ss.Value;
public static implicit operator SecureString(string s) => new SecureString(s);
public static implicit operator SecureString(string s) => new(s);
public static SecureString operator +(SecureString ss1, SecureString ss2)
{
if (ss1 is null) throw new ArgumentNullException(nameof(ss1));
@@ -123,12 +119,12 @@ namespace Daybreak.Models
public override int GetHashCode()
{
return Value.GetHashCode();
return this.Value.GetHashCode();
}
public override string ToString()
{
return Value;
return this.Value;
}
}
}
@@ -1,5 +1,5 @@
using Daybreak.Exceptions;
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Credentials;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
@@ -9,6 +9,7 @@ using Microsoft.Win32;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.IO;
@@ -29,7 +30,7 @@ namespace Daybreak.Services.ApplicationLauncher
private const string ProcessName = "gw";
private const string ArenaNetMutex = "AN-Mute";
private readonly IConfigurationManager configurationManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly ICredentialManager credentialManager;
private readonly IMutexHandler mutexHandler;
private readonly ILogger<ApplicationLauncher> logger;
@@ -40,7 +41,7 @@ namespace Daybreak.Services.ApplicationLauncher
public bool IsToolboxRunning => GuildwarsToolboxProcessDetected();
public ApplicationLauncher(
IConfigurationManager configurationManager,
ILiveOptions<ApplicationConfiguration> liveOptions,
ICredentialManager credentialManager,
IMutexHandler mutexHandler,
ILogger<ApplicationLauncher> logger,
@@ -49,13 +50,13 @@ namespace Daybreak.Services.ApplicationLauncher
this.logger = logger.ThrowIfNull(nameof(logger));
this.mutexHandler = mutexHandler.ThrowIfNull(nameof(mutexHandler));
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
}
public async Task<bool> LaunchGuildwars()
{
var configuration = this.configurationManager.GetConfiguration();
var configuration = this.liveOptions.Value;
var auth = await this.credentialManager.GetDefaultCredentials().ConfigureAwait(false);
return await auth.Switch(
onSome: async (credentials) =>
@@ -68,10 +69,10 @@ namespace Daybreak.Services.ApplicationLauncher
return false;
}
ClearGwLocks();
this.ClearGwLocks();
}
return await LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
return await this.LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
},
onNone: () =>
{
@@ -84,7 +85,7 @@ namespace Daybreak.Services.ApplicationLauncher
{
return Task.Run(() =>
{
var configuration = this.configurationManager.GetConfiguration();
var configuration = this.liveOptions.Value;
var executable = configuration.ToolboxPath;
if (File.Exists(executable) is false)
{
@@ -102,7 +103,7 @@ namespace Daybreak.Services.ApplicationLauncher
{
return Task.Run(() =>
{
var configuration = this.configurationManager.GetConfiguration();
var configuration = this.liveOptions.Value;
var executable = configuration.TexmodPath;
if (File.Exists(executable) is false)
{
@@ -145,7 +146,7 @@ namespace Daybreak.Services.ApplicationLauncher
private async Task<bool> LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
{
var executable = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
var executable = this.liveOptions.Value.GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (executable is null)
{
throw new ExecutableNotFoundException($"No executable selected");
@@ -169,7 +170,7 @@ namespace Daybreak.Services.ApplicationLauncher
args.Add(character);
}
var identity = this.configurationManager.GetConfiguration().ExperimentalFeatures.LaunchGuildwarsAsCurrentUser ?
var identity = this.liveOptions.Value.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser ?
System.Security.Principal.WindowsIdentity.GetCurrent().Name :
System.Security.Principal.WindowsIdentity.GetAnonymous().Name;
this.logger.LogInformation($"Launching guildwars as [{identity}] identity");
@@ -221,11 +222,11 @@ namespace Daybreak.Services.ApplicationLauncher
private bool GuildwarsProcessDetected()
{
if (this.configurationManager.GetConfiguration().ExperimentalFeatures.MultiLaunchSupport is true)
if (this.liveOptions.Value.ExperimentalFeatures.MultiLaunchSupport is true)
{
try
{
var path = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
var path = this.liveOptions.Value.GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (path is null)
{
return false;
@@ -253,7 +254,7 @@ namespace Daybreak.Services.ApplicationLauncher
private void SetRegistryGuildwarsPath()
{
var path = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
var path = this.liveOptions.Value.GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (path is null)
{
throw new ExecutableNotFoundException("No executable currently selected");
+1 -1
View File
@@ -2,8 +2,8 @@
using Microsoft.Extensions.Logging;
using System;
using System.Extensions;
using System.Http;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace Daybreak.Services.Bloogum
@@ -26,7 +26,7 @@ namespace Daybreak.Services.Configuration
}
catch(Exception e)
{
this.logger.LogWarning($"No configuration detected. Loading default configuration. Details: {e}");
this.logger.LogWarning(e, $"Failed to load configuration. Falling back to default configuration");
this.applicationConfiguration = new ApplicationConfiguration();
}
}
@@ -1,8 +1,9 @@
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Security.Cryptography;
@@ -15,13 +16,13 @@ namespace Daybreak.Services.Credentials
{
private static readonly byte[] Entropy = Convert.FromBase64String("R3VpbGR3YXJz");
private readonly ILogger<CredentialManager> logger;
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
public CredentialManager(
ILogger<CredentialManager> logger,
IConfigurationManager configurationManager)
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.logger = logger.ThrowIfNull(nameof(logger));
}
@@ -53,7 +54,7 @@ namespace Daybreak.Services.Credentials
return Task.Run(() =>
{
this.logger.LogInformation("Retrieving credentials");
var config = this.configurationManager.GetConfiguration();
var config = this.liveOptions.Value;
if (config.ProtectedLoginCredentials is null || config.ProtectedLoginCredentials.Count == 0)
{
this.logger.LogInformation("No credentials found");
@@ -62,9 +63,9 @@ namespace Daybreak.Services.Credentials
return config
.ProtectedLoginCredentials
.Select(UnprotectCredentials)
.Where(CredentialsUnprotected)
.Select(ExtractCredentials)
.Select(this.UnprotectCredentials)
.Where(this.CredentialsUnprotected)
.Select(this.ExtractCredentials)
.ToList();
});
}
@@ -73,13 +74,12 @@ namespace Daybreak.Services.Credentials
return Task.Run(() =>
{
this.logger.LogInformation("Storing credentials");
var config = this.configurationManager.GetConfiguration();
config.ProtectedLoginCredentials = loginCredentials
.Select(ProtectCredentials)
.Where(CredentialsProtected)
.Select(ExtractProtectedCredentials)
this.liveOptions.Value.ProtectedLoginCredentials = loginCredentials
.Select(this.ProtectCredentials)
.Where(this.CredentialsProtected)
.Select(this.ExtractProtectedCredentials)
.ToList();
this.configurationManager.SaveConfiguration(config);
this.liveOptions.UpdateOption();
});
}
@@ -0,0 +1,17 @@
using Daybreak.Controls;
using Daybreak.Models;
using Microsoft.Web.WebView2.Wpf;
using System.Threading;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconBrowser
{
void InitializeWebView(WebView2 webView2, CancellationToken cancellationToken);
/// <summary>
/// Queue an icon request. The browser will attempt to download the icon. Monitor the <see cref="IconRequest.Finished"/> to be notified when the request has been served.
/// </summary>
/// <param name="iconRequest">Request model.</param>
void QueueIconRequest(IconRequest iconRequest);
}
}
@@ -1,12 +1,12 @@
using Daybreak.Models.Builds;
using System;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconRetriever
public interface IIconCache
{
Task<Optional<Stream>> GetIcon(Skill skill);
Task<Optional<Uri>> GetIconUri(Skill skill);
}
}
@@ -0,0 +1,16 @@
using Daybreak.Controls;
using Daybreak.Models.Progress;
using Microsoft.Web.WebView2.Wpf;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconDownloader
{
void SetBrowser(WebView2 chromiumBrowserWrapper);
bool DownloadComplete { get; }
Task<IconDownloadStatus> StartIconDownload();
void CancelIconDownload();
}
}
@@ -0,0 +1,178 @@
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Models.Builds;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Core.Extensions;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconBrowser : IIconBrowser
{
// Sometimes due to browser issues, retrieved base64 is just a blank jpeg. This is the base64 of the image.
private const string FaultyBase64 = "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/bAEMBAwMDBAMECAQECBALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/AABEIAEAAQAMBIgACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AlUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//2Q==";
private const string LargeFaultyBase64 = "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/bAEMBAwMDBAMECAQECBALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/AABEIAIAAQAMBIgACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AlUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//2Q==";
private const string BaseUrl = "https://wiki.guildwars.com";
private const string QueryUrl = $"wiki/File:{NamePlaceholder}.jpg";
private const string NamePlaceholder = "[SKILLNAME]";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly ConcurrentQueue<IconRequest> iconRequests = new();
private readonly ILogger<IconBrowser> logger;
private WebView2 browserWrapper;
private CancellationToken cancellationToken;
public IconBrowser(
ILogger<IconBrowser> logger)
{
this.logger = logger.ThrowIfNull();
}
public void InitializeWebView(WebView2 webView2, CancellationToken cancellationToken)
{
this.browserWrapper = webView2.ThrowIfNull();
this.cancellationToken = cancellationToken;
Task.Run(this.PeriodicallyServeRequests, cancellationToken);
}
public void QueueIconRequest(IconRequest iconRequest)
{
this.iconRequests.Enqueue(iconRequest);
}
private async Task PeriodicallyServeRequests()
{
while(this.cancellationToken.IsCancellationRequested is false)
{
await Application.Current.Dispatcher.InvokeAsync(async () =>
{
await this.ServeRequest();
});
}
}
private async Task ServeRequest()
{
if (this.cancellationToken.IsCancellationRequested)
{
return;
}
if (this.iconRequests.TryDequeue(out var request) is false)
{
await Task.Delay(1000);
return;
}
var logger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyServeRequests), request.Skill?.Name);
logger.LogInformation($"Retrieving icon");
while (this.browserWrapper is null)
{
if (this.cancellationToken.IsCancellationRequested)
{
return;
}
logger.LogInformation($"Browser is not yet initialized. Waiting");
await Task.Delay(1000);
}
try
{
await this.browserWrapper.EnsureCoreWebView2Async();
}
catch(Exception e)
{
}
var curedSkillName = request.Skill.AlternativeName.IsNullOrWhiteSpace() ?
request.Skill.Name.Replace(" ", "_") :
request.Skill.AlternativeName.Replace(" ", "_");
var skillIconUrl = $"{BaseUrl}/{QueryUrl.Replace(NamePlaceholder, curedSkillName)}";
logger.LogInformation($"Looking for icon at {skillIconUrl}");
this.browserWrapper.CoreWebView2.Navigate(skillIconUrl);
for (var i = 0; i < 5; i++)
{
if (this.cancellationToken.IsCancellationRequested)
{
return;
}
logger.LogInformation("Executing extraction script");
var responseTask = await Application.Current.Dispatcher.InvokeAsync(async () =>
{
return await this.browserWrapper.ExecuteScriptAsync(Scripts.GetHrefFromSkillPage);
});
var response = await responseTask;
logger.LogInformation("Parsing response");
var iconPayload = JsonConvert.DeserializeObject<IconPayload>(response);
if (iconPayload is null)
{
logger.LogInformation("Bad response");
await Task.Delay(1000);
continue;
}
if (iconPayload.SkillUrl != skillIconUrl.Replace("\"", "%22"))
{
logger.LogInformation("Retrieved icon doesn't match");
await Task.Delay(1000);
continue;
}
var potentialBase64 = iconPayload.SkillImage.Split(',').Skip(1).FirstOrDefault();
if (potentialBase64 == FaultyBase64 ||
potentialBase64 == LargeFaultyBase64)
{
logger.LogInformation("Faulty base64 retrieved");
await Task.Delay(1000);
continue;
}
byte[] bytes;
try
{
bytes = Convert.FromBase64String(potentialBase64);
}
catch
{
logger.LogError("Failed to parse base64");
await Task.Delay(1000);
continue;
}
await SaveIconLocally(request.Skill, bytes);
request.IconBase64 = potentialBase64;
request.Finished = true;
break;
}
logger.LogError($"Failed to retrieve icon");
request.Finished = true;
}
private static async Task<string> SaveIconLocally(Skill skill, byte[] data)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
await File.WriteAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName), data);
return curedSkillName;
}
}
}
@@ -0,0 +1,56 @@
using Daybreak.Models.Builds;
using Microsoft.Extensions.Logging;
using System;
using System.Core.Extensions;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconCache : IIconCache
{
private const string NamePlaceholder = "[SKILLNAME]";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly ILogger<IconCache> logger;
public IconCache(
ILogger<IconCache> logger)
{
this.logger = logger.ThrowIfNull();
if (Directory.Exists(IconsDirectoryName) is false)
{
Directory.CreateDirectory(IconsDirectoryName);
}
}
public Task<Optional<Uri>> GetIconUri(Skill skill)
{
var maybeIconUri = this.GetLocalIcon(skill);
if (maybeIconUri.ExtractValue() is Uri uri)
{
return Task.FromResult(Optional.FromValue(uri));
}
return Task.FromResult(Optional.None<Uri>());
}
private Optional<Uri> GetLocalIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
this.logger.LogInformation("Checking local icon cache");
if (File.Exists(IconsLocation.Replace(NamePlaceholder, curedSkillName)))
{
this.logger.LogInformation("Local icon cache found. Retrieving icon");
return new Uri(AppDomain.CurrentDomain.BaseDirectory + "/" + IconsLocation.Replace(NamePlaceholder, curedSkillName), UriKind.Absolute);
}
this.logger.LogWarning("No local icon cache found");
return Optional.None<Uri>();
}
}
}
@@ -0,0 +1,239 @@
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Models.Builds;
using Daybreak.Models.Progress;
using Daybreak.Services.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconDownloader : IIconDownloader, IApplicationLifetimeService
{
private readonly IIconBrowser iconBrowser;
private readonly IIconCache iconCache;
private readonly IConfigurationManager configurationManager;
private readonly ILogger<IconDownloader> logger;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly ILogger<ChromiumBrowserWrapper> browserLogger;
private WebView2 browserWrapper;
private CancellationTokenSource cancellationTokenSource;
private IconDownloadStatus iconDownloadStatus;
public bool DownloadComplete { get; private set; }
public bool Downloading => this.cancellationTokenSource is not null;
public IconDownloader(
IIconBrowser iconBrowser,
IIconCache iconCache,
IConfigurationManager configurationManager,
ILogger<IconDownloader> logger,
ILiveOptions<ApplicationConfiguration> liveOptions,
ILogger<ChromiumBrowserWrapper> browserLogger)
{
this.iconBrowser = iconBrowser.ThrowIfNull();
this.iconCache = iconCache.ThrowIfNull();
this.configurationManager = configurationManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.browserLogger = browserLogger.ThrowIfNull();
this.HookIntoConfigurationChanges();
}
public void SetBrowser(WebView2 chromiumBrowserWrapper)
{
if (this.browserWrapper is not null)
{
throw new InvalidOperationException("Browser is already set");
}
this.browserWrapper = chromiumBrowserWrapper;
}
public async Task<IconDownloadStatus> StartIconDownload()
{
while(this.browserWrapper is null)
{
await Task.Delay(100);
}
this.logger.LogInformation("Starting download");
if (this.Downloading)
{
this.logger.LogInformation("Download already running");
return this.iconDownloadStatus;
}
this.cancellationTokenSource = new();
this.iconDownloadStatus = new IconDownloadStatus();
Task.Run(this.DownloadIcons);
return this.iconDownloadStatus;
}
public void CancelIconDownload()
{
this.cancellationTokenSource?.Cancel();
this.cancellationTokenSource?.Dispose();
this.cancellationTokenSource = null;
}
private async Task DownloadIcons()
{
this.logger.LogInformation("Beginning icon download");
if (await TestBrowserSupported() is false)
{
this.logger.LogError("Browser not supported. Icon downloading stopped");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.BrowserNotSupported;
return;
}
var progressIncrement = 100d / Skill.Skills.Count();
var progressValue = 0d;
var skillsToDownload = new List<Skill>();
foreach(var skill in Skill.Skills.OrderBy(s => s.Name))
{
if (skill == Skill.NoSkill)
{
continue;
}
var logger = this.logger.CreateScopedLogger(nameof(this.DownloadIcons), skill.Name);
logger.LogInformation("Verifying if icon exists");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Checking(skill.Name, progressValue);
if ((await this.iconCache.GetIconUri(skill)).ExtractValue() is not null)
{
progressValue += progressIncrement;
await Task.Delay(1);
continue;
}
skillsToDownload.Add(skill);
}
if (skillsToDownload.Count == 0)
{
this.logger.LogInformation("No icons missing. Stopping download");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Finished;
return;
}
this.iconBrowser.InitializeWebView(this.browserWrapper, this.cancellationTokenSource.Token);
var incomplete = false;
foreach (var skill in skillsToDownload)
{
if (this.cancellationTokenSource?.IsCancellationRequested is null or true)
{
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Stopped(progressValue);
return;
}
if (skill == Skill.NoSkill)
{
progressValue += progressIncrement;
continue;
}
logger.LogInformation("Downloading icon");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Downloading(skill.Name, progressValue);
var request = new IconRequest { Skill = skill };
this.iconBrowser.QueueIconRequest(request);
while (request.Finished is false)
{
await Task.Delay(1000);
}
if (request.IconBase64.IsNullOrWhiteSpace())
{
logger.LogWarning("Failed to download icon");
incomplete = true;
}
else
{
logger.LogInformation("Downloaded icon");
}
progressValue += progressIncrement;
}
if (incomplete)
{
this.logger.LogError("Failed to download all icons. Retrying");
await this.DownloadIcons();
}
else
{
Application.Current.Dispatcher.Invoke(() =>
{
this.browserWrapper.IsEnabled = false;
this.browserWrapper.Dispose();
});
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Finished;
this.DownloadComplete = true;
}
}
private static async Task<bool> TestBrowserSupported()
{
CoreWebView2Environment coreWebView2Environment;
try
{
var task = await Application.Current.Dispatcher.InvokeAsync(async () =>
{
return await CoreWebView2Environment.CreateAsync().ConfigureAwait(true);
});
coreWebView2Environment = await task;
}
catch (Exception)
{
return false;
}
return coreWebView2Environment is not null;
}
private void HookIntoConfigurationChanges()
{
this.configurationManager.ConfigurationChanged += async (_, _) =>
{
var configuration = this.configurationManager.GetConfiguration();
if (configuration.ExperimentalFeatures.DownloadIcons)
{
await this.StartIconDownload();
}
else
{
this.CancelIconDownload();
}
};
}
public async void OnStartup()
{
var configuration = this.configurationManager.GetConfiguration();
if (configuration.ExperimentalFeatures.DownloadIcons)
{
await this.StartIconDownload();
}
}
public void OnClosing()
{
this.cancellationTokenSource?.Cancel();
}
}
}
@@ -1,138 +0,0 @@
using Daybreak.Models.Builds;
using Daybreak.Services.Configuration;
using HtmlAgilityPack;
using Microsoft.Extensions.Logging;
using System;
using System.Extensions;
using System.Http;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconRetriever : IIconRetriever
{
private const string NamePlaceholder = "[SKILLNAME]";
private const string BaseUrl = "https://wiki.guildwars.com";
private const string QueryUrl = $"wiki/File:{NamePlaceholder}.jpg";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly IHttpClient<IconRetriever> httpClient;
private readonly ILogger<IconRetriever> logger;
private readonly IConfigurationManager configurationManager;
public IconRetriever(
ILogger<IconRetriever> logger,
IHttpClient<IconRetriever> httpClient,
IConfigurationManager configurationManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.httpClient.BaseAddress = new Uri(BaseUrl);
if (Directory.Exists(IconsDirectoryName) is false)
{
Directory.CreateDirectory(IconsDirectoryName);
}
}
public async Task<Optional<Stream>> GetIcon(Skill skill)
{
if (this.configurationManager.GetConfiguration().KeepLocalIconCache)
{
this.logger.LogInformation($"{nameof(IconRetriever)} configured to look first in cache before downloading icons");
var maybeIcon = await this.GetLocalIcon(skill);
if (maybeIcon.ExtractValue() is Stream stream)
{
return stream;
}
}
else
{
this.logger.LogInformation($"{nameof(IconRetriever)} configured to skip local cache. Downloading icon");
}
return await this.DownloadIcon(skill);
}
private async Task<Optional<Stream>> DownloadIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_");
var skillIconUrl = QueryUrl.Replace(NamePlaceholder, curedSkillName);
this.logger.LogInformation($"Looking up icon for skill '{skill.Name}' at url {skillIconUrl}");
using var response = await this.httpClient.GetAsync(skillIconUrl).ConfigureAwait(false);
if (response.IsSuccessStatusCode is false)
{
this.logger.LogError($"Client returned status code {response.StatusCode}");
return Optional.None<Stream>();
}
this.logger.LogInformation("Crawling through response for href to latest icon url");
var doc = new HtmlDocument();
doc.LoadHtml(await response.Content.ReadAsStringAsync());
var url = GetHref(doc);
if (url is null)
{
this.logger.LogError("Failed to find latest icon url");
return Optional.None<Stream>();
}
this.logger.LogInformation($"Found latest icon url at {BaseUrl + "/" + url}. Requesting stream");
using var iconResponse = await this.httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
this.logger.LogInformation("Retrieved latest icon stream");
var iconData = await iconResponse.Content.ReadAsByteArrayAsync();
if (this.configurationManager.GetConfiguration().KeepLocalIconCache)
{
await this.SaveIconLocally(skill, iconData);
}
return new MemoryStream(iconData);
}
this.logger.LogError($"Failed to retrieve icon from {BaseUrl + "/" + url}");
return Optional.None<Stream>();
}
private async Task<Optional<Stream>> GetLocalIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
this.logger.LogInformation("Checking local icon cache");
if (File.Exists(IconsLocation.Replace(NamePlaceholder, curedSkillName)))
{
this.logger.LogInformation("Local icon cache found. Retrieving icon");
return new MemoryStream(await File.ReadAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName)));
}
this.logger.LogWarning("No local icon cache found");
return Optional.None<Stream>();
}
private async Task SaveIconLocally(Skill skill, byte[] data)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
await File.WriteAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName), data);
}
private static string GetHref(HtmlDocument doc)
{
foreach (var child in doc.DocumentNode.Descendants("a"))
{
var targetAttribute = child.Attributes.Where(a => a.Name == "href" && a.Value.Contains("images")).FirstOrDefault();
if (targetAttribute is not null)
{
return targetAttribute.Value;
}
}
return null;
}
}
}
@@ -1,10 +1,10 @@
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Models;
using Daybreak.Services.KeyboardHook;
using Microsoft.Extensions.Logging;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Text;
@@ -17,32 +17,23 @@ namespace Daybreak.Services.KeyboardMacros
{
private readonly CancellationTokenSource cancellationTokenSource = new();
private readonly IKeyboardHookService keyboardHookService;
private readonly IConfigurationManager configurationManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly HashSet<Keys> KeysDown = new();
private IEnumerable<KeyMacro> loadedMacros;
private bool gameActive, hookEnabled;
private bool gameActive;
private IntPtr gwWindowHwnd;
public MacroService(
IKeyboardHookService keyboardHookService,
IConfigurationManager configurationManager)
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.keyboardHookService = keyboardHookService.ThrowIfNull(nameof(keyboardHookService));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.configurationManager.ConfigurationChanged += (s, e) => this.LoadConfiguration();
this.LoadConfiguration();
this.keyboardHookService.KeyboardPressed += this.KeyboardHookService_KeyboardPressed;
this.SetupGameActiveChecker();
}
private void LoadConfiguration()
{
this.hookEnabled = this.configurationManager.GetConfiguration().ExperimentalFeatures.CanInterceptKeys;
this.loadedMacros = this.configurationManager.GetConfiguration().ExperimentalFeatures.Macros ?? new List<KeyMacro>();
}
private void SetupGameActiveChecker()
{
TaskExtensions.RunPeriodicAsync(() =>
@@ -62,12 +53,12 @@ namespace Daybreak.Services.KeyboardMacros
},
TimeSpan.Zero,
TimeSpan.FromMilliseconds(33),
cancellationTokenSource.Token);
this.cancellationTokenSource.Token);
}
private void KeyboardHookService_KeyboardPressed(object sender, KeyboardHookEventArgs e)
{
if (this.gameActive && this.hookEnabled)
if (this.gameActive && this.liveOptions.Value.ExperimentalFeatures.CanInterceptKeys)
{
if (e.KeyboardState == KeyboardState.KeyDown)
{
@@ -79,7 +70,7 @@ namespace Daybreak.Services.KeyboardMacros
return;
}
e.Handled = this.loadedMacros
e.Handled = this.liveOptions.Value.ExperimentalFeatures.Macros
.Where(keyMacro => MacroContainsKey(keyMacro, e.KeyboardInput.Key))
.Where(this.MacroHit)
.Do(this.HandleMacro)
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Logging;
namespace Daybreak.Services.Logging
{
public sealed class CompositeLogsWriter : ILogsWriter
{
private readonly IEnumerable<ILogsWriter> logsWriters;
public CompositeLogsWriter(params ILogsWriter[] innerLogsWriters)
{
this.logsWriters = innerLogsWriters;
}
public void WriteLog(Log log)
{
foreach (var logWriter in this.logsWriters)
{
logWriter.WriteLog(log);
}
}
}
}
@@ -0,0 +1,13 @@
using System.Diagnostics;
using System.Logging;
namespace Daybreak.Services.Logging
{
public sealed class DebugLogsWriter : IDebugLogsWriter
{
public void WriteLog(Log log)
{
Debug.WriteLine($"[{log.LogTime}]\t[{log.LogLevel}]\t[{log.Category}]\n{log.Message}");
}
}
}
@@ -0,0 +1,8 @@
using System.Logging;
namespace Daybreak.Services.Logging
{
public interface IDebugLogsWriter : ILogsWriter
{
}
}
+3 -3
View File
@@ -2,14 +2,14 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using WpfExtended.Logging;
using System.Logging;
namespace Daybreak.Services.Logging
{
public interface ILogsManager : ILogsWriter
{
IEnumerable<Log> GetLogs(Expression<Func<Log, bool>> filter);
IEnumerable<Log> GetLogs();
IEnumerable<Models.Log> GetLogs(Expression<Func<Models.Log, bool>> filter);
IEnumerable<Models.Log> GetLogs();
int DeleteLogs();
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Extensions;
using System.Linq.Expressions;
using WpfExtended.Models;
using System.Logging;
namespace Daybreak.Services.Logging
{
@@ -56,7 +56,6 @@ namespace Daybreak.Services.ViewManagement
private void ShowViewInner(Type viewType, object dataContext)
{
var scopedManager = this.serviceManager.CreateScope();
scopedManager.As<IServiceManager>().RegisterScoped<ScopeMetadata, ScopeMetadata>((sp) => new ScopeMetadata(new CorrelationVector()));
Application.Current.Dispatcher.Invoke(() =>
{
var view = scopedManager.GetService(viewType).As<UserControl>();
@@ -0,0 +1,39 @@
using Daybreak.Configuration;
using Daybreak.Services.Configuration;
using System;
using System.Configuration;
using System.Extensions;
namespace Daybreak.Services.Options
{
public sealed class ApplicationConfigurationOptionsManager : IOptionsManager
{
private readonly IConfigurationManager configurationManager;
public ApplicationConfigurationOptionsManager(IConfigurationManager configurationManager)
{
this.configurationManager = configurationManager;
}
public T GetOptions<T>() where T : class
{
if (typeof(T) == typeof(ApplicationConfiguration))
{
return this.configurationManager.GetConfiguration().Cast<T>();
}
throw new InvalidOperationException($"{nameof(ApplicationConfigurationOptionsManager)} cannot return options of type {typeof(T).Name}");
}
public void UpdateOptions<T>(T value) where T : class
{
if (typeof(T) == typeof(ApplicationConfiguration))
{
this.configurationManager.SaveConfiguration(value.Cast<ApplicationConfiguration>());
return;
}
throw new InvalidOperationException($"{nameof(ApplicationConfigurationOptionsManager)} cannot save options of type {typeof(T).Name}");
}
}
}
+13 -6
View File
@@ -1,16 +1,20 @@
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Services.Configuration;
using ShellLink;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.IO;
namespace Daybreak.Services.Shortcuts
{
//TODO: Fix dependency on IConfigurationManager
public sealed class ShortcutManager : IShortcutManager
{
private const string ShortcutName = "Daybreak.lnk";
private readonly IConfigurationManager configurationManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
public bool ShortcutEnabled {
get => this.ShortcutExists();
@@ -27,16 +31,19 @@ namespace Daybreak.Services.Shortcuts
}
}
public ShortcutManager(IConfigurationManager configurationManager)
public ShortcutManager(
IConfigurationManager configurationManager,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.configurationManager.ConfigurationChanged += (_, _) => this.LoadConfiguration();
this.LoadConfiguration();
}
private void LoadConfiguration()
{
var shortcutEnabled = this.configurationManager.GetConfiguration().PlaceShortcut;
var shortcutEnabled = this.liveOptions.Value.PlaceShortcut;
if (shortcutEnabled && this.ShortcutEnabled is false)
{
this.ShortcutEnabled = true;
@@ -49,7 +56,7 @@ namespace Daybreak.Services.Shortcuts
private bool ShortcutExists()
{
var shortcutFolder = this.configurationManager.GetConfiguration().ShortcutLocation;
var shortcutFolder = this.liveOptions.Value.ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
if (File.Exists(shortcutPath))
{
@@ -73,7 +80,7 @@ namespace Daybreak.Services.Shortcuts
return;
}
var shortcutFolder = this.configurationManager.GetConfiguration().ShortcutLocation;
var shortcutFolder = this.liveOptions.Value.ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
var currentExecutable = Process.GetCurrentProcess()?.MainModule?.FileName;
var shortcut = Shortcut.CreateShortcut(currentExecutable);
@@ -92,7 +99,7 @@ namespace Daybreak.Services.Shortcuts
return;
}
var shortcutFolder = this.configurationManager.GetConfiguration().ShortcutLocation;
var shortcutFolder = this.liveOptions.Value.ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
File.Delete(shortcutPath);
}
+40 -173
View File
@@ -1,17 +1,18 @@
using Daybreak.Exceptions;
using Daybreak.Models;
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Models.Github;
using Daybreak.Services.Configuration;
using Daybreak.Models.Progress;
using Daybreak.Services.Updater.PostUpdate;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Daybreak.Views;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using System.Http;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -25,55 +26,41 @@ namespace Daybreak.Services.Updater
{
public sealed class ApplicationUpdater : IApplicationUpdater
{
private const string LaunchActionName = "Launch_Daybreak";
private const string ExecutionPolicyKey = "ExecutionPolicy";
private const string TemporaryInstallerFileName = "Daybreak.Installer.Temp.exe";
private const string InstallerFileName = "Daybreak.Installer.exe";
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 ExecutableNameTag = "{EXECUTABLE}";
private const string WorkingDirectoryTag = "{WORKINGDIRECTORY}";
private const string RefTagPrefix = "/refs/tags";
private const string VersionListUrl = "https://api.github.com/repos/AlexMacocian/Daybreak/git/refs/tags";
private const string Url = "https://github.com/AlexMacocian/Daybreak/releases/latest";
private const string DownloadUrl = $"https://github.com/AlexMacocian/Daybreak/releases/download/{VersionTag}/Daybreak{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 PrepareScheduledAction = $"$action = New-ScheduledTaskAction -Execute {ExecutableNameTag} -WorkingDirectory {WorkingDirectoryTag}";
private const string PrepareTriggerForAction = $"$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date)";
private const string RegisterScheduledAction = $"Register-ScheduledTask -Action $action -Trigger $trigger -TaskName {LaunchActionName} | Out-Null";
private const string LaunchScheduledAction = $"Start-ScheduledTask -TaskName {LaunchActionName}";
private const string SleepOneSecond = $"Start-Sleep -s 1";
private const string UnregisterScheduledAction = $"Unregister-ScheduledTask -TaskName {LaunchActionName} -Confirm:$false";
private const string RemoveTempFile = $"Remove-item {TempFile}";
private const string RemovePs1 = $"Remove-item {ExtractAndRunPs1}";
private readonly CancellationTokenSource updateCancellationTokenSource = new();
private readonly ILogger<ApplicationUpdater> logger;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly IPostUpdateActionProvider postUpdateActionProvider;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly IHttpClient<ApplicationUpdater> httpClient;
private readonly ILogger<ApplicationUpdater> logger;
public Version CurrentVersion { get; }
public ApplicationUpdater(
ILogger<ApplicationUpdater> logger,
IConfigurationManager configurationManager,
IViewManager viewManager,
IHttpClient<ApplicationUpdater> httpClient)
IPostUpdateActionProvider postUpdateActionProvider,
ILiveOptions<ApplicationConfiguration> liveOptions,
IHttpClient<ApplicationUpdater> httpClient,
ILogger<ApplicationUpdater> logger)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
this.viewManager = viewManager.ThrowIfNull();
this.postUpdateActionProvider = postUpdateActionProvider.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.httpClient = httpClient.ThrowIfNull();
this.logger = logger.ThrowIfNull();
this.httpClient.DefaultRequestHeaders.Add("user-agent", "Daybreak Client");
if (Version.TryParse(Assembly.GetExecutingAssembly().GetName().Version.ToString(), out var currentVersion))
{
if (currentVersion.HasPrefix is false)
@@ -175,7 +162,7 @@ namespace Daybreak.Services.Updater
{
System.Extensions.TaskExtensions.RunPeriodicAsync(async () =>
{
if (this.configurationManager.GetConfiguration().AutoCheckUpdate is false)
if (this.liveOptions.Value.AutoCheckUpdate is false)
{
this.updateCancellationTokenSource.Cancel();
return;
@@ -190,23 +177,7 @@ namespace Daybreak.Services.Updater
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();
}
@@ -215,16 +186,7 @@ namespace Daybreak.Services.Updater
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");
});
this.ExecutePostUpdateActions();
}
}
@@ -244,92 +206,31 @@ namespace Daybreak.Services.Updater
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,
PrepareScheduledAction
.Replace(ExecutableNameTag, Process.GetCurrentProcess()?.MainModule?.FileName)
.Replace(WorkingDirectoryTag, Directory.GetCurrentDirectory()),
PrepareTriggerForAction,
RegisterScheduledAction,
LaunchScheduledAction,
SleepOneSecond,
UnregisterScheduledAction,
RemovePs1,
});
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $@"{Directory.GetCurrentDirectory()}\{ExtractAndRunPs1}",
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Maximized,
WorkingDirectory = Directory.GetCurrentDirectory(),
Verb = "runas"
},
FileName = InstallerFileName
}
};
this.logger.LogInformation("Created extractor script. Attempting to launch powershell");
this.logger.LogInformation("Launching installer");
if (process.Start() is false)
{
throw new InvalidOperationException("Failed to create and start powershell script");
throw new InvalidOperationException("Failed to launch installer");
}
}
private async void ExecutePostUpdateActions()
{
this.logger.LogInformation("Executing post-update actions");
foreach(var action in this.postUpdateActionProvider.GetPostUpdateActions())
{
this.logger.LogInformation($"Starting [{action.GetType().Name}]");
action.DoPostUpdateAction();
await action.DoPostUpdateActionAsync();
this.logger.LogInformation($"Finished [{action.GetType().Name}]");
}
}
@@ -367,40 +268,6 @@ namespace Daybreak.Services.Updater
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);
@@ -1,4 +1,4 @@
using Daybreak.Models;
using Daybreak.Models.Progress;
using Daybreak.Models.Versioning;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Logging;
using System.Core.Extensions;
using System.IO;
namespace Daybreak.Services.Updater.PostUpdate.Actions
{
public sealed class RenameInstallerAction : PostUpdateActionBase
{
private const string TemporaryInstallerFileName = "Daybreak.Installer.Temp.exe";
private const string InstallerFileName = "Daybreak.Installer.exe";
private readonly ILogger<RenameInstallerAction> logger;
public RenameInstallerAction(
ILogger<RenameInstallerAction> logger)
{
this.logger = logger.ThrowIfNull();
}
public override void DoPostUpdateAction()
{
if (File.Exists(TemporaryInstallerFileName))
{
this.logger.LogInformation("Detected new installer version. Overwriting old installer with new one");
File.Copy(TemporaryInstallerFileName, InstallerFileName, true);
this.logger.LogInformation("Deleting new installer temporary file");
File.Delete(TemporaryInstallerFileName);
}
}
}
}
@@ -0,0 +1,6 @@
namespace Daybreak.Services.Updater.PostUpdate
{
public interface IPostUpdateActionManager : IPostUpdateActionProducer, IPostUpdateActionProvider
{
}
}
@@ -0,0 +1,8 @@
namespace Daybreak.Services.Updater.PostUpdate
{
public interface IPostUpdateActionProducer
{
void AddPostUpdateAction<T>()
where T : PostUpdateActionBase;
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Daybreak.Services.Updater.PostUpdate
{
public interface IPostUpdateActionProvider
{
IEnumerable<PostUpdateActionBase> GetPostUpdateActions();
}
}
@@ -0,0 +1,16 @@
using System.Threading.Tasks;
namespace Daybreak.Services.Updater.PostUpdate
{
public abstract class PostUpdateActionBase
{
public virtual void DoPostUpdateAction()
{
}
public virtual Task DoPostUpdateActionAsync()
{
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,33 @@
using Microsoft.Extensions.Logging;
using Slim;
using System.Collections.Generic;
using System.Core.Extensions;
namespace Daybreak.Services.Updater.PostUpdate
{
public sealed class PostUpdateActionManager : IPostUpdateActionManager
{
private readonly IServiceManager serviceManager;
private readonly ILogger<PostUpdateActionManager> logger;
public PostUpdateActionManager(
IServiceManager serviceManager,
ILogger<PostUpdateActionManager> logger)
{
this.serviceManager = serviceManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
}
public void AddPostUpdateAction<T>()
where T : PostUpdateActionBase
{
this.serviceManager.RegisterSingleton<T>();
this.logger.LogDebug($"Added post update action {typeof(T).Name}");
}
public IEnumerable<PostUpdateActionBase> GetPostUpdateActions()
{
return this.serviceManager.GetServicesOfType<PostUpdateActionBase>();
}
}
}
+27 -46
View File
@@ -22,7 +22,7 @@ namespace Daybreak
}
}
private static int Iterations { get; } = 10000;
private static RNGCryptoServiceProvider Rng { get; } = new RNGCryptoServiceProvider();
private static RandomNumberGenerator Rng { get; } = RandomNumberGenerator.Create();
public static int BlockSize { get => Aes.BlockSize; }
@@ -31,29 +31,19 @@ namespace Daybreak
var saltBytes = Generate128BitsOfRandomEntropy();
var ivBytes = Generate128BitsOfRandomEntropy();
using (var password = new Rfc2898DeriveBytes(key, saltBytes, Iterations))
{
var keyBytes = password.GetBytes(Aes.KeySize / 8);
using (var encryptor = Aes.CreateEncryptor(keyBytes, ivBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(bytes, 0, bytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
using (var encryptedMemoryStream = new MemoryStream((int)(saltBytes.Length + ivBytes.Length + memoryStream.Length)))
{
encryptedMemoryStream.Write(saltBytes, 0, saltBytes.Length);
encryptedMemoryStream.Write(ivBytes, 0, ivBytes.Length);
encryptedMemoryStream.Write(memoryStream.ToArray(), 0, (int)memoryStream.Length);
return encryptedMemoryStream.ToArray();
}
}
}
}
}
using var password = new Rfc2898DeriveBytes(key, saltBytes, Iterations);
var keyBytes = password.GetBytes(Aes.KeySize / 8);
using var encryptor = Aes.CreateEncryptor(keyBytes, ivBytes);
using var memoryStream = new MemoryStream();
using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
cryptoStream.Write(bytes, 0, bytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
using var encryptedMemoryStream = new MemoryStream((int)(saltBytes.Length + ivBytes.Length + memoryStream.Length));
encryptedMemoryStream.Write(saltBytes, 0, saltBytes.Length);
encryptedMemoryStream.Write(ivBytes, 0, ivBytes.Length);
encryptedMemoryStream.Write(memoryStream.ToArray(), 0, (int)memoryStream.Length);
return encryptedMemoryStream.ToArray();
}
public static byte[] DecryptBytes(this byte[] bytes, byte[] key)
@@ -61,29 +51,20 @@ namespace Daybreak
var saltBytes = new byte[Aes.BlockSize / 8];
var ivBytes = new byte[Aes.BlockSize / 8];
var cipherBytes = new byte[bytes.Length - Aes.BlockSize / 4];
using (MemoryStream encryptedStream = new MemoryStream(bytes))
{
encryptedStream.Read(saltBytes, 0, saltBytes.Length);
encryptedStream.Read(ivBytes, 0, ivBytes.Length);
encryptedStream.Read(cipherBytes, 0, cipherBytes.Length);
}
using (var password = new Rfc2898DeriveBytes(key, saltBytes, Iterations))
{
var keyBytes = password.GetBytes(Aes.KeySize / 8);
using (var decryptor = Aes.CreateDecryptor(keyBytes, ivBytes))
{
using (var memoryStream = new MemoryStream(cipherBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[memoryStream.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
return plainTextBytes.Take(decryptedByteCount).ToArray();
}
}
}
}
using var encryptedStream = new MemoryStream(bytes);
encryptedStream.Read(saltBytes, 0, saltBytes.Length);
encryptedStream.Read(ivBytes, 0, ivBytes.Length);
encryptedStream.Read(cipherBytes, 0, cipherBytes.Length);
using var password = new Rfc2898DeriveBytes(key, saltBytes, Iterations);
var keyBytes = password.GetBytes(Aes.KeySize / 8);
using var decryptor = Aes.CreateDecryptor(keyBytes, ivBytes);
using var memoryStream = new MemoryStream(cipherBytes);
using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
var plainTextBytes = new byte[memoryStream.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
return plainTextBytes.Take(decryptedByteCount).ToArray();
}
private static byte[] Generate128BitsOfRandomEntropy()
+27
View File
@@ -31,5 +31,32 @@
};
window.chrome.webview.postMessage(jsonObject);
});";
public const string GetHrefFromSkillPage = @"
new function(){
var img = document.getElementsByClassName('fullImageLink')[0].childNodes[0].childNodes[0];
function getDataUrl(img) {
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set width and height
canvas.width = img.width;
canvas.height = img.height;
// Draw the image
ctx.drawImage(img, 0, 0);
return canvas.toDataURL('image/jpeg');
}
console.log(img.src);
var imageBase64 = getDataUrl(img);
console.log(imageBase64);
let jsonObject =
{
skillUrl: document.URL,
skillImage: imageBase64
}
window.chrome.webview.postMessage(jsonObject);
return jsonObject;
}";
}
}
+1 -12
View File
@@ -1,20 +1,9 @@
using Newtonsoft.Json;
using System.Text;
using System.Text;
namespace Daybreak.Utils
{
public static class SerializationExtensions
{
public static string Serialize<T>(this T obj)
{
return JsonConvert.SerializeObject(obj);
}
public static T Deserialize<T>(this string serializedObject)
{
return JsonConvert.DeserializeObject<T>(serializedObject);
}
public static byte[] AsBytes(this string s)
{
return Encoding.UTF8.GetBytes(s);
+53
View File
@@ -0,0 +1,53 @@
using System;
using System.Linq;
namespace Utils
{
public static class StringUtils
{
public static int DamerauLevenshteinDistance(string s, string t)
{
var (height, width) = (s.Length + 1, t.Length + 1);
var matrix = new int[height, width];
for (var j = 0; j < height; j++) { matrix[j, 0] = j; };
for (var i = 0; i < width; i++) { matrix[0, i] = i; };
for (var j = 1; j < height; j++)
{
for (var i = 1; i < width; i++)
{
var cost = (s[j - 1] == t[i - 1]) ? 0 : 1;
var insertion = matrix[j, i - 1] + 1;
var deletion = matrix[j - 1, i] + 1;
var substitution = matrix[j - 1, i - 1] + cost;
var distance = Math.Min(insertion, Math.Min(deletion, substitution));
if (j > 1 && i > 1 && s[j - 1] == t[i - 2] && s[j - 2] == t[i - 1])
{
distance = Math.Min(distance, matrix[j - 2, i - 2] + cost);
}
matrix[j, i] = distance;
}
}
return matrix[height - 1, width - 1];
}
/// <summary>
/// Returns true if stringToSearch is somewhat close to searchString.
/// </summary>
/// <param name="stringToSearch"></param>
/// <param name="searchString"></param>
/// <returns>True if strings match.</returns>
public static bool MatchesSearchString(string stringToSearch, string searchString)
{
// Return true if either the distance between the entire text and the searchstring is small enough, or if any of the words are close to the search string.
return DamerauLevenshteinDistance(stringToSearch.ToLower()[..Math.Min(stringToSearch.Length, searchString.Length)], searchString.ToLower()) < 3 ||
stringToSearch.Split(' ').Any(word => DamerauLevenshteinDistance(word.ToLower()[..Math.Min(word.Length, searchString.Length)], searchString.ToLower()) < 3);
}
}
}
+7 -26
View File
@@ -1,8 +1,9 @@
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Services.Privilege;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
@@ -16,56 +17,36 @@ namespace Daybreak.Views
{
private readonly ILogger<AskUpdateView> logger;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
private readonly IPrivilegeManager privilegeManager;
private readonly IApplicationUpdater applicationUpdater;
public AskUpdateView(
ILogger<AskUpdateView> logger,
IViewManager viewManager,
IConfigurationManager configurationManager,
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions,
IPrivilegeManager privilegeManager,
IApplicationUpdater applicationUpdater)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.InitializeComponent();
}
private bool CheckIfAdmin()
{
if (this.privilegeManager.AdminPrivileges is false)
{
this.privilegeManager.RequestAdminPrivileges<MainView>("Application needs to be in administrator mode in order to update.");
return false;
}
return true;
}
private void NoButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User declined update");
var config = this.configurationManager.GetConfiguration();
config.AutoCheckUpdate = false;
this.configurationManager.SaveConfiguration(config);
this.liveOptions.Value.AutoCheckUpdate = false;
this.liveOptions.UpdateOption();
this.viewManager.ShowView<MainView>();
}
private async void YesButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User accepted update");
var config = this.configurationManager.GetConfiguration();
config.AutoCheckUpdate = false;
this.configurationManager.SaveConfiguration(config);
if (this.CheckIfAdmin() is false)
{
return;
}
var latestVersion = (await this.applicationUpdater.GetVersions()).Last();
this.viewManager.ShowView<UpdateView>(latestVersion);
}
+7 -6
View File
@@ -1,17 +1,17 @@
using Daybreak.Controls;
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Views
{
@@ -38,8 +38,9 @@ namespace Daybreak.Views
public BuildTemplateView(
IViewManager viewManager,
IBuildTemplateManager buildTemplateManager,
IIconRetriever iconRetriever,
IConfigurationManager configurationManager,
IIconCache iconRetriever,
IIconBrowser iconBrowser,
ILiveOptions<ApplicationConfiguration> liveOptions,
ILogger<ChromiumBrowserWrapper> chromiumLogger,
ILogger<BuildTemplateView> logger)
{
@@ -47,7 +48,7 @@ namespace Daybreak.Views
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.BuildTemplate.InitializeTemplate(iconRetriever, configurationManager, buildTemplateManager, chromiumLogger);
this.BuildTemplate.InitializeTemplate(iconRetriever, iconBrowser, liveOptions, buildTemplateManager, chromiumLogger);
this.DataContextChanged += (sender, contextArgs) =>
{
if (contextArgs.NewValue is BuildEntry)
+6 -1
View File
@@ -10,6 +10,7 @@
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
@@ -17,7 +18,11 @@
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:AddButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5"
Clicked="AddButton_Clicked"></controls:AddButton>
<ListView Grid.Row="1" Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=BuildEntries, Mode=OneWay}"
<controls:SearchTextBox Grid.Row="1"
FontSize="24"
Foreground="White"
TextChanged="SearchTextBox_TextChanged"></controls:SearchTextBox>
<ListView Grid.Row="2" Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=BuildEntries, Mode=OneWay}"
MouseDoubleClick="ListView_MouseDoubleClick" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
+14 -1
View File
@@ -2,9 +2,12 @@
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.ViewManagement;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
using Utils;
namespace Daybreak.Views
{
@@ -16,6 +19,8 @@ namespace Daybreak.Views
private readonly IViewManager viewManager;
private readonly IBuildTemplateManager buildTemplateManager;
private IEnumerable<BuildEntry> buildEntries;
public ObservableCollection<BuildEntry> BuildEntries { get; } = new ObservableCollection<BuildEntry>();
public BuildsListView(
@@ -30,7 +35,8 @@ namespace Daybreak.Views
private void LoadBuilds()
{
this.BuildEntries.ClearAnd().AddRange(this.buildTemplateManager.GetBuilds());
this.buildEntries = this.buildTemplateManager.GetBuilds();
this.BuildEntries.ClearAnd().AddRange(this.buildEntries);
}
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
@@ -54,5 +60,12 @@ namespace Daybreak.Views
this.buildTemplateManager.RemoveBuild(e);
this.LoadBuilds();
}
private void SearchTextBox_TextChanged(object sender, string e)
{
this.BuildEntries.Clear();
this.BuildEntries.AddRange(
this.buildEntries.Where(b => StringUtils.MatchesSearchString(b.Name, e)));
}
}
}
+10 -10
View File
@@ -1,10 +1,10 @@
using Daybreak.Controls;
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
@@ -17,15 +17,15 @@ namespace Daybreak.Views
/// </summary>
public partial class ExecutablesView : UserControl
{
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
private readonly IViewManager viewManager;
public ObservableCollection<GuildwarsPath> Paths { get; } = new();
public ExecutablesView(
IConfigurationManager configurationManager,
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions,
IViewManager viewManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(nameof(liveUpdateableOptions));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.GetPaths();
@@ -33,7 +33,7 @@ namespace Daybreak.Views
private void GetPaths()
{
this.Paths.AddRange(this.configurationManager.GetConfiguration().GuildwarsPaths);
this.Paths.AddRange(this.liveUpdateableOptions.Value.GuildwarsPaths);
}
private void BackButton_Clicked(object sender, EventArgs e)
@@ -53,9 +53,8 @@ namespace Daybreak.Views
private void SaveButton_Clicked(object sender, EventArgs e)
{
var config = this.configurationManager.GetConfiguration();
config.GuildwarsPaths = this.Paths.ToList();
this.configurationManager.SaveConfiguration(config);
this.liveUpdateableOptions.Value.GuildwarsPaths = this.Paths.ToList();
this.liveUpdateableOptions.UpdateOption();
this.viewManager.ShowView<SettingsCategoryView>();
}
@@ -81,6 +80,7 @@ namespace Daybreak.Views
{
path.Default = false;
}
gwPath.Default = true;
var view = CollectionViewSource.GetDefaultView(this.Paths);
view.Refresh();
@@ -92,6 +92,7 @@
<TextBlock Text="GWToolbox startup delay (in ms)" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Detect build templates (in browser)" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Launch gw as current user" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Download icons" Foreground="White" FontSize="22" Height="30"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<ToggleButton IsChecked="{Binding ElementName=_this, Path=MultiLaunch, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
@@ -103,6 +104,8 @@
Height="30" Width="60"></ToggleButton>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=LaunchAsCurrentUser, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=DownloadIcons, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
</StackPanel>
</Grid>
</Grid>
+22 -43
View File
@@ -1,6 +1,7 @@
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Services.ViewManagement;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows;
@@ -15,79 +16,57 @@ namespace Daybreak.Views
/// </summary>
public partial class ExperimentalSettingsView : UserControl
{
public static readonly DependencyProperty MultiLaunchProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(MultiLaunch));
public static readonly DependencyProperty GWToolboxLaunchDelayProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, string>(nameof(GWToolboxLaunchDelay));
public static readonly DependencyProperty DynamicBuildLoadingProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(DynamicBuildLoading));
public static readonly DependencyProperty LaunchAsCurrentUserProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(LaunchAsCurrentUser));
public static readonly DependencyProperty MacrosEnabledProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(MacrosEnabled));
[GenerateDependencyProperty]
private bool launchAsCurrentUser;
[GenerateDependencyProperty]
private bool multiLaunch;
[GenerateDependencyProperty]
private bool dynamicBuildLoading;
[GenerateDependencyProperty]
private bool macrosEnabled;
[GenerateDependencyProperty]
public string gWToolboxLaunchDelay;
[GenerateDependencyProperty]
public bool downloadIcons;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
public bool LaunchAsCurrentUser
{
get => this.GetTypedValue<bool>(LaunchAsCurrentUserProperty);
set => this.SetValue(LaunchAsCurrentUserProperty, value);
}
public bool MultiLaunch
{
get => this.GetTypedValue<bool>(MultiLaunchProperty);
set => this.SetValue(MultiLaunchProperty, value);
}
public string GWToolboxLaunchDelay
{
get => this.GetTypedValue<string>(GWToolboxLaunchDelayProperty);
set => this.SetValue(GWToolboxLaunchDelayProperty, value);
}
public bool DynamicBuildLoading
{
get => this.GetTypedValue<bool>(DynamicBuildLoadingProperty);
set => this.SetValue(DynamicBuildLoadingProperty, value);
}
public bool MacrosEnabled
{
get => this.GetTypedValue<bool>(MacrosEnabledProperty);
set => this.SetValue(MacrosEnabledProperty, value);
}
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
public ExperimentalSettingsView(
IViewManager viewManager,
IConfigurationManager configurationManager)
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(nameof(liveUpdateableOptions));
this.InitializeComponent();
this.LoadExperimentalSettings();
}
private void LoadExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveUpdateableOptions.Value;
this.MultiLaunch = config.ExperimentalFeatures.MultiLaunchSupport;
this.GWToolboxLaunchDelay = config.ExperimentalFeatures.ToolboxAutoLaunchDelay.ToString();
this.DynamicBuildLoading = config.ExperimentalFeatures.DynamicBuildLoading;
this.LaunchAsCurrentUser = config.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser;
this.MacrosEnabled = config.ExperimentalFeatures.CanInterceptKeys;
this.DownloadIcons = config.ExperimentalFeatures.DownloadIcons;
}
private void SaveExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveUpdateableOptions.Value;
config.ExperimentalFeatures.MultiLaunchSupport = this.MultiLaunch;
config.ExperimentalFeatures.DynamicBuildLoading = this.DynamicBuildLoading;
config.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser = this.LaunchAsCurrentUser;
config.ExperimentalFeatures.CanInterceptKeys = this.MacrosEnabled;
config.ExperimentalFeatures.DownloadIcons = this.DownloadIcons;
if (int.TryParse(this.GWToolboxLaunchDelay, out var gwToolboxLaunchDelay))
{
config.ExperimentalFeatures.ToolboxAutoLaunchDelay = gwToolboxLaunchDelay;
}
this.configurationManager.SaveConfiguration(config);
this.liveUpdateableOptions.UpdateOption();
}
private void SaveButton_Clicked(object sender, EventArgs e)
+24
View File
@@ -0,0 +1,24 @@
<UserControl x:Class="Daybreak.Views.IconDownloadView"
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"
xmlns:controls="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
Loaded="IconDownloadView_Loaded"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<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, 0" Foreground="Black"
TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding ElementName=_this, Path=ProgressValue, Mode=OneWay}" Width="300" Height="20"></ProgressBar>
<controls:OpaqueButton Text="Continue" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="80" Height="25"
Clicked="OpaqueButton_Clicked" Foreground="Black" FontSize="16"></controls:OpaqueButton>
</StackPanel>
</Grid>
</UserControl>
+68
View File
@@ -0,0 +1,68 @@
using Daybreak.Models.Progress;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System;
using System.Core.Extensions;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for UpdateView.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class IconDownloadView : UserControl
{
private readonly IIconDownloader iconDownloader;
private readonly IViewManager viewManager;
private readonly ILogger<IconDownloadView> logger;
[GenerateDependencyProperty]
private string description;
[GenerateDependencyProperty]
private double progressValue;
public IconDownloadView(
IIconDownloader iconDownloader,
IViewManager viewManager,
ILogger<IconDownloadView> logger)
{
this.iconDownloader = iconDownloader.ThrowIfNull();
this.viewManager = viewManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
this.InitializeComponent();
}
private void OpaqueButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<BuildsListView>();
}
private async void IconDownloadView_Loaded(object sender, RoutedEventArgs e)
{
var iconDownloadStatus = await this.iconDownloader.StartIconDownload().ConfigureAwait(true);
if (iconDownloadStatus.CurrentStep is IconDownloadStatus.FinishedIconDownloadStep or
IconDownloadStatus.StoppedIconDownloadStep)
{
this.viewManager.ShowView<BuildsListView>();
}
iconDownloadStatus.PropertyChanged += (_, _) =>
{
this.Dispatcher.Invoke(() =>
{
this.Description = iconDownloadStatus.CurrentStep.Description;
this.ProgressValue = iconDownloadStatus.CurrentStep.Progress;
});
};
this.Description = iconDownloadStatus.CurrentStep.Description;
this.ProgressValue = iconDownloadStatus.CurrentStep.Progress;
}
}
}
+23 -19
View File
@@ -1,13 +1,14 @@
using Daybreak.Controls;
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Exceptions;
using Daybreak.Models.Builds;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.Screens;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Threading;
@@ -27,7 +28,7 @@ namespace Daybreak.Views
{
private readonly IApplicationLauncher applicationDetector;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
private readonly IScreenManager screenManager;
private readonly IBuildTemplateManager buildTemplateManager;
private readonly ILogger<ChromiumBrowserWrapper> browserLogger;
@@ -58,7 +59,7 @@ namespace Daybreak.Views
public MainView(
IApplicationLauncher applicationDetector,
IViewManager viewManager,
IConfigurationManager configurationManager,
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions,
IScreenManager screenManager,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> browserLogger)
@@ -66,30 +67,32 @@ namespace Daybreak.Views
this.browserLogger = browserLogger;
this.buildTemplateManager = buildTemplateManager.ThrowIfNull(nameof(buildTemplateManager));
this.screenManager = screenManager.ThrowIfNull(nameof(screenManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.applicationDetector = applicationDetector.ThrowIfNull(nameof(applicationDetector));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.PeriodicallyCheckGameState();
this.InitializeBrowsers();
this.NavigateToDefaults();
}
private void InitializeBrowsers()
private async void InitializeBrowsers()
{
this.LeftWebBrowser.InitializeBrowser(this.configurationManager, this.buildTemplateManager, this.browserLogger);
this.RightWebBrowser.InitializeBrowser(this.configurationManager, this.buildTemplateManager, this.browserLogger);
await this.LeftWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
await this.RightWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
this.NavigateToDefaults();
}
private void NavigateToDefaults()
{
var applicationConfiguration = this.configurationManager.GetConfiguration();
var applicationConfiguration = this.liveOptions.Value;
if (applicationConfiguration.BrowsersEnabled)
{
this.LeftBrowserFavoriteAddress = applicationConfiguration.LeftBrowserDefault;
this.RightBrowserFavoriteAddress = applicationConfiguration.RightBrowserDefault;
this.LeftBrowserAddress = applicationConfiguration.LeftBrowserDefault;
this.RightBrowserAddress = applicationConfiguration.RightBrowserDefault;
this.LeftWebBrowser.WebBrowser.CoreWebView2.Navigate(applicationConfiguration.LeftBrowserDefault);
this.RightWebBrowser.WebBrowser.CoreWebView2.Navigate(applicationConfiguration.RightBrowserDefault);
}
else
{
@@ -127,21 +130,22 @@ namespace Daybreak.Views
return;
}
if (this.configurationManager.GetConfiguration().SetGuildwarsWindowSizeOnLaunch)
if (this.liveOptions.Value.SetGuildwarsWindowSizeOnLaunch)
{
var id = this.configurationManager.GetConfiguration().DesiredGuildwarsScreen;
var id = this.liveOptions.Value.DesiredGuildwarsScreen;
var desiredScreen = this.screenManager.Screens.Skip(id).FirstOrDefault();
if (desiredScreen is null)
{
throw new InvalidOperationException($"Unable to set guildwars on desired screen. No screen with id {id}");
}
await Task.Delay(1000);
this.screenManager.MoveGuildwarsToScreen(desiredScreen);
}
if (this.configurationManager.GetConfiguration().ToolboxAutoLaunch is true)
if (this.liveOptions.Value.ToolboxAutoLaunch is true)
{
var delay = this.configurationManager.GetConfiguration().ExperimentalFeatures.ToolboxAutoLaunchDelay;
var delay = this.liveOptions.Value.ExperimentalFeatures.ToolboxAutoLaunchDelay;
await Task.Delay(delay);
await this.applicationDetector.LaunchGuildwarsToolbox();
}
@@ -180,23 +184,23 @@ namespace Daybreak.Views
}
}
private void ChromiumBrowserWrapper_BuildDecoded(object sender, Models.Builds.Build e)
private void ChromiumBrowserWrapper_BuildDecoded(object sender, Build e)
{
this.viewManager.ShowView<BuildTemplateView>(new BuildEntry { Build = e, Name = string.Empty });
}
private void LeftBrowser_FavoriteUriChanged(object sender, string e)
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveOptions.Value;
config.LeftBrowserDefault = e;
this.configurationManager.SaveConfiguration(config);
this.liveOptions.UpdateOption();
}
private void RightBrowser_FavoriteUriChanged(object sender, string e)
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveOptions.Value;
config.RightBrowserDefault = e;
this.configurationManager.SaveConfiguration(config);
this.liveOptions.UpdateOption();
}
private void LeftChromiumBrowserWrapper_MaximizeClicked(object sender, EventArgs e)
+10 -10
View File
@@ -1,13 +1,13 @@
using Daybreak.Controls.Templates;
using Daybreak.Configuration;
using Daybreak.Controls.Templates;
using Daybreak.Models;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Configuration;
using Daybreak.Services.Screens;
using Daybreak.Services.ViewManagement;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
@@ -23,7 +23,7 @@ namespace Daybreak.Views
{
private readonly IScreenManager screenManager;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly IApplicationLauncher applicationLauncher;
private int selectedId;
@@ -33,15 +33,15 @@ namespace Daybreak.Views
public ScreenChoiceView(
IViewManager viewManager,
IScreenManager screenManager,
IConfigurationManager configurationManager,
ILiveOptions<ApplicationConfiguration> liveOptions,
IApplicationLauncher applicationLauncher)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenManager = screenManager.ThrowIfNull(nameof(screenManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.applicationLauncher = applicationLauncher.ThrowIfNull(nameof(applicationLauncher));
this.InitializeComponent();
this.selectedId = configurationManager.GetConfiguration().DesiredGuildwarsScreen;
this.selectedId = this.liveOptions.Value.DesiredGuildwarsScreen;
this.CanTest = applicationLauncher.IsGuildwarsRunning;
this.SetupView();
}
@@ -59,8 +59,8 @@ namespace Daybreak.Views
VerticalAlignment = System.Windows.VerticalAlignment.Top,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
Foreground = screen.Id == this.selectedId ? Brushes.LightGreen : Brushes.White
};
screenTemplate.Clicked += ScreenTemplate_Clicked;
};
screenTemplate.Clicked += this.ScreenTemplate_Clicked;
this.ScreenContainer.Children.Add(screenTemplate);
}
}
@@ -86,7 +86,7 @@ namespace Daybreak.Views
private void SaveButton_Clicked(object sender, EventArgs e)
{
this.configurationManager.GetConfiguration().DesiredGuildwarsScreen = this.selectedId;
this.liveOptions.Value.DesiredGuildwarsScreen = this.selectedId;
this.viewManager.ShowView<SettingsCategoryView>();
}
+16 -3
View File
@@ -1,4 +1,7 @@
using Daybreak.Services.ViewManagement;
using Daybreak.Configuration;
using Daybreak.Services.ViewManagement;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Windows.Controls;
@@ -10,11 +13,14 @@ namespace Daybreak.Views
public partial class SettingsCategoryView : UserControl
{
private readonly IViewManager viewManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
public SettingsCategoryView(
ILiveOptions<ApplicationConfiguration> liveOptions,
IViewManager viewManager)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.liveOptions = liveOptions.ThrowIfNull();
this.viewManager = viewManager.ThrowIfNull();
InitializeComponent();
}
@@ -35,7 +41,14 @@ namespace Daybreak.Views
private void BuildsButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<BuildsListView>();
if (this.liveOptions.Value.ExperimentalFeatures.DownloadIcons)
{
this.viewManager.ShowView<IconDownloadView>();
}
else
{
this.viewManager.ShowView<BuildsListView>();
}
}
private void VersionButton_Clicked(object sender, System.EventArgs e)
-3
View File
@@ -94,7 +94,6 @@
<TextBlock Text="Desired screen id: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Place Shortcut: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Shortcut folder: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Keep local cache of icons: " FontSize="22" Foreground="White" Height="30"></TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1">
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
@@ -149,8 +148,6 @@
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ShortcutFolderPickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=KeepLocalIconCache, Mode=TwoWay}"></ToggleButton>
</StackPanel>
</Grid>
</ScrollViewer>
+8 -15
View File
@@ -1,11 +1,8 @@
using Daybreak.Services.Configuration;
using Daybreak.Services.Shortcuts;
using Daybreak.Configuration;
using Daybreak.Services.ViewManagement;
using Microsoft.Win32;
using System;
using System.Configuration;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Forms;
@@ -18,7 +15,7 @@ namespace Daybreak.Views
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class SettingsView : System.Windows.Controls.UserControl
{
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
private readonly IViewManager viewManager;
[GenerateDependencyProperty]
@@ -45,14 +42,12 @@ namespace Daybreak.Views
private bool shortcutPlaced;
[GenerateDependencyProperty]
private bool autoCheckUpdate;
[GenerateDependencyProperty]
private bool keepLocalIconCache;
public SettingsView(
IConfigurationManager configurationManager,
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions,
IViewManager viewManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(nameof(liveUpdateableOptions));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.LoadSettings();
@@ -60,7 +55,7 @@ namespace Daybreak.Views
private void LoadSettings()
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveUpdateableOptions.Value;
this.AddressBarReadonly = config.AddressBarReadonly;
this.ToolboxPath = config.ToolboxPath;
this.LeftBrowserUrl = config.LeftBrowserDefault;
@@ -73,12 +68,11 @@ namespace Daybreak.Views
this.ShortcutFolder = config.ShortcutLocation;
this.ShortcutPlaced = config.PlaceShortcut;
this.AutoCheckUpdate = config.AutoCheckUpdate;
this.KeepLocalIconCache = config.KeepLocalIconCache;
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
var currentConfig = this.configurationManager.GetConfiguration();
var currentConfig = this.liveUpdateableOptions.Value;
currentConfig.ToolboxPath = this.ToolboxPath;
currentConfig.AddressBarReadonly = this.AddressBarReadonly;
currentConfig.LeftBrowserDefault = this.LeftBrowserUrl;
@@ -91,8 +85,7 @@ namespace Daybreak.Views
currentConfig.ShortcutLocation = this.ShortcutFolder;
currentConfig.PlaceShortcut = this.ShortcutPlaced;
currentConfig.AutoCheckUpdate = this.AutoCheckUpdate;
currentConfig.KeepLocalIconCache = this.KeepLocalIconCache;
this.configurationManager.SaveConfiguration(currentConfig);
this.liveUpdateableOptions.UpdateOption();
this.viewManager.ShowView<SettingsCategoryView>();
}
+2 -2
View File
@@ -1,4 +1,4 @@
using Daybreak.Models;
using Daybreak.Models.Progress;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
@@ -51,7 +51,7 @@ namespace Daybreak.Views
this.ProgressValue = downloadUpdateStep.Progress * 100;
}
this.Description = this.updateStatus.CurrentStep.Name;
this.Description = this.updateStatus.CurrentStep.Description;
});
}
+2 -1
View File
@@ -4,6 +4,7 @@ using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Extensions;
using Version = Daybreak.Models.Versioning.Version;
@@ -39,7 +40,7 @@ namespace Daybreak.Views
private async void LoadVersionList()
{
this.Versions.ClearAnd().AddRange(await this.applicationUpdater.GetVersions());
this.Versions.ClearAnd().AddRange((await this.applicationUpdater.GetVersions()).Reverse());
}
private void CurrentVersion_Clicked(object sender, EventArgs e)
+2
View File
@@ -5,6 +5,8 @@ Param(
Write-Output "Deleting pdb file"
Remove-item .\Publish\Daybreak.pdb
Remove-item .\Publish\Daybreak.Installer.pdb
Move-Item -Path .\Publish\Daybreak.Installer.exe -Destination .\Publish\Daybreak.Installer.Temp.exe
$zipPath = "Publish\daybreakv$version.zip"
Write-Output "Compressing binaries to $zipPath"
Compress-Archive .\Publish\* $zipPath -Force
+22
View File
@@ -0,0 +1,22 @@
Param(
[Parameter(Mandatory=$true)]
[string]$currentVersion,
[Parameter(Mandatory=$true)]
[string]$lastVersion
)
if ($currentVersion.StartsWith("v")){
$currentVersion = $currentVersion.Substring(1)
}
if ($lastVersion.StartsWith("v")){
$lastVersion = $lastVersion.Substring(1)
}
$isNewer = $currentVersion.CompareTo($lastVersion) -eq 1
if ($isNewer -eq $false){
throw "Version is not incremented. Current version " + $currentVersion + ". Last version " + $lastVersion
}
else{
Write-Host "Version has been incremented"
}