Merge pull request #518 from Mbucari/master

Improve Audible login and Libation Upgrade
This commit is contained in:
rmcrackan 2023-02-28 09:51:08 -05:00 committed by GitHub
commit 740b73beb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 854 additions and 558 deletions

View File

@ -23,10 +23,10 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [Linux, MacOS] os: [ubuntu-latest, macos-latest]
arch: [x64, arm64] arch: [x64, arm64]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -45,62 +45,63 @@ jobs:
then then
version="${inputVersion}" version="${inputVersion}"
else else
version="$(grep -oP '(?<=<Version>).*(?=</Version)' ./Source/AppScaffolding/AppScaffolding.csproj)" version="$(grep -Eio -m 1 '<Version>.*</Version>' ./Source/AppScaffolding/AppScaffolding.csproj | sed -r 's/<\/?Version>//g')"
fi fi
echo "version=${version}" >> "${GITHUB_OUTPUT}" echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Unit test - name: Unit test
if: ${{ inputs.run_unit_tests }} if: ${{ inputs.run_unit_tests }}
working-directory: ./Source working-directory: ./Source
run: dotnet test run: dotnet test
- name: Publish - name: Publish
id: publish
working-directory: ./Source working-directory: ./Source
run: | run: |
os=${{ matrix.os }} os=${{ matrix.os }}
RUNTIME_IDENTIFIER="$(echo ${os,} | sed 's/macOS/osx/')-${{ matrix.arch }}" target_os="$(echo ${os/-latest/} | sed 's/ubuntu/linux/')"
display_os="$(echo ${target_os/macos/macOS} | sed 's/linux/Linux/')"
echo "display_os=${display_os}" >> $GITHUB_OUTPUT
RUNTIME_IDENTIFIER="$(echo ${target_os/macos/osx})-${{ matrix.arch }}"
echo "$RUNTIME_IDENTIFIER" echo "$RUNTIME_IDENTIFIER"
dotnet publish \ dotnet publish \
LibationAvalonia/LibationAvalonia.csproj \ LibationAvalonia/LibationAvalonia.csproj \
--runtime "$RUNTIME_IDENTIFIER" \ --runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \ --configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ --output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LibationAvalonia/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml -p:PublishProfile=LibationAvalonia/Properties/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \ dotnet publish \
LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj \ LoadByOS/${display_os}ConfigApp/${display_os}ConfigApp.csproj \
--runtime "$RUNTIME_IDENTIFIER" \ --runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \ --configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ --output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml -p:PublishProfile=LoadByOS/Properties/${display_os}ConfigApp/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \ dotnet publish \
LibationCli/LibationCli.csproj \ LibationCli/LibationCli.csproj \
--runtime "$RUNTIME_IDENTIFIER" \ --runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \ --configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ --output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LibationCli/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml -p:PublishProfile=LibationCli/Properties/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \ dotnet publish \
HangoverAvalonia/HangoverAvalonia.csproj \ HangoverAvalonia/HangoverAvalonia.csproj \
--runtime "$RUNTIME_IDENTIFIER" \ --runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \ --configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ --output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=HangoverAvalonia/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml -p:PublishProfile=HangoverAvalonia/Properties/PublishProfiles/${display_os}Profile.pubxml
- name: Build bundle - name: Build bundle
id: bundle id: bundle
working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} working-directory: ./Source/bin/Publish/${{ steps.publish.outputs.display_os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }}
run: | run: |
BUNDLE_DIR=$(pwd) BUNDLE_DIR=$(pwd)
echo "Bundle dir: ${BUNDLE_DIR}" echo "Bundle dir: ${BUNDLE_DIR}"
cd .. cd ..
SCRIPT=../../../Scripts/Bundle_${{ matrix.os }}.sh SCRIPT=../../../Scripts/Bundle_${{ steps.publish.outputs.display_os }}.sh
chmod +rx ${SCRIPT} chmod +rx ${SCRIPT}
${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ matrix.arch }}" ${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ matrix.arch }}"
artifact=$(ls ./bundle) artifact=$(ls ./bundle)
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}" echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"
- name: Publish bundle - name: Publish bundle
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: ${{ steps.bundle.outputs.artifact }} name: ${{ steps.bundle.outputs.artifact }}
path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }} path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }}
if-no-files-found: error if-no-files-found: error

21
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Source/bin/Avalonia/Debug/Libation.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
}
]
}

42
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"dependsOn": [
"build_libation",
"build_linuxconfigapp"
]
},
{
"label": "build_libation",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/Source/LibationAvalonia/LibationAvalonia.csproj"
],
"group": "build",
"presentation": {
//"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
{
"label": "build_linuxconfigapp",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj"
],
"group": "build",
"presentation": {
//"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}

View File

@ -13,14 +13,10 @@ This walkthrough should get you up and running with Libation on your Mac.
- Move the extracted Libation app bundle to your applications folder. - Move the extracted Libation app bundle to your applications folder.
- Open a terminal (Go > Utilities > Terminal) - Open a terminal (Go > Utilities > Terminal)
- Copy/paste/run the following command (you'll be prompted to enter your password) - Copy/paste/run the following command (you'll be prompted to enter your password)
- macOS x64
```Console ```Console
sudo spctl --master-disable && sudo spctl --add --label "Libation" /Applications/Libation.app && open /Applications/Libation.app && sudo spctl --master-enable sudo spctl --master-disable && sudo spctl --add --label "Libation" /Applications/Libation.app && open /Applications/Libation.app && sudo spctl --master-enable
``` ```
- macOS arm64
```Console
codesign --force --deep -s - /Applications/Libation.app && sudo spctl --master-disable && sudo spctl --add --label "Libation" /Applications/Libation.app && open /Applications/Libation.app && sudo spctl --master-enable
```
- Close the terminal and use Libation! - Close the terminal and use Libation!
## Running Hangover ## Running Hangover

View File

@ -88,38 +88,27 @@ cp $FOLDER_EXEC/Libation.desktop $FOLDER_DESKTOP/Libation.desktop
echo "Creating pre-install file..." echo "Creating pre-install file..."
echo "#!/bin/bash echo "#!/bin/bash
# Pre-install script, removes previous installation program files and sym links # Pre-install script, removes previous installation program files and sym links
echo \"Removing previously created symlinks...\" echo \"Removing previously created symlinks...\"
rm /usr/bin/libation rm /usr/bin/libation
rm /usr/bin/hangover rm /usr/bin/hangover
rm /usr/bin/libationcli rm /usr/bin/libationcli
echo \"Removing previously installed Libation files...\" echo \"Removing previously installed Libation files...\"
rm -r /usr/lib/libation rm -r /usr/lib/libation
# making sure it won't stop installation # making sure it won't stop installation
exit 0 exit 0
" >> $FOLDER_DEBIAN/preinst " >> $FOLDER_DEBIAN/preinst
echo "Creating post-install file..." echo "Creating post-install file..."
echo "#!/bin/bash echo "#!/bin/bash
gtk-update-icon-cache -f /usr/share/icons/hicolor/ gtk-update-icon-cache -f /usr/share/icons/hicolor/
ln -s /usr/lib/libation/Libation /usr/bin/libation ln -s /usr/lib/libation/Libation /usr/bin/libation
ln -s /usr/lib/libation/Hangover /usr/bin/hangover ln -s /usr/lib/libation/Hangover /usr/bin/hangover
ln -s /usr/lib/libation/LibationCli /usr/bin/libationcli ln -s /usr/lib/libation/LibationCli /usr/bin/libationcli
# Increase the maximum number of inotify instances # Increase the maximum number of inotify instances
if ! grep -q 'fs.inotify.max_user_instances=524288' /etc/sysctl.conf; then
if ! grep -q 'fs.inotify.max_user_instances=524288' /etc/sysctl.conf; then
echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p
fi fi
# workaround until this file is moved to the user's home directory # workaround until this file is moved to the user's home directory
touch /usr/lib/libation/appsettings.json touch /usr/lib/libation/appsettings.json
chmod 666 /usr/lib/libation/appsettings.json chmod 666 /usr/lib/libation/appsettings.json
@ -139,6 +128,11 @@ echo "Changing permissions for pre- and post-install files..."
chmod +x "$FOLDER_DEBIAN/preinst" chmod +x "$FOLDER_DEBIAN/preinst"
chmod +x "$FOLDER_DEBIAN/postinst" chmod +x "$FOLDER_DEBIAN/postinst"
if [ "$(uname -s)" == "Darwin" ]; then
echo "macOS detected, installing dpkg"
brew install dpkg
fi
DEB_FILE=Libation.${VERSION}-linux-chardonnay-${ARCH}.deb DEB_FILE=Libation.${VERSION}-linux-chardonnay-${ARCH}.deb
echo "Creating $DEB_FILE" echo "Creating $DEB_FILE"
dpkg-deb -Zxz --build $DEB_DIR ./$DEB_FILE dpkg-deb -Zxz --build $DEB_DIR ./$DEB_FILE
@ -149,4 +143,4 @@ mv $DEB_FILE ./bundle/$DEB_FILE
rm -r "$BIN_DIR" rm -r "$BIN_DIR"
echo "Done!" echo "Done!"

View File

@ -96,6 +96,9 @@ done
APP_FILE=Libation.${VERSION}-macOS-chardonnay-${ARCH}.tgz APP_FILE=Libation.${VERSION}-macOS-chardonnay-${ARCH}.tgz
echo "Signing executables in: $BUNDLE"
codesign --force --deep -s - $BUNDLE
echo "Creating app bundle: $APP_FILE" echo "Creating app bundle: $APP_FILE"
tar -czvf $APP_FILE $BUNDLE tar -czvf $APP_FILE $BUNDLE
@ -105,4 +108,4 @@ mv $APP_FILE ./bundle/$APP_FILE
rm -r $BUNDLE rm -r $BUNDLE
echo "Done!" echo "Done!"

View File

@ -3,7 +3,6 @@ using Dinah.Core.Net.Http;
using Dinah.Core.StepRunner; using Dinah.Core.StepRunner;
using FileManager; using FileManager;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -225,6 +224,7 @@ namespace AaxDecrypter
} }
finally finally
{ {
nfsp.NetworkFileStream.RequestHeaders["User-Agent"] = DownloadOptions.UserAgent;
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps; nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
} }

View File

@ -39,42 +39,6 @@ namespace AudibleUtilities
return new ApiExtended(api); return new ApiExtended(api);
} }
/// <summary>Get api from existing tokens else login with native api callbacks.</summary>
public static async Task<ApiExtended> CreateAsync(Account account, ILoginCallback loginCallback)
{
Serilog.Log.Logger.Information("{@DebugInfo}", new
{
LoginType = nameof(ILoginCallback),
Account = account?.MaskedLogEntry ?? "[null]",
LocaleName = account?.Locale?.Name
});
var api = await EzApiCreator.GetApiAsync(
loginCallback,
account.Locale,
AudibleApiStorage.AccountsSettingsFile,
account.GetIdentityTokensJsonPath());
return new ApiExtended(api);
}
/// <summary>Get api from existing tokens else login with external browser</summary>
public static async Task<ApiExtended> CreateAsync(Account account, ILoginExternal loginExternal)
{
Serilog.Log.Logger.Information("{@DebugInfo}", new
{
LoginType = nameof(ILoginExternal),
Account = account?.MaskedLogEntry ?? "[null]",
LocaleName = account?.Locale?.Name
});
var api = await EzApiCreator.GetApiAsync(
loginExternal,
account.Locale,
AudibleApiStorage.AccountsSettingsFile,
account.GetIdentityTokensJsonPath());
return new ApiExtended(api);
}
/// <summary>Get api from existing tokens. Assumes you have valid login tokens. Else exception</summary> /// <summary>Get api from existing tokens. Assumes you have valid login tokens. Else exception</summary>
public static async Task<ApiExtended> CreateAsync(Account account) public static async Task<ApiExtended> CreateAsync(Account account)
{ {

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AudibleApi" Version="8.0.0.1" /> <PackageReference Include="AudibleApi" Version="8.1.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using AudibleApi; using AudibleApi;
using AudibleApi.Authorization; using AudibleApi.Authorization;
using AudibleApi.Cryptography;
using Dinah.Core; using Dinah.Core;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -178,7 +179,7 @@ namespace AudibleUtilities
LocaleCode = account.Locale.CountryCode, LocaleCode = account.Locale.CountryCode,
RefreshToken = account.IdentityTokens.RefreshToken.Value, RefreshToken = account.IdentityTokens.RefreshToken.Value,
StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie, StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie,
WebsiteCookies = new(account.IdentityTokens.Cookies.ToKeyValuePair()), WebsiteCookies = new(account.IdentityTokens.Cookies),
}; };
} }

View File

@ -21,13 +21,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@ -16,13 +16,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<!-- <!--

View File

@ -11,11 +11,13 @@ using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using ApplicationServices; using ApplicationServices;
using Avalonia.Controls;
namespace LibationAvalonia namespace LibationAvalonia
{ {
public class App : Application public class App : Application
{ {
public static Window MainWindow { get;private set; }
public static IBrush ProcessQueueBookFailedBrush { get; private set; } public static IBrush ProcessQueueBookFailedBrush { get; private set; }
public static IBrush ProcessQueueBookCompletedBrush { get; private set; } public static IBrush ProcessQueueBookCompletedBrush { get; private set; }
public static IBrush ProcessQueueBookCancelledBrush { get; private set; } public static IBrush ProcessQueueBookCancelledBrush { get; private set; }
@ -213,7 +215,7 @@ namespace LibationAvalonia
private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop) private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
{ {
var mainWindow = new MainWindow(); var mainWindow = new MainWindow();
desktop.MainWindow = mainWindow; desktop.MainWindow = MainWindow = mainWindow;
mainWindow.RestoreSizeAndLocation(Configuration.Instance); mainWindow.RestoreSizeAndLocation(Configuration.Instance);
mainWindow.OnLoad(); mainWindow.OnLoad();
mainWindow.OnLibraryLoaded(LibraryTask.GetAwaiter().GetResult()); mainWindow.OnLibraryLoaded(LibraryTask.GetAwaiter().GetResult());

View File

@ -1,8 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using LibationAvalonia.Dialogs;
using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LibationAvalonia namespace LibationAvalonia
@ -18,6 +16,9 @@ namespace LibationAvalonia
return defaultBrush; return defaultBrush;
} }
public static Task<DialogResult> ShowDialogAsync(this DialogWindow dialogWindow, Window owner = null)
=> dialogWindow.ShowDialog<DialogResult>(owner ?? App.MainWindow);
public static Window GetParentWindow(this IControl control) => control.VisualRoot as Window; public static Window GetParentWindow(this IControl control) => control.VisualRoot as Window;
} }
} }

View File

@ -36,6 +36,7 @@
Width="60" Width="60"
Height="30" Height="30"
Content="X" Content="X"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}" IsEnabled="{Binding !IsDefault}"
Click="DeleteButton_Clicked" /> Click="DeleteButton_Clicked" />

View File

@ -1,22 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using System;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs.Login
{
public abstract class AvaloniaLoginBase
{
/// <returns>True if ShowDialog's DialogResult == OK</returns>
protected static async Task<bool> ShowDialog(DialogWindow dialog)
{
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return false;
var result = await dialog.ShowDialog<DialogResult>(desktop.MainWindow);
Serilog.Log.Logger.Debug("{@DebugInfo}", new { DialogResult = result });
return result == DialogResult.OK;
}
}
}

View File

@ -5,36 +5,38 @@ using AudibleUtilities;
namespace LibationAvalonia.Dialogs.Login namespace LibationAvalonia.Dialogs.Login
{ {
public class AvaloniaLoginCallback : AvaloniaLoginBase, ILoginCallback public class AvaloniaLoginCallback : ILoginCallback
{ {
private Account _account { get; } private Account _account { get; }
public string DeviceName { get; } = "Libation";
public AvaloniaLoginCallback(Account account) public AvaloniaLoginCallback(Account account)
{ {
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
} }
public async Task<string> Get2faCodeAsync() public async Task<string> Get2faCodeAsync(string prompt)
{ {
var dialog = new _2faCodeDialog(); var dialog = new _2faCodeDialog(prompt);
if (await ShowDialog(dialog)) if (await dialog.ShowDialogAsync() is DialogResult.OK)
return dialog.Code; return dialog.Code;
return null; return null;
} }
public async Task<string> GetCaptchaAnswerAsync(byte[] captchaImage) public async Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
{ {
var dialog = new CaptchaDialog(captchaImage); var dialog = new CaptchaDialog(password, captchaImage);
if (await ShowDialog(dialog)) if (await dialog.ShowDialogAsync() is DialogResult.OK)
return dialog.Answer; return (dialog.Password, dialog.Answer);
return null; return (null, null);
} }
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig) public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
{ {
var dialog = new MfaDialog(mfaConfig); var dialog = new MfaDialog(mfaConfig);
if (await ShowDialog(dialog)) if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.SelectedName, dialog.SelectedValue); return (dialog.SelectedName, dialog.SelectedValue);
return (null, null); return (null, null);
} }
@ -42,7 +44,7 @@ namespace LibationAvalonia.Dialogs.Login
public async Task<(string email, string password)> GetLoginAsync() public async Task<(string email, string password)> GetLoginAsync()
{ {
var dialog = new LoginCallbackDialog(_account); var dialog = new LoginCallbackDialog(_account);
if (await ShowDialog(dialog)) if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (_account.AccountId, dialog.Password); return (_account.AccountId, dialog.Password);
return (null, null); return (null, null);
} }
@ -50,7 +52,7 @@ namespace LibationAvalonia.Dialogs.Login
public async Task ShowApprovalNeededAsync() public async Task ShowApprovalNeededAsync()
{ {
var dialog = new ApprovalNeededDialog(); var dialog = new ApprovalNeededDialog();
await ShowDialog(dialog); await dialog.ShowDialogAsync();
} }
} }
} }

View File

@ -5,14 +5,15 @@ using AudibleUtilities;
namespace LibationAvalonia.Dialogs.Login namespace LibationAvalonia.Dialogs.Login
{ {
public class AvaloniaLoginChoiceEager : AvaloniaLoginBase, ILoginChoiceEager public class AvaloniaLoginChoiceEager : ILoginChoiceEager
{ {
/// <summary>Convenience method. Recommended when wiring up Winforms to <see cref="ApplicationServices.LibraryCommands.ImportAccountAsync"/></summary> /// <summary>Convenience method. Recommended when wiring up Winforms to <see cref="ApplicationServices.LibraryCommands.ImportAccountAsync"/></summary>
public static async Task<ApiExtended> ApiExtendedFunc(Account account) => await ApiExtended.CreateAsync(account, new AvaloniaLoginChoiceEager(account)); public static async Task<ApiExtended> ApiExtendedFunc(Account account)
=> await ApiExtended.CreateAsync(account, new AvaloniaLoginChoiceEager(account));
public ILoginCallback LoginCallback { get; private set; } public ILoginCallback LoginCallback { get; }
private Account _account { get; } private readonly Account _account;
public AvaloniaLoginChoiceEager(Account account) public AvaloniaLoginChoiceEager(Account account)
{ {
@ -24,10 +25,9 @@ namespace LibationAvalonia.Dialogs.Login
{ {
var dialog = new LoginChoiceEagerDialog(_account); var dialog = new LoginChoiceEagerDialog(_account);
if (!await ShowDialog(dialog)) if (await dialog.ShowDialogAsync() is not DialogResult.OK)
return null; return null;
switch (dialog.LoginMethod) switch (dialog.LoginMethod)
{ {
case LoginMethod.Api: case LoginMethod.Api:
@ -35,7 +35,7 @@ namespace LibationAvalonia.Dialogs.Login
case LoginMethod.External: case LoginMethod.External:
{ {
var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl); var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
return await ShowDialog(externalDialog) return await externalDialog.ShowDialogAsync() is DialogResult.OK
? ChoiceOut.External(externalDialog.ResponseUrl) ? ChoiceOut.External(externalDialog.ResponseUrl)
: null; : null;
} }

View File

@ -2,22 +2,22 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="180" mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="250"
MinWidth="220" MinHeight="180" MinWidth="220" MinHeight="250"
MaxWidth="220" MaxHeight="180" MaxWidth="220" MaxHeight="250"
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog" x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
Title="CAPTCHA" Title="CAPTCHA"
Icon="/Assets/libation.ico"> Icon="/Assets/libation.ico">
<Grid <Grid
RowDefinitions="Auto,Auto,*" RowDefinitions="Auto,Auto,Auto,Auto,*"
ColumnDefinitions="Auto,*"> ColumnDefinitions="Auto,*"
Margin="10">
<Panel <Panel
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Margin="10"
MinWidth="200" MinWidth="200"
MinHeight="70" MinHeight="70"
Background="LightGray"> Background="LightGray">
@ -30,23 +30,40 @@
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
Margin="0,10,0,0"
VerticalAlignment="Center"
Text="Password:" />
<TextBox
Name="passwordBox"
Grid.Row="2"
Grid.Column="0" Grid.Column="0"
Margin="10,0,10,0" Grid.ColumnSpan="2"
Margin="0,10,0,0"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="0,10,10,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="CAPTCHA&#xa;answer:" /> Text="CAPTCHA&#xa;answer:" />
<TextBox <TextBox
Grid.Row="1" Name="captchaBox"
Grid.Row="3"
Grid.Column="1" Grid.Column="1"
Margin="10,0,10,0" Text="{Binding Answer}" /> Margin="0,10,0,0"
Text="{Binding Answer, Mode=TwoWay}" />
<Button <Button
Grid.Row="2" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Margin="10"
Padding="0,5,0,5" Padding="0,5,0,5"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="Submit" Content="Submit"
Click="Submit_Click" /> Click="Submit_Click" />

View File

@ -1,5 +1,8 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using LibationAvalonia.ViewModels;
using ReactiveUI;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,18 +10,43 @@ namespace LibationAvalonia.Dialogs.Login
{ {
public partial class CaptchaDialog : DialogWindow public partial class CaptchaDialog : DialogWindow
{ {
public string Answer { get; set; } public string Password => _viewModel.Password;
public Bitmap CaptchaImage { get; } public string Answer => _viewModel.Answer;
private readonly CaptchaDialogViewModel _viewModel;
public CaptchaDialog() public CaptchaDialog()
{ {
InitializeComponent(); InitializeComponent();
passwordBox = this.FindControl<TextBox>(nameof(passwordBox));
captchaBox = this.FindControl<TextBox>(nameof(captchaBox));
} }
public CaptchaDialog(byte[] captchaImage) :this() public CaptchaDialog(string password, byte[] captchaImage) :this()
{ {
using var ms = new MemoryStream(captchaImage); //Avalonia doesn't support animated gifs.
CaptchaImage = new Bitmap(ms); //Deconstruct gifs into frames and manually switch them.
DataContext = this; using var gif = SixLabors.ImageSharp.Image.Load(captchaImage);
var gifEncoder = new SixLabors.ImageSharp.Formats.Gif.GifEncoder();
var gifFrames = new Bitmap[gif.Frames.Count];
var frameDelayMs = new int[gif.Frames.Count];
for (int i = 0; i < gif.Frames.Count; i++)
{
var frameMetadata = gif.Frames[i].Metadata.GetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance);
using var clonedFrame = gif.Frames.CloneFrame(i);
using var framems = new MemoryStream();
clonedFrame.Save(framems, gifEncoder);
framems.Position = 0;
gifFrames[i] = new Bitmap(framems);
frameDelayMs[i] = frameMetadata.FrameDelay * 10;
}
DataContext = _viewModel = new(password, gifFrames, frameDelayMs);
Opened += (_, _) => (string.IsNullOrEmpty(password) ? passwordBox : captchaBox).Focus();
} }
private void InitializeComponent() private void InitializeComponent()
@ -26,15 +54,73 @@ namespace LibationAvalonia.Dialogs.Login
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
protected override async Task SaveAndCloseAsync()
protected override Task SaveAndCloseAsync()
{ {
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer }); if (string.IsNullOrWhiteSpace(_viewModel.Password))
{
await MessageBox.Show(this, "Please re-enter your password");
return;
}
return base.SaveAndCloseAsync(); Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { _viewModel.Answer });
await _viewModel.StopAsync();
await base.SaveAndCloseAsync();
}
protected override async Task CancelAndCloseAsync()
{
await _viewModel.StopAsync();
await base.CancelAndCloseAsync();
} }
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync(); => await SaveAndCloseAsync();
} }
public class CaptchaDialogViewModel : ViewModelBase
{
public string Answer { get; set; }
public string Password { get; set; }
public Bitmap CaptchaImage { get => _captchaImage; private set => this.RaiseAndSetIfChanged(ref _captchaImage, value); }
private Bitmap _captchaImage;
private bool keepSwitching = true;
private readonly Task FrameSwitch;
public CaptchaDialogViewModel(string password, Bitmap[] gifFrames, int[] frameDelayMs)
{
Password = password;
if (gifFrames.Length == 1)
{
FrameSwitch = Task.CompletedTask;
CaptchaImage = gifFrames[0];
}
else
{
FrameSwitch = SwitchFramesAsync(gifFrames, frameDelayMs);
}
}
public async Task StopAsync()
{
keepSwitching = false;
await FrameSwitch;
}
private async Task SwitchFramesAsync(Bitmap[] gifFrames, int[] frameDelayMs)
{
int index = 0;
while(keepSwitching)
{
CaptchaImage = gifFrames[index];
await Task.Delay(frameDelayMs[index++]);
index %= gifFrames.Length;
}
foreach (var frame in gifFrames)
frame.Dispose();
}
}
} }

View File

@ -1,4 +1,3 @@
using AudibleApi;
using AudibleUtilities; using AudibleUtilities;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -49,7 +48,7 @@ namespace LibationAvalonia.Dialogs.Login
protected override async Task SaveAndCloseAsync() protected override async Task SaveAndCloseAsync()
{ {
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl }); Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl });
if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out var result)) if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out _))
{ {
await MessageBox.Show("Invalid response URL"); await MessageBox.Show("Invalid response URL");
return; return;

View File

@ -2,9 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="160" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
MinWidth="400" MinHeight="160" MinWidth="400" MinHeight="200"
MaxWidth="400" MaxHeight="160" MaxWidth="400" MaxHeight="200"
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog" x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
Title="Two-Step Verification" Title="Two-Step Verification"
Icon="/Assets/libation.ico"> Icon="/Assets/libation.ico">

View File

@ -2,30 +2,41 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="140" d:DesignHeight="100" mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200"
MinWidth="140" MinHeight="100" MinWidth="200" MinHeight="200"
MaxWidth="140" MaxHeight="100" MaxWidth="200" MaxHeight="200"
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog" x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
Title="2FA Code" Title="2FA Code"
Icon="/Assets/libation.ico"> Icon="/Assets/libation.ico">
<Grid RowDefinitions="Auto,Auto,*"> <Grid
VerticalAlignment="Stretch"
ColumnDefinitions="*" Margin="5"
RowDefinitions="*,Auto,Auto,Auto">
<TextBlock
TextAlignment="Center"
TextWrapping="Wrap"
Text="{Binding Prompt}" />
<TextBlock <TextBlock
Margin="5" Margin="5"
Grid.Row="1"
TextAlignment="Center" TextAlignment="Center"
Text="Enter 2FA Code" /> Text="Enter 2FA Code" />
<TextBox <TextBox
Name="_2FABox"
Margin="5,0,5,0" Margin="5,0,5,0"
Grid.Row="1" Grid.Row="2"
HorizontalContentAlignment="Center"
Text="{Binding Code, Mode=TwoWay}" /> Text="{Binding Code, Mode=TwoWay}" />
<Button <Button
Margin="5" Margin="5"
Grid.Row="2" Grid.Row="3"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="Submit" Content="Submit"
Click="Submit_Click" /> Click="Submit_Click" />
</Grid> </Grid>

View File

@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,16 +7,20 @@ namespace LibationAvalonia.Dialogs.Login
public partial class _2faCodeDialog : DialogWindow public partial class _2faCodeDialog : DialogWindow
{ {
public string Code { get; set; } public string Code { get; set; }
public string Prompt { get; } = "For added security, please enter the One Time Password (OTP) generated by your Authenticator App";
public _2faCodeDialog() public _2faCodeDialog()
{ {
InitializeComponent(); AvaloniaXamlLoader.Load(this);
DataContext = this; _2FABox = this.FindControl<TextBox>(nameof(_2FABox));
} }
private void InitializeComponent() public _2faCodeDialog(string prompt) : this()
{ {
AvaloniaXamlLoader.Load(this); Prompt = prompt;
DataContext = this;
Opened += (_, _) => _2FABox.Focus();
} }
protected override Task SaveAndCloseAsync() protected override Task SaveAndCloseAsync()

View File

@ -4,8 +4,6 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
<TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>libation.ico</ApplicationIcon> <ApplicationIcon>libation.ico</ApplicationIcon>
<AssemblyName>Libation</AssemblyName> <AssemblyName>Libation</AssemblyName>
@ -17,13 +15,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -109,7 +101,7 @@
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview4 " /> <PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview4 " />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" /> <PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -25,6 +25,9 @@ namespace LibationAvalonia.ViewModels
public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel(); public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel();
public ProductsDisplayViewModel ProductsDisplay { get; } = new ProductsDisplayViewModel(); public ProductsDisplayViewModel ProductsDisplay { get; } = new ProductsDisplayViewModel();
private double? _downloadProgress = null;
public double? DownloadProgress { get => _downloadProgress; set => this.RaiseAndSetIfChanged(ref _downloadProgress, value); }
/// <summary> Library filterting query </summary> /// <summary> Library filterting query </summary>
public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); } public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); }

View File

@ -9,7 +9,6 @@ using System.Linq;
namespace LibationAvalonia.Views namespace LibationAvalonia.Views
{ {
//DONE
public partial class MainWindow public partial class MainWindow
{ {
private InterruptableTimer autoScanTimer; private InterruptableTimer autoScanTimer;
@ -17,11 +16,7 @@ namespace LibationAvalonia.Views
private void Configure_ScanAuto() private void Configure_ScanAuto()
{ {
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok // creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
var hours = 0; autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
var minutes = 5;
var seconds = 0;
var _5_minutes = new TimeSpan(hours, minutes, seconds);
autoScanTimer = new InterruptableTimer(_5_minutes);
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI // subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
autoScanTimer.Elapsed += async (_, __) => autoScanTimer.Elapsed += async (_, __) =>
@ -44,9 +39,9 @@ namespace LibationAvalonia.Views
}; };
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan; _viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
// if enabled: begin on load // if enabled: begin on load
Load += startAutoScan; Opened += startAutoScan;
// if new 'default' account is added, run autoscan // if new 'default' account is added, run autoscan
AccountsSettingsPersister.Saving += accountsPreSave; AccountsSettingsPersister.Saving += accountsPreSave;

View File

@ -1,87 +0,0 @@
using AppScaffolding;
using LibationAvalonia.Dialogs;
using LibationFileManager;
using System;
using System.IO;
using System.Threading.Tasks;
namespace LibationAvalonia.Views
{
public partial class MainWindow
{
private void Configure_Update()
{
Opened += async (_, _) => await checkForUpdates();
}
private async Task checkForUpdates()
{
async Task<string> downloadUpdate(UpgradeProperties upgradeProperties)
{
if (upgradeProperties.ZipUrl is null)
{
Serilog.Log.Logger.Warning("Download link for new version not found");
return null;
}
//Silently download the update in the background, save it to a temp file.
var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl));
Serilog.Log.Logger.Information($"Downloading {zipFile}");
try
{
System.Net.Http.HttpClient cli = new();
using var fs = File.OpenWrite(zipFile);
using var dlStream = await cli.GetStreamAsync(new Uri(upgradeProperties.ZipUrl));
await dlStream.CopyToAsync(fs);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Failed to download the update: {pdate}", upgradeProperties.ZipUrl);
return null;
}
return zipFile;
}
try
{
var upgradeProperties = await Task.Run(LibationScaffolding.GetLatestRelease);
if (upgradeProperties is null) return;
const string ignoreUpdate = "IgnoreUpdate";
var config = Configuration.Instance;
if (config.GetString(propertyName: ignoreUpdate) == upgradeProperties.LatestRelease.ToString())
return;
var interop = InteropFactory.Create();
if (!interop.CanUpdate)
Serilog.Log.Logger.Information("Can't perform update automatically");
var notificationResult = await new UpgradeNotificationDialog(upgradeProperties, interop.CanUpdate).ShowDialog<DialogResult>(this);
if (notificationResult == DialogResult.Ignore)
config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpdate);
if (notificationResult != DialogResult.OK) return;
//Download the update file in the background,
string updateBundle = await downloadUpdate(upgradeProperties);
if (string.IsNullOrEmpty(updateBundle) || !File.Exists(updateBundle)) return;
//Install the update
Serilog.Log.Logger.Information($"Begin running auto-updater");
interop.InstallUpdate(updateBundle);
Serilog.Log.Logger.Information($"Completed running auto-updater");
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occured while checking for app updates.");
}
}
}
}

View File

@ -0,0 +1,34 @@
using Avalonia.Threading;
using LibationAvalonia.Dialogs;
using LibationUiBase;
using System.Threading.Tasks;
namespace LibationAvalonia.Views
{
public partial class MainWindow
{
private void Configure_Upgrade()
{
setProgressVisible(false);
#if !DEBUG
async Task upgradeAvailable(UpgradeEventArgs e)
{
var notificationResult = await new UpgradeNotificationDialog(e.UpgradeProperties, e.CapUpgrade).ShowDialogAsync(this);
e.Ignore = notificationResult == DialogResult.Ignore;
e.InstallUpgrade = notificationResult == DialogResult.OK;
}
var upgrader = new Upgrader();
upgrader.DownloadProgress += async (_, e) => await Dispatcher.UIThread.InvokeAsync(() => _viewModel.DownloadProgress = e.ProgressPercentage);
upgrader.DownloadBegin += async (_, _) => await Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(true));
upgrader.DownloadCompleted += async (_, _) => await Dispatcher.UIThread.InvokeAsync(() => setProgressVisible(false));
Opened += async (_, _) => await upgrader.CheckForUpgradeAsync(upgradeAvailable);
#endif
}
private void setProgressVisible(bool visible) => _viewModel.DownloadProgress = visible ? 0 : null;
}
}

View File

@ -194,9 +194,16 @@
</Border> </Border>
<!-- Bottom Status Strip --> <!-- Bottom Status Strip -->
<Grid Grid.Row="3" Margin="0,10,0,0" VerticalAlignment="Bottom" ColumnDefinitions="*,Auto"> <Grid Grid.Row="3" Margin="0,10,0,0" VerticalAlignment="Bottom" ColumnDefinitions="Auto,Auto,*,Auto">
<TextBlock FontSize="14" Grid.Column="0" Text="{Binding VisibleCountText}" VerticalAlignment="Center" /> <Grid.Styles>
<TextBlock FontSize="14" Grid.Column="1" Text="{Binding StatusCountText}" VerticalAlignment="Center" HorizontalAlignment="Right" /> <Style Selector="ProgressBar:horizontal">
<Setter Property="MinWidth" Value="100" />
</Style>
</Grid.Styles>
<TextBlock FontSize="14" Grid.Column="0" Text="Upgrading:" VerticalAlignment="Center" IsVisible="{Binding DownloadProgress, Converter={x:Static ObjectConverters.IsNotNull}}" />
<ProgressBar Grid.Column="1" Margin="5,0,10,0" VerticalAlignment="Stretch" Width="100" Value="{Binding DownloadProgress}" IsVisible="{Binding DownloadProgress, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<TextBlock FontSize="14" Grid.Column="2" Text="{Binding VisibleCountText}" VerticalAlignment="Center" />
<TextBlock FontSize="14" Grid.Column="3" Text="{Binding StatusCountText}" VerticalAlignment="Center" />
</Grid> </Grid>
</Grid> </Grid>
</Border> </Border>

View File

@ -40,9 +40,7 @@ namespace LibationAvalonia.Views
Configure_Export(); Configure_Export();
Configure_Settings(); Configure_Settings();
Configure_ProcessQueue(); Configure_ProcessQueue();
#if !DEBUG Configure_Upgrade();
Configure_Update();
#endif
Configure_Filter(); Configure_Filter();
// misc which belongs in winforms app but doesn't have a UI element // misc which belongs in winforms app but doesn't have a UI element
Configure_NonUI(); Configure_NonUI();

View File

@ -11,13 +11,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<!-- <!--

View File

@ -8,7 +8,7 @@ namespace LibationFileManager
void SetFolderIcon(string image, string directory); void SetFolderIcon(string image, string directory);
void DeleteFolderIcon(string directory); void DeleteFolderIcon(string directory);
Process RunAsRoot(string exe, string args); Process RunAsRoot(string exe, string args);
void InstallUpdate(string updateBundle); void InstallUpgrade(string upgradeBundle);
bool CanUpdate { get; } bool CanUpgrade { get; }
} }
} }

View File

@ -11,8 +11,8 @@ namespace LibationFileManager
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException(); public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException(); public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
public bool CanUpdate => throw new PlatformNotSupportedException(); public bool CanUpgrade => throw new PlatformNotSupportedException();
public Process RunAsRoot(string exe, string args) => throw new PlatformNotSupportedException(); public Process RunAsRoot(string exe, string args) => throw new PlatformNotSupportedException();
public void InstallUpdate(string updateBundle) => throw new PlatformNotSupportedException(); public void InstallUpgrade(string updateBundle) => throw new PlatformNotSupportedException();
} }
} }

View File

@ -8,6 +8,10 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" /> <ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
<ProjectReference Include="..\AppScaffolding\AppScaffolding.csproj" /> <ProjectReference Include="..\AppScaffolding\AppScaffolding.csproj" />

View File

@ -0,0 +1,148 @@
using AppScaffolding;
using Dinah.Core.Net.Http;
using LibationFileManager;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace LibationUiBase
{
public class UpgradeEventArgs
{
public UpgradeProperties UpgradeProperties { get; internal init; }
public bool CapUpgrade { get; internal init; }
private bool _ignore = false;
private bool _installUpgrade = true;
public bool Ignore
{
get => _ignore;
set
{
_ignore = value;
_installUpgrade &= !Ignore;
}
}
public bool InstallUpgrade
{
get => _installUpgrade;
set
{
_installUpgrade = value;
_ignore &= !InstallUpgrade;
}
}
}
public class Upgrader
{
public event EventHandler DownloadBegin;
public event EventHandler<DownloadProgress> DownloadProgress;
public event EventHandler<bool> DownloadCompleted;
public async Task CheckForUpgradeAsync(Func<UpgradeEventArgs,Task> upgradeAvailableHandler)
{
try
{
var upgradeProperties = await Task.Run(LibationScaffolding.GetLatestRelease);
if (upgradeProperties is null) return;
const string ignoreUpgrade = "IgnoreUpgrade";
var config = Configuration.Instance;
if (config.GetString(propertyName: ignoreUpgrade) == upgradeProperties.LatestRelease.ToString())
return;
var interop = InteropFactory.Create();
if (!interop.CanUpgrade)
Serilog.Log.Logger.Information("Can't perform upgrade automatically");
var upgradeEventArgs = new UpgradeEventArgs
{
UpgradeProperties = upgradeProperties,
CapUpgrade = interop.CanUpgrade
};
await upgradeAvailableHandler(upgradeEventArgs);
if (upgradeEventArgs.Ignore)
config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpgrade);
if (!upgradeEventArgs.InstallUpgrade) return;
//Download the upgrade file in the background,
DownloadBegin?.Invoke(this, EventArgs.Empty);
string upgradeBundle = await DownloadUpgradeAsync(upgradeProperties);
if (string.IsNullOrEmpty(upgradeBundle) || !File.Exists(upgradeBundle))
{
DownloadCompleted?.Invoke(this, false);
}
else
{
DownloadCompleted?.Invoke(this, true);
//Install the upgrade
Serilog.Log.Logger.Information($"Begin running auto-upgrader");
interop.InstallUpgrade(upgradeBundle);
Serilog.Log.Logger.Information($"Completed running auto-upgrader");
}
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occured while checking for app upgrades.");
}
}
private async Task<string> DownloadUpgradeAsync(UpgradeProperties upgradeProperties)
{
if (upgradeProperties.ZipUrl is null)
{
Serilog.Log.Logger.Warning("Download link for new version not found");
return null;
}
//Silently download the upgrade in the background, save it to a temp file.
var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl));
Serilog.Log.Logger.Information($"Downloading {zipFile}");
try
{
using var dlClient = new HttpClient();
using var response = await dlClient.GetAsync(upgradeProperties.ZipUrl, HttpCompletionOption.ResponseHeadersRead);
using var dlStream = await response.Content.ReadAsStreamAsync();
using var tempFile = File.OpenWrite(zipFile);
int read;
long totalRead = 0;
Memory<byte> buffer = new byte[128 * 1024];
long contentLength = response.Content.Headers.ContentLength ?? 0;
while ((read = await dlStream.ReadAsync(buffer)) > 0)
{
await tempFile.WriteAsync(buffer[..read]);
totalRead += read;
DownloadProgress?.Invoke(
this,
new DownloadProgress
{
BytesReceived = totalRead,
TotalBytesToReceive = contentLength,
ProgressPercentage = contentLength > 0 ? 100d * totalRead / contentLength : 0
});
}
return zipFile;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Failed to download the upgrade: {bundle}", upgradeProperties.ZipUrl);
return null;
}
}
}
}

View File

@ -28,68 +28,95 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.captchaPb = new System.Windows.Forms.PictureBox(); captchaPb = new System.Windows.Forms.PictureBox();
this.answerTb = new System.Windows.Forms.TextBox(); answerTb = new System.Windows.Forms.TextBox();
this.submitBtn = new System.Windows.Forms.Button(); submitBtn = new System.Windows.Forms.Button();
this.answerLbl = new System.Windows.Forms.Label(); answerLbl = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.captchaPb)).BeginInit(); label1 = new System.Windows.Forms.Label();
this.SuspendLayout(); passwordTb = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)captchaPb).BeginInit();
SuspendLayout();
// //
// captchaPb // captchaPb
// //
this.captchaPb.Location = new System.Drawing.Point(12, 12); captchaPb.Location = new System.Drawing.Point(13, 14);
this.captchaPb.Name = "captchaPb"; captchaPb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.captchaPb.Size = new System.Drawing.Size(200, 70); captchaPb.Name = "captchaPb";
this.captchaPb.TabIndex = 0; captchaPb.Size = new System.Drawing.Size(235, 81);
this.captchaPb.TabStop = false; captchaPb.TabIndex = 0;
captchaPb.TabStop = false;
// //
// answerTb // answerTb
// //
this.answerTb.Location = new System.Drawing.Point(118, 88); answerTb.Location = new System.Drawing.Point(136, 130);
this.answerTb.Name = "answerTb"; answerTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.answerTb.Size = new System.Drawing.Size(94, 20); answerTb.Name = "answerTb";
this.answerTb.TabIndex = 1; answerTb.Size = new System.Drawing.Size(111, 23);
answerTb.TabIndex = 2;
// //
// submitBtn // submitBtn
// //
this.submitBtn.Location = new System.Drawing.Point(137, 114); submitBtn.Location = new System.Drawing.Point(159, 171);
this.submitBtn.Name = "submitBtn"; submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.submitBtn.Size = new System.Drawing.Size(75, 23); submitBtn.Name = "submitBtn";
this.submitBtn.TabIndex = 2; submitBtn.Size = new System.Drawing.Size(88, 27);
this.submitBtn.Text = "Submit"; submitBtn.TabIndex = 2;
this.submitBtn.UseVisualStyleBackColor = true; submitBtn.Text = "Submit";
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click); submitBtn.UseVisualStyleBackColor = true;
submitBtn.Click += submitBtn_Click;
// //
// answerLbl // answerLbl
// //
this.answerLbl.AutoSize = true; answerLbl.AutoSize = true;
this.answerLbl.Location = new System.Drawing.Point(12, 91); answerLbl.Location = new System.Drawing.Point(13, 133);
this.answerLbl.Name = "answerLbl"; answerLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.answerLbl.Size = new System.Drawing.Size(100, 13); answerLbl.Name = "answerLbl";
this.answerLbl.TabIndex = 0; answerLbl.Size = new System.Drawing.Size(106, 15);
this.answerLbl.Text = "CAPTCHA answer: "; answerLbl.TabIndex = 0;
answerLbl.Text = "CAPTCHA answer: ";
//
// label1
//
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(13, 104);
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(60, 15);
label1.TabIndex = 0;
label1.Text = "Password:";
//
// passwordTb
//
passwordTb.Location = new System.Drawing.Point(81, 101);
passwordTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
passwordTb.Name = "passwordTb";
passwordTb.PasswordChar = '*';
passwordTb.Size = new System.Drawing.Size(167, 23);
passwordTb.TabIndex = 1;
// //
// CaptchaDialog // CaptchaDialog
// //
this.AcceptButton = this.submitBtn; AcceptButton = submitBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(224, 149); ClientSize = new System.Drawing.Size(261, 210);
this.Controls.Add(this.answerLbl); Controls.Add(passwordTb);
this.Controls.Add(this.submitBtn); Controls.Add(label1);
this.Controls.Add(this.answerTb); Controls.Add(answerLbl);
this.Controls.Add(this.captchaPb); Controls.Add(submitBtn);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; Controls.Add(answerTb);
this.MaximizeBox = false; Controls.Add(captchaPb);
this.MinimizeBox = false; FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "CaptchaDialog"; Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.ShowIcon = false; MaximizeBox = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; MinimizeBox = false;
this.Text = "CAPTCHA"; Name = "CaptchaDialog";
((System.ComponentModel.ISupportInitialize)(this.captchaPb)).EndInit(); ShowIcon = false;
this.ResumeLayout(false); StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.PerformLayout(); Text = "CAPTCHA";
((System.ComponentModel.ISupportInitialize)captchaPb).EndInit();
ResumeLayout(false);
PerformLayout();
} }
#endregion #endregion
@ -98,5 +125,7 @@
private System.Windows.Forms.TextBox answerTb; private System.Windows.Forms.TextBox answerTb;
private System.Windows.Forms.Button submitBtn; private System.Windows.Forms.Button submitBtn;
private System.Windows.Forms.Label answerLbl; private System.Windows.Forms.Label answerLbl;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox passwordTb;
} }
} }

View File

@ -9,23 +9,35 @@ namespace LibationWinForms.Dialogs.Login
public partial class CaptchaDialog : Form public partial class CaptchaDialog : Form
{ {
public string Answer { get; private set; } public string Answer { get; private set; }
public string Password { get; private set; }
private MemoryStream ms { get; } private MemoryStream ms { get; }
private Image image { get; } private Image image { get; }
public CaptchaDialog(byte[] captchaImage) public CaptchaDialog() => InitializeComponent();
public CaptchaDialog(string password, byte[] captchaImage) : this()
{ {
InitializeComponent();
this.FormClosed += (_, __) => { ms?.Dispose(); image?.Dispose(); }; this.FormClosed += (_, __) => { ms?.Dispose(); image?.Dispose(); };
ms = new MemoryStream(captchaImage); ms = new MemoryStream(captchaImage);
image = Image.FromStream(ms); image = Image.FromStream(ms);
this.captchaPb.Image = image; this.captchaPb.Image = image;
passwordTb.Text = password;
(string.IsNullOrEmpty(password) ? passwordTb : answerTb).Select();
} }
private void submitBtn_Click(object sender, EventArgs e) private void submitBtn_Click(object sender, EventArgs e)
{ {
Answer = this.answerTb.Text; if (string.IsNullOrWhiteSpace(passwordTb.Text))
{
MessageBox.Show(this, "Please re-enter your password");
return;
}
Answer = answerTb.Text;
Password = passwordTb.Text;
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer }); Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });

View File

@ -10,25 +10,27 @@ namespace LibationWinForms.Login
{ {
private Account _account { get; } private Account _account { get; }
public string DeviceName { get; } = "Libation";
public WinformLoginCallback(Account account) public WinformLoginCallback(Account account)
{ {
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
} }
public Task<string> Get2faCodeAsync() public Task<string> Get2faCodeAsync(string prompt)
{ {
using var dialog = new _2faCodeDialog(); using var dialog = new _2faCodeDialog(prompt);
if (ShowDialog(dialog)) if (ShowDialog(dialog))
return Task.FromResult(dialog.Code); return Task.FromResult(dialog.Code);
return Task.FromResult<string>(null); return Task.FromResult<string>(null);
} }
public Task<string> GetCaptchaAnswerAsync(byte[] captchaImage) public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
{ {
using var dialog = new CaptchaDialog(captchaImage); using var dialog = new CaptchaDialog(password, captchaImage);
if (ShowDialog(dialog)) if (ShowDialog(dialog))
return Task.FromResult(dialog.Answer); return Task.FromResult((dialog.Password, dialog.Answer));
return Task.FromResult<string>(null); return Task.FromResult<(string, string)>((null,null));
} }
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig) public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)

View File

@ -28,62 +28,77 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.submitBtn = new System.Windows.Forms.Button(); submitBtn = new System.Windows.Forms.Button();
this.codeTb = new System.Windows.Forms.TextBox(); codeTb = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label(); label1 = new System.Windows.Forms.Label();
this.SuspendLayout(); promptLbl = new System.Windows.Forms.Label();
SuspendLayout();
// //
// submitBtn // submitBtn
// //
this.submitBtn.Location = new System.Drawing.Point(15, 51); submitBtn.Location = new System.Drawing.Point(18, 108);
this.submitBtn.Name = "SaveBtn"; submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.submitBtn.Size = new System.Drawing.Size(79, 23); submitBtn.Name = "submitBtn";
this.submitBtn.TabIndex = 1; submitBtn.Size = new System.Drawing.Size(191, 27);
this.submitBtn.Text = "Submit"; submitBtn.TabIndex = 1;
this.submitBtn.UseVisualStyleBackColor = true; submitBtn.Text = "Submit";
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click); submitBtn.UseVisualStyleBackColor = true;
submitBtn.Click += submitBtn_Click;
// //
// codeTb // codeTb
// //
this.codeTb.Location = new System.Drawing.Point(15, 25); codeTb.Location = new System.Drawing.Point(108, 79);
this.codeTb.Name = "newTagsTb"; codeTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.codeTb.ScrollBars = System.Windows.Forms.ScrollBars.Both; codeTb.Name = "codeTb";
this.codeTb.Size = new System.Drawing.Size(79, 20); codeTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.codeTb.TabIndex = 0; codeTb.Size = new System.Drawing.Size(101, 23);
codeTb.TabIndex = 0;
// //
// label1 // label1
// //
this.label1.AutoSize = true; label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 9); label1.Location = new System.Drawing.Point(13, 82);
this.label1.Name = "label1"; label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Size = new System.Drawing.Size(82, 13); label1.Name = "label1";
this.label1.TabIndex = 2; label1.Size = new System.Drawing.Size(87, 15);
this.label1.Text = "Enter 2FA Code"; label1.TabIndex = 2;
label1.Text = "Enter 2FA Code";
//
// promptLbl
//
promptLbl.Location = new System.Drawing.Point(13, 9);
promptLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
promptLbl.Name = "promptLbl";
promptLbl.Size = new System.Drawing.Size(196, 59);
promptLbl.TabIndex = 2;
promptLbl.Text = "[Prompt]";
// //
// _2faCodeDialog // _2faCodeDialog
// //
this.AcceptButton = this.submitBtn; AcceptButton = submitBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(106, 86); ClientSize = new System.Drawing.Size(222, 147);
this.Controls.Add(this.label1); Controls.Add(promptLbl);
this.Controls.Add(this.codeTb); Controls.Add(label1);
this.Controls.Add(this.submitBtn); Controls.Add(codeTb);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; Controls.Add(submitBtn);
this.MaximizeBox = false; FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MinimizeBox = false; Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.Name = "_2faCodeDialog"; MaximizeBox = false;
this.ShowIcon = false; MinimizeBox = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; Name = "_2faCodeDialog";
this.Text = "2FA Code"; ShowIcon = false;
this.ResumeLayout(false); StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.PerformLayout(); Text = "2FA Code";
ResumeLayout(false);
PerformLayout();
} }
#endregion #endregion
private System.Windows.Forms.Button submitBtn; private System.Windows.Forms.Button submitBtn;
private System.Windows.Forms.TextBox codeTb; private System.Windows.Forms.TextBox codeTb;
private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label promptLbl;
} }
} }

View File

@ -8,9 +8,10 @@ namespace LibationWinForms.Dialogs.Login
{ {
public string Code { get; private set; } public string Code { get; private set; }
public _2faCodeDialog() public _2faCodeDialog() => InitializeComponent();
public _2faCodeDialog(string prompt) : this()
{ {
InitializeComponent(); promptLbl.Text = prompt;
} }
private void submitBtn_Click(object sender, EventArgs e) private void submitBtn_Click(object sender, EventArgs e)

View File

@ -71,7 +71,9 @@
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.visibleCountLbl = new LibationWinForms.FormattableToolStripStatusLabel(); this.upgradePb = new System.Windows.Forms.ToolStripProgressBar();
this.upgradeLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.visibleCountLbl = new LibationWinForms.FormattableToolStripStatusLabel();
this.springLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.springLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.pdfsCountsLbl = new LibationWinForms.FormattableToolStripStatusLabel(); this.pdfsCountsLbl = new LibationWinForms.FormattableToolStripStatusLabel();
@ -418,7 +420,9 @@
// //
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(40, 40); this.statusStrip1.ImageScalingSize = new System.Drawing.Size(40, 40);
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.visibleCountLbl, this.upgradeLbl,
this.upgradePb,
this.visibleCountLbl,
this.springLbl, this.springLbl,
this.backupsCountsLbl, this.backupsCountsLbl,
this.pdfsCountsLbl}); this.pdfsCountsLbl});
@ -429,10 +433,21 @@
this.statusStrip1.Size = new System.Drawing.Size(1025, 22); this.statusStrip1.Size = new System.Drawing.Size(1025, 22);
this.statusStrip1.TabIndex = 6; this.statusStrip1.TabIndex = 6;
this.statusStrip1.Text = "statusStrip1"; this.statusStrip1.Text = "statusStrip1";
// //
// visibleCountLbl // upgradePb
// //
this.visibleCountLbl.FormatText = "Visible: {0}"; this.upgradePb.Name = "upgradePb";
this.upgradePb.Size = new System.Drawing.Size(100, 16);
//
// upgradeLbl
//
this.upgradeLbl.Name = "upgradeLbl";
this.upgradeLbl.Size = new System.Drawing.Size(66, 17);
this.upgradeLbl.Text = "Upgrading:";
//
// visibleCountLbl
//
this.visibleCountLbl.FormatText = "Visible: {0}";
this.visibleCountLbl.Name = "visibleCountLbl"; this.visibleCountLbl.Name = "visibleCountLbl";
this.visibleCountLbl.Size = new System.Drawing.Size(61, 17); this.visibleCountLbl.Size = new System.Drawing.Size(61, 17);
this.visibleCountLbl.Text = "Visible: {0}"; this.visibleCountLbl.Text = "Visible: {0}";
@ -671,5 +686,7 @@
private System.Windows.Forms.Button removeBooksBtn; private System.Windows.Forms.Button removeBooksBtn;
private System.Windows.Forms.Button doneRemovingBtn; private System.Windows.Forms.Button doneRemovingBtn;
private System.Windows.Forms.ToolStripMenuItem setPdfDownloadedManualToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem setPdfDownloadedManualToolStripMenuItem;
} private System.Windows.Forms.ToolStripProgressBar upgradePb;
private System.Windows.Forms.ToolStripStatusLabel upgradeLbl;
}
} }

View File

@ -17,11 +17,8 @@ namespace LibationWinForms
private void Configure_ScanAuto() private void Configure_ScanAuto()
{ {
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok // creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
var hours = 0;
var minutes = 5; autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
var seconds = 0;
var _5_minutes = new TimeSpan(hours, minutes, seconds);
autoScanTimer = new InterruptableTimer(_5_minutes);
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI // subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
autoScanTimer.Elapsed += async (_, __) => autoScanTimer.Elapsed += async (_, __) =>
@ -50,7 +47,7 @@ namespace LibationWinForms
// load init state to menu checkbox // load init state to menu checkbox
Load += updateAutoScanLibraryToolStripMenuItem; Load += updateAutoScanLibraryToolStripMenuItem;
// if enabled: begin on load // if enabled: begin on load
Load += startAutoScan; Shown += startAutoScan;
// if new 'default' account is added, run autoscan // if new 'default' account is added, run autoscan
AccountsSettingsPersister.Saving += accountsPreSave; AccountsSettingsPersister.Saving += accountsPreSave;

View File

@ -0,0 +1,35 @@
using LibationUiBase;
using LibationWinForms.Dialogs;
using System.Threading.Tasks;
namespace LibationWinForms
{
public partial class Form1
{
private void Configure_Upgrade()
{
setProgressVisible(false);
#if !DEBUG
Task upgradeAvailable(UpgradeEventArgs e)
{
var notificationResult = new UpgradeNotificationDialog(e.UpgradeProperties).ShowDialog(this);
e.Ignore = notificationResult == System.Windows.Forms.DialogResult.Ignore;
e.InstallUpgrade = notificationResult == System.Windows.Forms.DialogResult.Yes;
return Task.CompletedTask;
}
var upgrader = new Upgrader();
upgrader.DownloadProgress += (_, e) => Invoke(() => upgradePb.Value = int.Max(0, int.Min(100, (int)(e.ProgressPercentage ?? 0))));
upgrader.DownloadBegin += (_, _) => Invoke(() => setProgressVisible(true));
upgrader.DownloadCompleted += (_, _) => Invoke(() => setProgressVisible(false));
Shown += async (_, _) => await upgrader.CheckForUpgradeAsync(upgradeAvailable);
#endif
}
private void setProgressVisible(bool visible) => upgradeLbl.Visible = upgradePb.Visible = visible;
}
}

View File

@ -51,6 +51,7 @@ namespace LibationWinForms
Configure_Settings(); Configure_Settings();
Configure_ProcessQueue(); Configure_ProcessQueue();
Configure_Filter(); Configure_Filter();
Configure_Upgrade();
// misc which belongs in winforms app but doesn't have a UI element // misc which belongs in winforms app but doesn't have a UI element
Configure_NonUI(); Configure_NonUI();

View File

@ -13,19 +13,12 @@
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<StartupObject />
<IsPublishable>true</IsPublishable> <IsPublishable>true</IsPublishable>
<!-- Version is now in AppScaffolding.csproj --> <!-- Version is now in AppScaffolding.csproj -->
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -44,7 +37,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.6" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" /> <PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" />
</ItemGroup> </ItemGroup>

View File

@ -51,9 +51,6 @@ namespace LibationWinForms
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue(); MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
#if !DEBUG
checkForUpdate();
#endif
// logging is init'd here // logging is init'd here
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config); AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config);
} }
@ -165,31 +162,6 @@ namespace LibationWinForms
// - long running. won't get a chance to finish in cli. don't move to app scaffolding // - long running. won't get a chance to finish in cli. don't move to app scaffolding
} }
private static void checkForUpdate()
{
AppScaffolding.UpgradeProperties upgradeProperties;
try
{
upgradeProperties = AppScaffolding.LibationScaffolding.GetLatestRelease();
if (upgradeProperties is null)
return;
}
catch (Exception ex)
{
MessageBoxLib.ShowAdminAlert(null, "Error checking for update", "Error checking for update", ex);
return;
}
if (upgradeProperties.ZipUrl is null)
{
MessageBox.Show(upgradeProperties.HtmlUrl, "New version available");
return;
}
Updater.Run(upgradeProperties);
}
private static void postLoggingGlobalExceptionHandling() private static void postLoggingGlobalExceptionHandling()
{ {
// this line is all that's needed for strict handling // this line is all that's needed for strict handling

View File

@ -1,57 +0,0 @@
using System;
using System.Windows.Forms;
using AppScaffolding;
using AutoUpdaterDotNET;
using LibationFileManager;
using LibationWinForms.Dialogs;
namespace LibationWinForms
{
public static class Updater
{
public static void Run(UpgradeProperties upgradeProperties)
{
string latestVersionOnServer = upgradeProperties.LatestRelease.ToString();
string downloadZipUrl = upgradeProperties.ZipUrl;
AutoUpdater.ParseUpdateInfoEvent +=
args => args.UpdateInfo = new()
{
CurrentVersion = latestVersionOnServer,
DownloadURL = downloadZipUrl,
ChangelogURL = LibationScaffolding.RepositoryLatestUrl
};
void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args is null || !args.IsUpdateAvailable)
return;
const string ignoreUpdate = "IgnoreUpdate";
var config = Configuration.Instance;
if (config.GetString(propertyName: ignoreUpdate) == args.CurrentVersion)
return;
var notificationResult = new UpgradeNotificationDialog(upgradeProperties).ShowDialog();
if (notificationResult == DialogResult.Ignore)
config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpdate);
if (notificationResult != DialogResult.Yes) return;
try
{
Serilog.Log.Logger.Information("Start upgrade. {@DebugInfo}", new { CurrentlyInstalled = args.InstalledVersion, TargetVersion = args.CurrentVersion });
AutoUpdater.DownloadUpdate(args);
}
catch (Exception ex)
{
MessageBoxLib.ShowAdminAlert(null, "Error downloading update", "Error downloading update", ex);
}
}
AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
AutoUpdater.Start(LibationScaffolding.RepositoryLatestUrl);
}
}
}

View File

@ -11,13 +11,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -31,7 +25,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" /> <ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -23,12 +23,12 @@ namespace LinuxConfigApp
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException(); public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException(); public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
//only run the auto updater if the current app was installed from the //only run the auto upgrader if the current app was installed from the
//.deb package. Try to detect this by checking if the symlink exists. //.deb package. Try to detect this by checking if the symlink exists.
public bool CanUpdate => Directory.Exists("/usr/lib/libation"); public bool CanUpgrade => Directory.Exists("/usr/lib/libation");
public void InstallUpdate(string updateBundle) public void InstallUpgrade(string upgradeBundle)
{ {
RunAsRoot("apt", $"install '{updateBundle}'"); RunAsRoot("apt", $"install '{upgradeBundle}'");
} }
public Process RunAsRoot(string exe, string args) public Process RunAsRoot(string exe, string args)

View File

@ -11,13 +11,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -31,7 +25,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" /> <ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -13,15 +13,15 @@ namespace MacOSConfigApp
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException(); public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
//I haven't figured out how to find the app bundle's directory from within //I haven't figured out how to find the app bundle's directory from within
//the running process, so don't update unless it's "installed" in /Applications //the running process, so don't upgrade unless it's "installed" in /Applications
public bool CanUpdate => Directory.Exists(AppPath); public bool CanUpgrade => Directory.Exists(AppPath);
public void InstallUpdate(string updateBundle) public void InstallUpgrade(string upgradeBundle)
{ {
Serilog.Log.Information($"Extracting update bundle to {AppPath}"); Serilog.Log.Information($"Extracting upgrade bundle to {AppPath}");
//tar wil overwrite existing without elevated privileges //tar wil overwrite existing without elevated privileges
Process.Start("tar", $"-xf \"{updateBundle}\" -C \"/Applications\"").WaitForExit(); Process.Start("tar", $"-xf \"{upgradeBundle}\" -C \"/Applications\"").WaitForExit();
//For now, it seems like this step is unnecessary. We can overwrite and //For now, it seems like this step is unnecessary. We can overwrite and
//run Libation without needing to re-add the exception. This is insurance. //run Libation without needing to re-add the exception. This is insurance.

View File

@ -0,0 +1,106 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace WindowsConfigApp
{
internal static partial class FolderIcon
{
// https://stackoverflow.com/a/21389253
public static byte[] ToIcon(this Image img)
{
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
// Header
bw.Write((short)0); // 0-1 : reserved
bw.Write((short)1); // 2-3 : 1=ico, 2=cur
bw.Write((short)1); // 4-5 : number of images
// Image directory
var w = img.Width;
if (w >= 256) w = 0;
bw.Write((byte)w); // 0 : width of image
var h = img.Height;
if (h >= 256) h = 0;
bw.Write((byte)h); // 1 : height of image
bw.Write((byte)0); // 2 : number of colors in palette
bw.Write((byte)0); // 3 : reserved
bw.Write((short)0); // 4 : number of color planes
bw.Write((short)0); // 6 : bits per pixel
var sizeHere = ms.Position;
bw.Write((int)0); // 8 : image size
var start = (int)ms.Position + 4;
bw.Write(start); // 12: offset of image data
// Image data
img.Save(ms, new PngEncoder());
var imageSize = (int)ms.Position - start;
ms.Seek(sizeHere, SeekOrigin.Begin);
bw.Write(imageSize);
ms.Seek(0, SeekOrigin.Begin);
// And load it
return ms.ToArray();
}
public static void DeleteIcon(this DirectoryInfo directoryInfo) => DeleteIcon(directoryInfo.FullName);
public static void DeleteIcon(string dir)
{
string[] array = new string[3] { "desktop.ini", "Icon.ico", ".hidden" };
foreach (string path in array)
{
string text = Path.Combine(dir, path);
if (File.Exists(text))
{
File.SetAttributes(text, File.GetAttributes(text) | FileAttributes.Normal);
new FileInfo(text).IsReadOnly = false;
File.Delete(text);
}
}
refresh();
}
// https://github.com/dimuththarindu/FIC-Folder-Icon-Changer/blob/master/project/FIC/Classes/IconCustomizer.cs
public static void SetIcon(this DirectoryInfo directoryInfo, string icoPath, string folderType)
=> SetIcon(directoryInfo.FullName, icoPath, folderType);
public static void SetIcon(string dir, string icoPath, string folderType)
{
var desktop_ini = Path.Combine(dir, "desktop.ini");
var Icon_ico = Path.Combine(dir, "Icon.ico");
var hidden = Path.Combine(dir, ".hidden");
//deleting existing files
DeleteIcon(dir);
//copying Icon file //overwriting
File.Copy(icoPath, Icon_ico, true);
//writing configuration file
string[] desktopLines = { "[.ShellClassInfo]", "IconResource=Icon.ico,0", "[ViewState]", "Mode=", "Vid=", $"FolderType={folderType}" };
File.WriteAllLines(desktop_ini, desktopLines);
//configure file 2
string[] hiddenLines = { "desktop.ini", "Icon.ico" };
File.WriteAllLines(hidden, hiddenLines);
//making system files
File.SetAttributes(desktop_ini, File.GetAttributes(desktop_ini) | FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly);
File.SetAttributes(Icon_ico, File.GetAttributes(Icon_ico) | FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly);
File.SetAttributes(hidden, File.GetAttributes(hidden) | FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly);
// this strangely completes the process. also hides these 3 hidden system files, even if "show hidden items" is checked
File.SetAttributes(dir, File.GetAttributes(dir) | FileAttributes.ReadOnly);
refresh();
}
private static void refresh() => SHChangeNotify(0x08000000, 0x0000, 0, 0); //SHCNE_ASSOCCHANGED SHCNF_IDLIST
[DllImport("shell32.dll", SetLastError = true)]
private static extern void SHChangeNotify(int wEventId, int uFlags, nint dwItem1, nint dwItem2);
}
}

View File

@ -1,12 +1,9 @@
using System; using SixLabors.ImageSharp;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Dinah.Core.WindowsDesktop;
using Dinah.Core.WindowsDesktop.Drawing;
using LibationFileManager; using LibationFileManager;
using System.IO;
using System;
using Dinah.Core;
namespace WindowsConfigApp namespace WindowsConfigApp
{ {
@ -21,11 +18,11 @@ namespace WindowsConfigApp
try try
{ {
var icon = ImageReader.ToIcon(image); var icon = Image.Load(File.ReadAllBytes(image)).ToIcon();
iconPath = Path.Combine(directory, $"{Guid.NewGuid()}.ico"); iconPath = Path.Combine(directory, $"{Guid.NewGuid()}.ico");
icon.Save(iconPath); File.WriteAllBytes(iconPath, icon);
new DirectoryInfo(directory).SetIcon(iconPath, Directories.FolderTypes.Music); new DirectoryInfo(directory)?.SetIcon(iconPath, "Music");
} }
finally finally
{ {
@ -36,8 +33,9 @@ namespace WindowsConfigApp
public void DeleteFolderIcon(string directory) public void DeleteFolderIcon(string directory)
=> new DirectoryInfo(directory)?.DeleteIcon(); => new DirectoryInfo(directory)?.DeleteIcon();
public bool CanUpdate => true;
public void InstallUpdate(string updateBundle) public bool CanUpgrade => true;
public void InstallUpgrade(string upgradeBundle)
{ {
var thisExe = Environment.ProcessPath; var thisExe = Environment.ProcessPath;
var thisDir = Path.GetDirectoryName(thisExe); var thisDir = Path.GetDirectoryName(thisExe);
@ -45,7 +43,10 @@ namespace WindowsConfigApp
File.Copy("ZipExtractor.exe", zipExtractor, overwrite: true); File.Copy("ZipExtractor.exe", zipExtractor, overwrite: true);
RunAsRoot(zipExtractor, $"--input \"{updateBundle}\" --output \"{thisDir}\" --executable \"{thisExe}\""); RunAsRoot(zipExtractor,
$"--input {upgradeBundle.SurroundWithQuotes()} " +
$"--output {thisDir.SurroundWithQuotes()} " +
$"--executable {thisExe.SurroundWithQuotes()}");
} }
public Process RunAsRoot(string exe, string args) public Process RunAsRoot(string exe, string args)

View File

@ -2,24 +2,15 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!-- <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
- Not using SatelliteResourceLanguages will load all language packs: works
- Specifying 'en' semicolon 1 more should load 1 language pack: works
- Specifying only 'en' should load no language packs: broken, still loads all
-->
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -33,11 +24,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" /> <ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>