Compare commits

...
17 Commits
Author SHA1 Message Date
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
amacocianandGitHub 2b025e225b Updated wpfextended dependency 2021-06-11 13:50:58 +02:00
amacocianandGitHub 15a67ea28c Minor fixes
Stop showing browser when browser is disabled
Prevent setting address of browser when browser is disabled (should prevent browser from instantiating when disabled)
Ignore errors from browser caused by switching view before the browser could be initialized
Provide expression to filter logs
Button to export logs to json file
2021-06-07 14:33:54 +02:00
amacocianandGitHub c6b62fd5fd Logging improvements
Log uncaught exceptions
Logview expander
2021-06-07 00:34:43 +02:00
88 changed files with 1659 additions and 917 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:
build:
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>
+41
View File
@@ -0,0 +1,41 @@
// 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("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>();
}
}
}
@@ -4,7 +4,8 @@ using LiteDB;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using WpfExtended.Logging;
using System.Linq;
using System.Logging;
namespace Daybreak.Tests.Services
{
@@ -37,6 +38,18 @@ namespace Daybreak.Tests.Services
logger.Should().NotBeNull();
}
[TestMethod]
public void LoggerLogsAndReaderReadsFiltered()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.LogTrace("Logging some trace");
logger.LogInformation("Logging some stuff");
logger.LogError("Logging some error");
this.logsManager.GetLogs(l => l.LogLevel < LogLevel.Information).Should().HaveCount(1);
var log = this.logsManager.GetLogs(l => l.LogLevel < LogLevel.Information).First();
log.LogLevel.Should().Be(LogLevel.Error);
}
[TestMethod]
public void LoggerLogsAndReaderReads()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
+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();
}
+30 -25
View File
@@ -1,5 +1,4 @@
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.Bloogum;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
@@ -8,7 +7,6 @@ using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
using Daybreak.Services.Screens;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Shortcuts;
@@ -20,9 +18,11 @@ 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;
namespace Daybreak.Configuration
{
@@ -32,47 +32,51 @@ namespace Daybreak.Configuration
{
serviceManager.ThrowIfNull(nameof(serviceManager));
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.RegisterSingleton<ApplicationLifetimeManager>();
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<IConfigurationManager, ConfigurationManager>();
serviceProducer.RegisterSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
serviceProducer.RegisterSingleton<IRuntimeStore, RuntimeStore>();
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 RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
applicationLifetimeProducer.ThrowIfNull(nameof(applicationLifetimeProducer));
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
applicationLifetimeProducer.RegisterService<IShortcutManager>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
viewProducer.ThrowIfNull(nameof(viewProducer));
@@ -91,6 +95,7 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<ScreenChoiceView>();
viewProducer.RegisterView<VersionManagementView>();
viewProducer.RegisterView<LogsView>();
viewProducer.RegisterView<IconDownloadView>();
}
}
}
+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;
@@ -22,17 +23,21 @@ namespace Daybreak.Controls
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class ChromiumBrowserWrapper : UserControl
{
public static readonly DependencyProperty AddressProperty =
DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
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)]
@@ -49,8 +54,17 @@ namespace Daybreak.Controls
private bool navigating;
[GenerateDependencyProperty]
private string favoriteAddress;
[GenerateDependencyProperty]
private string address;
public string Address
{
get => this.GetTypedValue<string>(AddressProperty);
set
{
if (this.BrowserSupported is true)
{
this.SetValue(AddressProperty, value);
}
}
}
public ChromiumBrowserWrapper()
{
@@ -67,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();
@@ -86,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;
@@ -94,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)
@@ -109,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) =>
{
@@ -184,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>>();
@@ -193,6 +213,11 @@ namespace Daybreak.Controls
return;
}
if (this.buildTemplateManager is null)
{
return;
}
if (this.buildTemplateManager.IsTemplate(maybeTemplate) is false)
{
return;
@@ -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>
@@ -1,13 +1,16 @@
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;
@@ -26,8 +29,10 @@ 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;
@@ -59,16 +64,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);
@@ -97,6 +104,7 @@ namespace Daybreak.Controls
{
this.loadedBuild.Build.Secondary = this.SecondaryProfession;
}
this.LoadSkills();
this.LoadAttributes();
if (this.suppressBuildChanged is false)
@@ -129,6 +137,11 @@ namespace Daybreak.Controls
}
}
private void BuildTemplate_Unloaded(object sender, RoutedEventArgs e)
{
this.cancellationTokenSource.Cancel();
}
private void InitializeProperties()
{
this.PrimaryProfession = Profession.None;
@@ -192,55 +205,62 @@ 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)
.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;
@@ -274,13 +294,19 @@ namespace Daybreak.Controls
private void ShowInfoBrowser()
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
}
}
private void HideInfoBrowser()
{
this.SkillBrowser.Width = 0;
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 0;
}
}
private void ShowSkillListView()
@@ -301,7 +327,7 @@ namespace Daybreak.Controls
return;
}
this.BrowseToInfo(PrimaryProfession.Name);
this.BrowseToInfo(this.PrimaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
@@ -344,6 +370,7 @@ namespace Daybreak.Controls
{
this.BrowseToInfo(skill.Name);
}
e.Handled = true;
}
@@ -362,14 +389,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)
@@ -0,0 +1,18 @@
<UserControl x:Class="Daybreak.Controls.LogMessageTemplate"
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="BoolToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch">
<TextBlock Text="{Binding ElementName=_this, Path=Message, Mode=OneWay}" Background="Transparent"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
MouseLeftButtonDown="TextBox_MouseLeftButtonDown" MaxHeight="18" TextWrapping="Wrap"></TextBlock>
</Grid>
</UserControl>
@@ -0,0 +1,36 @@
using System.Windows.Extensions;
using System.Windows.Controls;
using System.Windows.Input;
using System.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for LogTemplate.xaml
/// </summary>
public partial class LogMessageTemplate : UserControl
{
private bool expanded;
[GenerateDependencyProperty]
private string message;
public LogMessageTemplate()
{
this.InitializeComponent();
}
private void TextBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs eventArgs)
{
this.expanded = !this.expanded;
if (this.expanded)
{
sender.As<TextBlock>().MaxHeight = double.MaxValue;
}
else
{
sender.As<TextBlock>().MaxHeight = 18;
}
}
}
}
@@ -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;
});
}
}
}
+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</Version>
<Version>0.9.3.6</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.4.3" />
<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.4.1" />
<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>
+21 -10
View File
@@ -1,6 +1,5 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using Slim;
@@ -16,7 +15,7 @@ namespace Daybreak.Launch
{
public sealed class Launcher : ExtendedApplication<MainWindow>
{
public static IServiceManager ApplicationServiceManager { get; private set; }
private ILogger logger;
private readonly static Launcher launcher = new();
[STAThread]
@@ -27,14 +26,12 @@ namespace Daybreak.Launch
protected override void SetupServiceManager(IServiceManager serviceManager)
{
ApplicationServiceManager = this.ServiceManager;
ProjectConfiguration.RegisterResolvers(serviceManager);
}
protected override void RegisterServices(IServiceProducer serviceProducer)
{
ProjectConfiguration.RegisterServices(this.ServiceManager);
ServiceManager.BuildSingletons();
ProjectConfiguration.RegisterLifetimeServices(this.ServiceManager.GetService<IApplicationLifetimeManager>());
ProjectConfiguration.RegisterViews(this.ServiceManager.GetService<IViewManager>());
}
protected override bool HandleException(Exception e)
@@ -44,17 +41,21 @@ namespace Daybreak.Launch
return false;
}
this.ServiceManager.GetService<ILogger>().LogCritical(e, $"Unhandled exception");
if (this.logger is null)
{
return false;
}
if (e is FatalException fatalException)
{
this.ServiceManager.GetService<ILogger>().LogCritical(e, $"{nameof(FatalException)} encountered. Closing application.");
this.logger.LogCritical(e, $"{nameof(FatalException)} encountered. Closing application");
MessageBox.Show(fatalException.ToString());
File.WriteAllText("crash.log", e.ToString());
return false;
}
else if (e is TargetInvocationException targetInvocationException && e.InnerException is FatalException innerFatalException)
{
this.ServiceManager.GetService<ILogger>().LogCritical(e, $"{nameof(FatalException)} encountered. Closing application.");
this.logger.LogCritical(e, $"{nameof(FatalException)} encountered. Closing application");
MessageBox.Show(innerFatalException.ToString());
File.WriteAllText("crash.log", e.ToString());
return false;
@@ -66,23 +67,33 @@ namespace Daybreak.Launch
{
/*
* Ignore exception caused by browser failing to initialize due to missing window.
* Likely caused by switching windows before browser was initialized.
* Likely caused by switching views before browser was initialized.
*/
this.logger.LogError(e, "Failed to initialize browser");
return true;
}
}
else if (e.Message.Contains("Invalid window handle.") && e.StackTrace.Contains("CoreWebView2Environment.CreateCoreWebView2ControllerAsync"))
{
/*
* Ignore exception caused by browser failing to initialize due to missing window.
* Likely caused by switching views before the browser was initialized.
*/
this.logger.LogError(e, "Failed to initialize browser");
return true;
}
this.logger.LogError(e, $"Unhandled exception caught {e.GetType()}");
MessageBox.Show(e.ToString());
return true;
}
protected override void ApplicationStarting()
{
this.ServiceManager.GetService<IApplicationLifetimeManager>().OnStartup();
this.logger = this.ServiceManager.GetService<ILogger<Launcher>>();
this.RegisterViewContainer();
}
protected override void ApplicationClosing()
{
this.ServiceManager.GetService<IApplicationLifetimeManager>().OnClosing();
}
private void RegisterViewContainer()
+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="Stretch"
HorizontalAlignment="Stretch"
Visibility="Collapsed"
Grid.Row="1"></webview:WebView2>
</Grid>
</Window>
+23 -21
View File
@@ -1,13 +1,15 @@
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.Runtime;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
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;
@@ -32,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]
@@ -50,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)
@@ -110,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);
@@ -178,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; }
}
}
+15
View File
@@ -0,0 +1,15 @@
using Microsoft.Extensions.Logging;
using System;
namespace Daybreak.Models
{
public sealed class Log
{
public string Message { get; set; }
public string Category { get; set; }
public LogLevel LogLevel { get; set; }
public string CorrelationVector { get; set; }
public string EventId { get; set; }
public DateTime LogTime { 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,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Extensions;
using IServiceProvider = Slim.IServiceProvider;
namespace Daybreak.Services.ApplicationLifetime
{
public sealed class ApplicationLifetimeManager : IApplicationLifetimeManager
{
private readonly IServiceProvider serviceProvider;
private List<Type> RegisteredTypes { get; } = new List<Type>();
public ApplicationLifetimeManager(IServiceProvider serviceProvider)
{
serviceProvider.ThrowIfNull(nameof(serviceProvider));
this.serviceProvider = serviceProvider;
}
public void RegisterService<T>() where T : IApplicationLifetimeService
{
this.RegisteredTypes.Add(typeof(T));
}
public void OnStartup()
{
foreach (var serviceType in this.RegisteredTypes)
{
var service = this.serviceProvider.GetService(serviceType);
service.As<IApplicationLifetimeService>().OnStartup();
}
}
public void OnClosing()
{
foreach (var serviceType in this.RegisteredTypes)
{
var service = this.serviceProvider.GetService(serviceType);
service.As<IApplicationLifetimeService>().OnClosing();
}
}
}
}
@@ -1,8 +0,0 @@
namespace Daybreak.Services.ApplicationLifetime
{
public interface IApplicationLifetimeManager : IApplicationLifetimeProducer
{
void OnStartup();
void OnClosing();
}
}
@@ -1,7 +0,0 @@
namespace Daybreak.Services.ApplicationLifetime
{
public interface IApplicationLifetimeProducer
{
void RegisterService<T>() where T : IApplicationLifetimeService;
}
}
@@ -1,8 +0,0 @@
namespace Daybreak.Services.ApplicationLifetime
{
public interface IApplicationLifetimeService
{
void OnStartup();
void OnClosing();
}
}
+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,213 @@
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.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;
}
this.iconBrowser.InitializeWebView(this.browserWrapper, this.cancellationTokenSource.Token);
var progressIncrement = 100d / Skill.Skills.Count();
var incomplete = false;
var progressValue = 0d;
foreach (var skill in Skill.Skills.OrderBy(s => s.Name))
{
if (this.cancellationTokenSource?.IsCancellationRequested is null or true)
{
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Stopped(progressValue);
return;
}
if (skill == Skill.NoSkill)
{
progressValue += progressIncrement;
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)
{
logger.LogInformation("Icon exists");
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
{
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,6 +1,6 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using System;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.KeyboardHook
{
@@ -1,4 +1,4 @@
using Daybreak.Services.ApplicationLifetime;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.KeyboardMacros
{
@@ -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
{
}
}
+7 -4
View File
@@ -1,12 +1,15 @@
using System.Collections.Generic;
using WpfExtended.Logging;
using WpfExtended.Models;
using Daybreak.Models;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Logging;
namespace Daybreak.Services.Logging
{
public interface ILogsManager : ILogsWriter
{
IEnumerable<Log> GetLogs();
IEnumerable<Models.Log> GetLogs(Expression<Func<Models.Log, bool>> filter);
IEnumerable<Models.Log> GetLogs();
int DeleteLogs();
}
}
+21 -5
View File
@@ -1,7 +1,9 @@
using LiteDB;
using System;
using System.Collections.Generic;
using System.Extensions;
using WpfExtended.Models;
using System.Linq.Expressions;
using System.Logging;
namespace Daybreak.Services.Logging
{
@@ -14,17 +16,31 @@ namespace Daybreak.Services.Logging
this.liteDatabase = liteDatabase.ThrowIfNull(nameof(liteDatabase));
}
public IEnumerable<Log> GetLogs()
public IEnumerable<Models.Log> GetLogs(Expression<Func<Models.Log, bool>> filter)
{
return this.liteDatabase.GetCollection<Log>().FindAll();
return this.liteDatabase.GetCollection<Models.Log>().Find(filter);
}
public IEnumerable<Models.Log> GetLogs()
{
return this.liteDatabase.GetCollection<Models.Log>().FindAll();
}
public void WriteLog(Log log)
{
this.liteDatabase.GetCollection<Log>().Insert(log);
var dbLog = new Models.Log
{
EventId = log.EventId,
Message = log.Exception is null ? log.Message : $"{log.Message}{Environment.NewLine}{log.Exception}",
Category = log.Category,
LogLevel = log.LogLevel,
LogTime = log.LogTime,
CorrelationVector = log.CorrelationVector
};
this.liteDatabase.GetCollection<Models.Log>().Insert(dbLog);
}
public int DeleteLogs()
{
return this.liteDatabase.GetCollection<Log>().DeleteAll();
return this.liteDatabase.GetCollection<Models.Log>().DeleteAll();
}
}
}
@@ -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}");
}
}
}
@@ -1,9 +0,0 @@
namespace Daybreak.Services.Runtime
{
public interface IRuntimeStore
{
void StoreValue<T>(string name, T value);
bool TryGetValue<T>(string name, out T value);
T GetValue<T>(string name);
}
}
-36
View File
@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Extensions;
namespace Daybreak.Services.Runtime
{
public sealed class RuntimeStore : IRuntimeStore
{
private Dictionary<string, object> InnerStore { get; } = new Dictionary<string, object>();
public T GetValue<T>(string name)
{
if(this.InnerStore.TryGetValue(name, out var value))
{
return value.Cast<T>();
}
throw new InvalidOperationException($"Could not find any value stored with name {name}");
}
public void StoreValue<T>(string name, T value)
{
this.InnerStore[name] = value;
}
public bool TryGetValue<T>(string name, out T value)
{
if (this.InnerStore.TryGetValue(name, out var valueObj))
{
value = valueObj.Cast<T>();
return true;
}
value = default;
return false;
}
}
}
@@ -1,5 +1,5 @@
using Daybreak.Services.ApplicationLifetime;
using System.Extensions;
using System.Extensions;
using System.Windows.Extensions.Services;
using System.Windows.Media;
namespace Daybreak.Services.Screenshots
@@ -1,4 +1,4 @@
using Daybreak.Services.ApplicationLifetime;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.Shortcuts
{
+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);
}
+13 -168
View File
@@ -1,17 +1,16 @@
using Daybreak.Exceptions;
using Daybreak.Models;
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Models.Github;
using Daybreak.Services.Runtime;
using Daybreak.Models.Progress;
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.Diagnostics;
using System.Extensions;
using System.Http;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -25,53 +24,32 @@ namespace Daybreak.Services.Updater
{
public sealed class ApplicationUpdater : IApplicationUpdater
{
private const string LaunchActionName = "Launch_Daybreak";
private const string UpdateDesiredKey = "UpdateDesired";
private const string ExecutionPolicyKey = "ExecutionPolicy";
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 IRuntimeStore runtimeStore;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly IHttpClient<ApplicationUpdater> httpClient;
public Version CurrentVersion { get; }
public ApplicationUpdater(
ILogger<ApplicationUpdater> logger,
IRuntimeStore runtimeStore,
ILiveOptions<ApplicationConfiguration> liveOptions,
IViewManager viewManager,
IHttpClient<ApplicationUpdater> httpClient)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.runtimeStore = runtimeStore.ThrowIfNull(nameof(runtimeStore));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
this.httpClient.DefaultRequestHeaders.Add("user-agent", "Daybreak Client");
@@ -176,7 +154,7 @@ namespace Daybreak.Services.Updater
{
System.Extensions.TaskExtensions.RunPeriodicAsync(async () =>
{
if (this.runtimeStore.TryGetValue<bool>(UpdateDesiredKey, out var desiringUpdate) && desiringUpdate is false)
if (this.liveOptions.Value.AutoCheckUpdate is false)
{
this.updateCancellationTokenSource.Cancel();
return;
@@ -191,23 +169,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();
}
@@ -216,16 +178,6 @@ 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");
});
}
}
@@ -245,92 +197,19 @@ 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");
}
}
@@ -368,40 +247,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,8 +1,8 @@
using Daybreak.Models;
using Daybreak.Models.Progress;
using Daybreak.Models.Versioning;
using Daybreak.Services.ApplicationLifetime;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.Updater
{
+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);
+8 -25
View File
@@ -1,8 +1,9 @@
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
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;
@@ -14,56 +15,38 @@ namespace Daybreak.Views
/// </summary>
public partial class AskUpdateView : UserControl
{
private const string UpdateDesiredKey = "UpdateDesired";
private readonly ILogger<AskUpdateView> logger;
private readonly IViewManager viewManager;
private readonly IRuntimeStore runtimeStore;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
private readonly IPrivilegeManager privilegeManager;
private readonly IApplicationUpdater applicationUpdater;
public AskUpdateView(
ILogger<AskUpdateView> logger,
IViewManager viewManager,
IRuntimeStore runtimeStore,
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions,
IPrivilegeManager privilegeManager,
IApplicationUpdater applicationUpdater)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.runtimeStore = runtimeStore.ThrowIfNull(nameof(runtimeStore));
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");
this.runtimeStore.StoreValue(UpdateDesiredKey, false);
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");
this.runtimeStore.StoreValue(UpdateDesiredKey, true);
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)
+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;
}
}
}
+13 -1
View File
@@ -24,6 +24,12 @@
Clicked="BinButton_Clicked"></controls:BinButton>
<controls:RefreshGlyph Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="0, 5, 45, 5"
Clicked="RefreshGlyph_Clicked"></controls:RefreshGlyph>
<controls:BackButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="0, 5, 85, 5"
Clicked="ExportButton_Clicked">
<controls:BackButton.RenderTransform>
<RotateTransform Angle="270" CenterX="15" CenterY="15"></RotateTransform>
</controls:BackButton.RenderTransform>
</controls:BackButton>
<DataGrid IsReadOnly="True" Background="Transparent" Foreground="White" Grid.Row="1"
ItemsSource="{Binding ElementName=_this, Path=Logs, Mode=OneWay}" HorizontalScrollBarVisibility="Disabled"
AutoGenerateColumns="False" HeadersVisibility="Column" EnableColumnVirtualization="True"
@@ -54,7 +60,13 @@
<DataGridTextColumn IsReadOnly="True" Header="Category" Binding="{Binding Category}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="LogLevel" Binding="{Binding LogLevel}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="EventId" Binding="{Binding EventId}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="Message" Binding="{Binding Message}" ElementStyle="{StaticResource WrapText}" Width="*"/>
<DataGridTemplateColumn IsReadOnly="True" Header="Message" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<controls:LogMessageTemplate Message="{Binding Message}" Foreground="White"></controls:LogMessageTemplate>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
+34 -4
View File
@@ -1,10 +1,15 @@
using Daybreak.Services.Logging;
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Windows.Controls;
using WpfExtended.Models;
namespace Daybreak.Views
{
@@ -15,22 +20,47 @@ namespace Daybreak.Views
{
private readonly IViewManager viewManager;
private readonly ILogsManager logManager;
private readonly ILogger<LogsView> logger;
public ObservableCollection<Log> Logs { get; } = new ObservableCollection<Log>();
public LogsView(
IViewManager viewManager,
ILogsManager logManager)
ILogsManager logManager,
ILogger<LogsView> logger)
{
this.logManager = logManager.ThrowIfNull(nameof(logManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.logger = logger.ThrowIfNull(nameof(logger));
this.InitializeComponent();
this.UpdateLogs();
}
private void UpdateLogs()
{
this.Logs.ClearAnd().AddRange(this.logManager.GetLogs());
this.Logs.ClearAnd().AddRange(this.logManager.GetLogs(l => l.LogLevel < Microsoft.Extensions.Logging.LogLevel.Trace));
}
private async void ExportButton_Clicked(object sender, EventArgs e)
{
this.logger.LogInformation("Exporting logs");
var saveFileDialog = new SaveFileDialog
{
DefaultExt = "json",
Filter = "Json files (*.json)|*.json",
Title = "Export logs",
ValidateNames = true,
CreatePrompt = true
};
if (saveFileDialog.ShowDialog() is true)
{
var fileName = saveFileDialog.FileName;
this.logger.LogInformation($"Exporting to {fileName}");
await File.WriteAllTextAsync(fileName, this.logManager.GetLogs().ToList().Serialize());
}
else
{
this.logger.LogInformation("Exporting canceled");
}
}
private void BackButton_Clicked(object sender, EventArgs e)
{
+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)
+1
View File
@@ -5,6 +5,7 @@ Param(
Write-Output "Deleting pdb file"
Remove-item .\Publish\Daybreak.pdb
Remove-item .\Publish\Daybreak.Installer.pdb
$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"
}