Compare commits

..

23 Commits

Author SHA1 Message Date
Robert McRackan
cf9ec9facf did last tag incorrect. New version 2023-02-28 10:13:26 -05:00
Robert McRackan
f6084ef10c v9.4.1 2023-02-28 10:04:47 -05:00
rmcrackan
740b73beb7 Merge pull request #518 from Mbucari/master
Improve Audible login and Libation Upgrade
2023-02-28 09:51:08 -05:00
Mbucari
5c45802391 Fixed review comments 2023-02-28 07:42:26 -07:00
MBucari
429aa603f5 Update workflows 2023-02-27 21:41:59 -07:00
MBucari
80ea394934 Merge branch 'master' of https://github.com/Mbucari/Libation 2023-02-27 16:33:16 -07:00
Mbucari
bce4437c79 Change workflows 2023-02-27 16:18:48 -07:00
Mbucari
b6ad1a289b Remove windows desktop runtime dependency from chardonnay 2023-02-27 16:13:40 -07:00
Mbucari
2a22d05f37 Remove windows desktop runtime dependency from chardonnay 2023-02-27 15:08:54 -07:00
Mbucari
d787843fd2 Unify upgrade process and add update progress bar 2023-02-27 14:08:15 -07:00
Mbucari
ded58f687d Update 2FA and Captcha controls 2023-02-27 14:08:14 -07:00
Mbucari
1f1f34b6ce Merge branch 'rmcrackan:master' into master 2023-02-27 09:36:53 -07:00
Mbucari
ffadf90f4f Fix MFA and 2FA 2023-02-27 09:36:19 -07:00
rmcrackan
67807efacf Merge pull request #515 from Mbucari/patch-4
Update InstallOnMac.md
2023-02-26 15:29:55 -05:00
Mbucari
980f5afa54 Update InstallOnMac.md 2023-02-25 19:42:45 -07:00
Robert McRackan
b2f68760b2 New audible api login 2023-02-24 15:52:14 -05:00
rmcrackan
faf86711a5 Merge pull request #509 from Mbucari/master
Add More MP3 Options and improved AAXClean
2023-02-24 15:35:38 -05:00
Mbucari
4a78b9d28f Revert workflow change 2023-02-24 12:38:29 -07:00
Michael Bucari-Tovo
1b0a7f5062 New mp3 options and improved encoding performance 2023-02-24 12:12:41 -07:00
Mbucari
49982043e0 Merge branch 'rmcrackan:master' into master 2023-02-24 11:15:14 -07:00
Robert McRackan
378cf7057e updated to AudibleApi v8 2023-02-24 13:12:18 -05:00
Mbucari
abdc0f018e Update build-linux.yml 2023-02-22 09:23:15 -07:00
Robert McRackan
c65f61b92e Fix paypal links 2023-02-22 07:33:58 -05:00
89 changed files with 1164 additions and 627 deletions

View File

@@ -23,10 +23,10 @@ env:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [Linux, MacOS]
os: [ubuntu-latest, macos-latest]
arch: [x64, arm64]
steps:
- uses: actions/checkout@v3
@@ -45,62 +45,63 @@ jobs:
then
version="${inputVersion}"
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
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Unit test
if: ${{ inputs.run_unit_tests }}
working-directory: ./Source
run: dotnet test
- name: Publish
id: publish
working-directory: ./Source
run: |
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"
dotnet publish \
LibationAvalonia/LibationAvalonia.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LibationAvalonia/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LibationAvalonia/Properties/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \
LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj \
LoadByOS/${display_os}ConfigApp/${display_os}ConfigApp.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LoadByOS/Properties/${display_os}ConfigApp/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \
LibationCli/LibationCli.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LibationCli/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=LibationCli/Properties/PublishProfiles/${display_os}Profile.pubxml
dotnet publish \
HangoverAvalonia/HangoverAvalonia.csproj \
--runtime "$RUNTIME_IDENTIFIER" \
--configuration ${{ env.DOTNET_CONFIGURATION }} \
--output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=HangoverAvalonia/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml
--output bin/Publish/${display_os}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \
-p:PublishProfile=HangoverAvalonia/Properties/PublishProfiles/${display_os}Profile.pubxml
- name: Build 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: |
BUNDLE_DIR=$(pwd)
echo "Bundle dir: ${BUNDLE_DIR}"
cd ..
SCRIPT=../../../Scripts/Bundle_${{ matrix.os }}.sh
SCRIPT=../../../Scripts/Bundle_${{ steps.publish.outputs.display_os }}.sh
chmod +rx ${SCRIPT}
${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ matrix.arch }}"
artifact=$(ls ./bundle)
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"
- name: Publish bundle
uses: actions/upload-artifact@v3
with:
name: ${{ 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

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
@@ -13,9 +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.
- Open a terminal (Go > Utilities > Terminal)
- Copy/paste/run the following command (you'll be prompted to enter your password)
```Console
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!
## Running Hangover

View File

@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.

View File

@@ -2,7 +2,7 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PayPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
# Table of Contents

View File

@@ -88,38 +88,27 @@ cp $FOLDER_EXEC/Libation.desktop $FOLDER_DESKTOP/Libation.desktop
echo "Creating pre-install file..."
echo "#!/bin/bash
# Pre-install script, removes previous installation program files and sym links
echo \"Removing previously created symlinks...\"
rm /usr/bin/libation
rm /usr/bin/hangover
rm /usr/bin/libationcli
echo \"Removing previously installed Libation files...\"
rm -r /usr/lib/libation
# making sure it won't stop installation
exit 0
" >> $FOLDER_DEBIAN/preinst
echo "Creating post-install file..."
echo "#!/bin/bash
gtk-update-icon-cache -f /usr/share/icons/hicolor/
ln -s /usr/lib/libation/Libation /usr/bin/libation
ln -s /usr/lib/libation/Hangover /usr/bin/hangover
ln -s /usr/lib/libation/LibationCli /usr/bin/libationcli
# 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
fi
# workaround until this file is moved to the user's home directory
touch /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/postinst"
if [ "$(uname -s)" == "Darwin" ]; then
echo "macOS detected, installing dpkg"
brew install dpkg
fi
DEB_FILE=Libation.${VERSION}-linux-chardonnay-${ARCH}.deb
echo "Creating $DEB_FILE"
dpkg-deb -Zxz --build $DEB_DIR ./$DEB_FILE
@@ -149,4 +143,4 @@ mv $DEB_FILE ./bundle/$DEB_FILE
rm -r "$BIN_DIR"
echo "Done!"
echo "Done!"

View File

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

View File

@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="0.5.16" />
<PackageReference Include="AAXClean.Codecs" Version="1.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -40,8 +40,14 @@ namespace AaxDecrypter
AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", "");
}
if (DownloadOptions.FixupFile && !string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator))
AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator);
if (DownloadOptions.FixupFile)
{
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator))
AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator);
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Copyright))
AaxFile.AppleTags.Copyright = AaxFile.AppleTags.Copyright.Replace("(P)", "℗").Replace("&#169;", "©");
}
//Finishing configuring lame encoder.
if (DownloadOptions.OutputFormat == OutputFormat.Mp3)

View File

@@ -93,15 +93,13 @@ That naming may not be desirable for everyone, but it's an easy change to instea
? AaxFile.ConvertToMultiMp4aAsync
(
splitChapters,
newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback),
DownloadOptions.TrimOutputToChapterLength
newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback)
)
: AaxFile.ConvertToMultiMp3Async
(
splitChapters,
newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback),
DownloadOptions.LameConfig,
DownloadOptions.TrimOutputToChapterLength
DownloadOptions.LameConfig
);
void newSplit(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)

View File

@@ -3,6 +3,7 @@ using AAXClean.Codecs;
using Dinah.Core.Net.Http;
using FileManager;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
@@ -14,12 +15,15 @@ namespace AaxDecrypter
public AaxcDownloadSingleConverter(string outFileName, string cacheDirectory, IDownloadOptions dlOptions)
: base(outFileName, cacheDirectory, dlOptions)
{
var step = 1;
AsyncSteps.Name = $"Download and Convert Aaxc To {DownloadOptions.OutputFormat}";
AsyncSteps["Step 1: Get Aaxc Metadata"] = () => Task.Run(Step_GetMetadata);
AsyncSteps["Step 2: Download Decrypted Audiobook"] = Step_DownloadAndDecryptAudiobookAsync;
AsyncSteps["Step 3: Download Clips and Bookmarks"] = Step_DownloadClipsBookmarksAsync;
AsyncSteps["Step 4: Create Cue"] = Step_CreateCueAsync;
AsyncSteps[$"Step {step++}: Get Aaxc Metadata"] = () => Task.Run(Step_GetMetadata);
AsyncSteps[$"Step {step++}: Download Decrypted Audiobook"] = Step_DownloadAndDecryptAudiobookAsync;
if (DownloadOptions.MoveMoovToBeginning && DownloadOptions.OutputFormat is OutputFormat.M4b)
AsyncSteps[$"Step {step++}: Move moov atom to beginning"] = Step_MoveMoov;
AsyncSteps[$"Step {step++}: Download Clips and Bookmarks"] = Step_DownloadClipsBookmarksAsync;
AsyncSteps[$"Step {step++}: Create Cue"] = Step_CreateCueAsync;
}
protected async override Task<bool> Step_DownloadAndDecryptAudiobookAsync()
@@ -31,18 +35,7 @@ namespace AaxDecrypter
try
{
await (AaxConversion = decryptAsync(outputFile));
if (AaxConversion.IsCompletedSuccessfully
&& DownloadOptions.MoveMoovToBeginning
&& DownloadOptions.OutputFormat is OutputFormat.M4b)
{
outputFile.Close();
AaxConversion = Mp4File.RelocateMoovAsync(OutputFileName);
AaxConversion.ConversionProgressUpdate += AaxConversion_MoovProgressUpdate;
await AaxConversion;
AaxConversion.ConversionProgressUpdate -= AaxConversion_MoovProgressUpdate;
}
await (AaxConversion = decryptAsync(outputFile));
return AaxConversion.IsCompletedSuccessfully;
}
@@ -52,23 +45,30 @@ namespace AaxDecrypter
}
}
private async Task<bool> Step_MoveMoov()
{
AaxConversion = Mp4File.RelocateMoovAsync(OutputFileName);
AaxConversion.ConversionProgressUpdate += AaxConversion_MoovProgressUpdate;
await AaxConversion;
AaxConversion.ConversionProgressUpdate -= AaxConversion_MoovProgressUpdate;
return AaxConversion.IsCompletedSuccessfully;
}
private void AaxConversion_MoovProgressUpdate(object sender, ConversionProgressEventArgs e)
{
averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds);
var remainingTimeToProcess = (e.TotalDuration - e.ProcessPosition).TotalSeconds;
var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds;
var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average;
if (double.IsNormal(estTimeRemaining))
OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
var progressPercent = 100d * (1 - remainingTimeToProcess / e.TotalDuration.TotalSeconds);
OnDecryptProgressUpdate(
new DownloadProgress
{
ProgressPercentage = progressPercent,
BytesReceived = (long)(InputFileStream.Length * progressPercent),
ProgressPercentage = 100 * e.FractionCompleted,
BytesReceived = (long)(InputFileStream.Length * e.FractionCompleted),
TotalBytesToReceive = InputFileStream.Length
});
}
@@ -79,15 +79,13 @@ namespace AaxDecrypter
(
outputFile,
DownloadOptions.LameConfig,
DownloadOptions.ChapterInfo,
DownloadOptions.TrimOutputToChapterLength
DownloadOptions.ChapterInfo
)
: DownloadOptions.FixupFile
? AaxFile.ConvertToMp4aAsync
(
outputFile,
DownloadOptions.ChapterInfo,
DownloadOptions.TrimOutputToChapterLength
DownloadOptions.ChapterInfo
)
: AaxFile.ConvertToMp4aAsync(outputFile);
}

View File

@@ -3,7 +3,6 @@ using Dinah.Core.Net.Http;
using Dinah.Core.StepRunner;
using FileManager;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
@@ -27,6 +26,7 @@ namespace AaxDecrypter
protected IDownloadOptions DownloadOptions { get; }
protected NetworkFileStream InputFileStream => nfsPersister.NetworkFileStream;
protected virtual long InputFilePosition => InputFileStream.Position;
private bool downloadFinished;
private readonly NetworkFileStreamPersister nfsPersister;
private readonly DownloadProgress zeroProgress;
@@ -84,7 +84,11 @@ namespace AaxDecrypter
{
AverageSpeed averageSpeed = new();
while (InputFileStream.CanRead && InputFileStream.Length > InputFilePosition && !InputFileStream.IsCancelled)
while (
InputFileStream.CanRead
&& InputFileStream.Length > InputFilePosition
&& !InputFileStream.IsCancelled
&& !downloadFinished)
{
averageSpeed.AddPosition(InputFilePosition);
@@ -138,8 +142,7 @@ namespace AaxDecrypter
protected virtual void FinalizeDownload()
{
nfsPersister?.Dispose();
OnDecryptTimeRemaining(TimeSpan.Zero);
OnDecryptProgressUpdate(zeroProgress);
downloadFinished = true;
}
protected async Task<bool> Step_DownloadClipsBookmarksAsync()
@@ -221,6 +224,7 @@ namespace AaxDecrypter
}
finally
{
nfsp.NetworkFileStream.RequestHeaders["User-Agent"] = DownloadOptions.UserAgent;
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
}

View File

@@ -1,5 +1,6 @@
using AAXClean;
using NAudio.Lame;
using System;
namespace AaxDecrypter
{
@@ -9,17 +10,26 @@ namespace AaxDecrypter
{
double bitrateMultiple = 1;
if (mp4File.TimeScale < lameConfig.OutputSampleRate)
{
lameConfig.OutputSampleRate = mp4File.TimeScale;
}
else if (mp4File.TimeScale > lameConfig.OutputSampleRate)
{
bitrateMultiple *= (double)lameConfig.OutputSampleRate / mp4File.TimeScale;
}
if (mp4File.AudioChannels == 2)
{
if (downsample)
bitrateMultiple = 0.5;
bitrateMultiple /= 2;
else
lameConfig.Mode = MPEGMode.Stereo;
}
if (matchSourceBitrate)
{
int kbps = (int)(mp4File.AverageBitrate * bitrateMultiple / 1024);
int kbps = (int)Math.Round(mp4File.AverageBitrate * bitrateMultiple / 1024);
if (lameConfig.VBR is null)
lameConfig.BitRate = kbps;

View File

@@ -14,6 +14,7 @@ namespace AaxDecrypter
public class NetworkFileStream : Stream, IUpdatable
{
public event EventHandler Updated;
public event EventHandler DownloadCompleted;
#region Public Properties
@@ -139,7 +140,10 @@ namespace AaxDecrypter
public async Task BeginDownloadingAsync()
{
if (ContentLength != 0 && WritePosition == ContentLength)
{
_backgroundDownloadTask = Task.CompletedTask;
return;
}
if (ContentLength != 0 && WritePosition > ContentLength)
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
@@ -230,6 +234,7 @@ namespace AaxDecrypter
_writeFile.Close();
_downloadedPiece.Set();
OnUpdate();
DownloadCompleted?.Invoke(this, null);
}
}

View File

@@ -26,8 +26,11 @@ namespace AaxDecrypter
protected override async Task<bool> Step_DownloadAndDecryptAudiobookAsync()
{
while (InputFilePosition < InputFileStream.Length && !InputFileStream.IsCancelled)
await Task.Delay(200);
TaskCompletionSource completionSource = new();
InputFileStream.DownloadCompleted += (_, _) => completionSource.SetResult();
await completionSource.Task;
if (IsCanceled)
return false;

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>9.3.3.1</Version>
<Version>9.4.2.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="5.0.0" />

View File

@@ -39,42 +39,6 @@ namespace AudibleUtilities
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>
public static async Task<ApiExtended> CreateAsync(Account account)
{

View File

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

View File

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

View File

@@ -17,10 +17,14 @@ namespace FileLiberator
protected LameConfig GetLameOptions(Configuration config)
{
LameConfig lameConfig = new();
lameConfig.Mode = MPEGMode.Mono;
LameConfig lameConfig = new()
{
Mode = MPEGMode.Mono,
Quality = config.LameEncoderQuality,
OutputSampleRate = (int)config.MaxSampleRate
};
if (config.LameTargetBitrate)
if (config.LameTargetBitrate)
{
if (config.LameConstantBitrate)
lameConfig.BitRate = config.LameBitrate;

View File

@@ -103,20 +103,20 @@ namespace FileLiberator
{
averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds);
var remainingTimeToProcess = (e.TotalDuration - e.ProcessPosition).TotalSeconds;
var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds;
var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average;
if (double.IsNormal(estTimeRemaining))
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
double progressPercent = 100 * e.ProcessPosition.TotalSeconds / e.TotalDuration.TotalSeconds;
double progressPercent = 100 * e.FractionCompleted;
OnStreamingProgressChanged(
new DownloadProgress
{
ProgressPercentage = progressPercent,
BytesReceived = (long)e.ProcessPosition.TotalSeconds,
TotalBytesToReceive = (long)e.TotalDuration.TotalSeconds
BytesReceived = (long)(e.ProcessPosition - e.StartTime).TotalSeconds,
TotalBytesToReceive = (long)(e.EndTime - e.StartTime).TotalSeconds
});
}
}

View File

@@ -22,7 +22,7 @@ namespace FileLiberator
public OutputFormat OutputFormat { get; init; }
public ChapterInfo ChapterInfo { get; init; }
public NAudio.Lame.LameConfig LameConfig { get; init; }
public string UserAgent => AudibleApi.Resources.USER_AGENT;
public string UserAgent => AudibleApi.Resources.Download_User_Agent;
public bool TrimOutputToChapterLength => config.AllowLibationFixup && config.StripAudibleBrandAudio;
public bool StripUnabridged => config.AllowLibationFixup && config.StripUnabridged;
public bool CreateCueSheet => config.CreateCueSheet;

View File

@@ -21,13 +21,7 @@
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -16,13 +16,7 @@
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<!--

View File

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

View File

@@ -1,8 +1,6 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using System;
using System.Threading;
using LibationAvalonia.Dialogs;
using System.Threading.Tasks;
namespace LibationAvalonia
@@ -18,6 +16,9 @@ namespace LibationAvalonia
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;
}
}

View File

@@ -36,6 +36,7 @@
Width="60"
Height="30"
Content="X"
HorizontalContentAlignment="Center"
IsEnabled="{Binding !IsDefault}"
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
{
public class AvaloniaLoginCallback : AvaloniaLoginBase, ILoginCallback
public class AvaloniaLoginCallback : ILoginCallback
{
private Account _account { get; }
public string DeviceName { get; } = "Libation";
public AvaloniaLoginCallback(Account 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();
if (await ShowDialog(dialog))
var dialog = new _2faCodeDialog(prompt);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return dialog.Code;
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);
if (await ShowDialog(dialog))
return dialog.Answer;
return null;
var dialog = new CaptchaDialog(password, captchaImage);
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.Password, dialog.Answer);
return (null, null);
}
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
{
var dialog = new MfaDialog(mfaConfig);
if (await ShowDialog(dialog))
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (dialog.SelectedName, dialog.SelectedValue);
return (null, null);
}
@@ -42,7 +44,7 @@ namespace LibationAvalonia.Dialogs.Login
public async Task<(string email, string password)> GetLoginAsync()
{
var dialog = new LoginCallbackDialog(_account);
if (await ShowDialog(dialog))
if (await dialog.ShowDialogAsync() is DialogResult.OK)
return (_account.AccountId, dialog.Password);
return (null, null);
}
@@ -50,7 +52,7 @@ namespace LibationAvalonia.Dialogs.Login
public async Task ShowApprovalNeededAsync()
{
var dialog = new ApprovalNeededDialog();
await ShowDialog(dialog);
await dialog.ShowDialogAsync();
}
}
}

View File

@@ -5,14 +5,15 @@ using AudibleUtilities;
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>
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)
{
@@ -24,10 +25,9 @@ namespace LibationAvalonia.Dialogs.Login
{
var dialog = new LoginChoiceEagerDialog(_account);
if (!await ShowDialog(dialog))
if (await dialog.ShowDialogAsync() is not DialogResult.OK)
return null;
switch (dialog.LoginMethod)
{
case LoginMethod.Api:
@@ -35,7 +35,7 @@ namespace LibationAvalonia.Dialogs.Login
case LoginMethod.External:
{
var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl);
return await ShowDialog(externalDialog)
return await externalDialog.ShowDialogAsync() is DialogResult.OK
? ChoiceOut.External(externalDialog.ResponseUrl)
: null;
}

View File

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

View File

@@ -1,5 +1,8 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using LibationAvalonia.ViewModels;
using ReactiveUI;
using System.IO;
using System.Threading.Tasks;
@@ -7,18 +10,43 @@ namespace LibationAvalonia.Dialogs.Login
{
public partial class CaptchaDialog : DialogWindow
{
public string Answer { get; set; }
public Bitmap CaptchaImage { get; }
public string Password => _viewModel.Password;
public string Answer => _viewModel.Answer;
private readonly CaptchaDialogViewModel _viewModel;
public CaptchaDialog()
{
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);
CaptchaImage = new Bitmap(ms);
DataContext = this;
//Avalonia doesn't support animated gifs.
//Deconstruct gifs into frames and manually switch them.
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()
@@ -26,15 +54,73 @@ namespace LibationAvalonia.Dialogs.Login
AvaloniaXamlLoader.Load(this);
}
protected override Task SaveAndCloseAsync()
protected override async 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)
=> 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 Avalonia;
using Avalonia.Controls;
@@ -49,7 +48,7 @@ namespace LibationAvalonia.Dialogs.Login
protected override async Task SaveAndCloseAsync()
{
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");
return;

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Threading.Tasks;
@@ -8,16 +7,20 @@ namespace LibationAvalonia.Dialogs.Login
public partial class _2faCodeDialog : DialogWindow
{
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()
{
InitializeComponent();
DataContext = this;
AvaloniaXamlLoader.Load(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()

View File

@@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="850" d:DesignHeight="620"
MinWidth="800" MinHeight="620"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
MinWidth="900" MinHeight="700"
x:Class="LibationAvalonia.Dialogs.SettingsDialog"
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
Title="Edit Settings"
@@ -603,6 +603,25 @@
</CheckBox>
</Grid>
<Grid Margin="5,5,5,0" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,Auto">
<TextBlock Margin="0,0,0,5" Text="Max audio sample rate:" />
<controls:WheelComboBox
Grid.Row="1"
HorizontalAlignment="Stretch"
Items="{Binding AudioSettings.SampleRates}"
SelectedItem="{Binding AudioSettings.SelectedSampleRate, Mode=TwoWay}"/>
<TextBlock Margin="0,0,0,5" Grid.Column="2" Text="Encoder Quality:" />
<controls:WheelComboBox
Grid.Column="2"
Grid.Row="1"
HorizontalAlignment="Stretch"
Items="{Binding AudioSettings.EncoderQualities}"
SelectedItem="{Binding AudioSettings.SelectedEncoderQuality, Mode=TwoWay}"/>
</Grid>
<controls:GroupBox
Margin="5,5,5,0"
BorderWidth="1"

View File

@@ -11,6 +11,7 @@ using System.Linq;
using FileManager;
using System.IO;
using Avalonia.Collections;
using LibationUiBase;
namespace LibationAvalonia.Dialogs
{
@@ -372,6 +373,31 @@ namespace LibationAvalonia.Dialogs
private int _lameBitrate;
private int _lameVBRQuality;
private string _chapterTitleTemplate;
public SampleRateSelection SelectedSampleRate { get; set; }
public NAudio.Lame.EncoderQuality SelectedEncoderQuality { get; set; }
public AvaloniaList<SampleRateSelection> SampleRates { get; }
= new(
new []
{
AAXClean.SampleRate.Hz_44100,
AAXClean.SampleRate.Hz_32000,
AAXClean.SampleRate.Hz_24000,
AAXClean.SampleRate.Hz_22050,
AAXClean.SampleRate.Hz_16000,
AAXClean.SampleRate.Hz_12000,
}
.Select(s => new SampleRateSelection(s)));
public AvaloniaList<NAudio.Lame.EncoderQuality> EncoderQualities { get; }
= new(
new[]
{
NAudio.Lame.EncoderQuality.High,
NAudio.Lame.EncoderQuality.Standard,
NAudio.Lame.EncoderQuality.Fast,
});
public AudioSettings(Configuration config)
{
@@ -398,6 +424,9 @@ namespace LibationAvalonia.Dialogs
LameMatchSource = config.LameMatchSourceBR;
LameBitrate = config.LameBitrate;
LameVBRQuality = config.LameVBRQuality;
SelectedSampleRate = SampleRates.FirstOrDefault(s => s.SampleRate == config.MaxSampleRate);
SelectedEncoderQuality = config.LameEncoderQuality;
}
public Task<bool> SaveSettingsAsync(Configuration config)
@@ -422,6 +451,9 @@ namespace LibationAvalonia.Dialogs
config.LameBitrate = LameBitrate;
config.LameVBRQuality = LameVBRQuality;
config.LameEncoderQuality = SelectedEncoderQuality;
config.MaxSampleRate = SelectedSampleRate?.SampleRate ?? config.MaxSampleRate;
return Task.FromResult(true);
}

View File

@@ -4,8 +4,6 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<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>
<ApplicationIcon>libation.ico</ApplicationIcon>
<AssemblyName>Libation</AssemblyName>
@@ -17,13 +15,7 @@
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -109,7 +101,7 @@
<PackageReference Include="Avalonia.Diagnostics" 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="XamlNameReferenceGenerator" Version="1.5.1" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -25,6 +25,9 @@ namespace LibationAvalonia.ViewModels
public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel();
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>
public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); }

View File

@@ -47,6 +47,23 @@ namespace LibationAvalonia.Views
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
}
}
public void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook libraryBook)
{
try
{
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated)
{
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
SetQueueCollapseState(false);
_viewModel.ProcessQueue.AddConvertMp3(libraryBook);
}
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
}
}
private void SetQueueCollapseState(bool collapsed)
{
_viewModel.QueueOpen = !collapsed;

View File

@@ -9,7 +9,6 @@ using System.Linq;
namespace LibationAvalonia.Views
{
//DONE
public partial class MainWindow
{
private InterruptableTimer autoScanTimer;
@@ -17,11 +16,7 @@ namespace LibationAvalonia.Views
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
var hours = 0;
var minutes = 5;
var seconds = 0;
var _5_minutes = new TimeSpan(hours, minutes, seconds);
autoScanTimer = new InterruptableTimer(_5_minutes);
autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
// 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 (_, __) =>
@@ -44,9 +39,9 @@ namespace LibationAvalonia.Views
};
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
// if enabled: begin on load
Load += startAutoScan;
Opened += startAutoScan;
// if new 'default' account is added, run autoscan
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

@@ -188,14 +188,22 @@
<views:ProductsDisplay
Name="productsDisplay"
DataContext="{Binding ProductsDisplay}"
LiberateClicked="ProductsDisplay_LiberateClicked"/>
LiberateClicked="ProductsDisplay_LiberateClicked"
ConvertToMp3Clicked="ProductsDisplay_ConvertToMp3Clicked" />
</SplitView>
</Border>
<!-- Bottom Status Strip -->
<Grid Grid.Row="3" Margin="0,10,0,0" VerticalAlignment="Bottom" ColumnDefinitions="*,Auto">
<TextBlock FontSize="14" Grid.Column="0" Text="{Binding VisibleCountText}" VerticalAlignment="Center" />
<TextBlock FontSize="14" Grid.Column="1" Text="{Binding StatusCountText}" VerticalAlignment="Center" HorizontalAlignment="Right" />
<Grid Grid.Row="3" Margin="0,10,0,0" VerticalAlignment="Bottom" ColumnDefinitions="Auto,Auto,*,Auto">
<Grid.Styles>
<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>
</Border>

View File

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

View File

@@ -19,6 +19,7 @@ namespace LibationAvalonia.Views
public partial class ProductsDisplay : UserControl
{
public event EventHandler<LibraryBook> LiberateClicked;
public event EventHandler<LibraryBook> ConvertToMp3Clicked;
private ProductsDisplayViewModel _viewModel => DataContext as ProductsDisplayViewModel;
ImageDisplayDialog imageDisplayDialog;
@@ -131,6 +132,12 @@ namespace LibationAvalonia.Views
await MessageBox.ShowAdminAlert(null, msg, msg, ex);
}
};
var convertToMp3MenuItem = new MenuItem
{
Header = "_Convert to Mp3",
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated
};
convertToMp3MenuItem.Click += (_, _) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
var bookRecordMenuItem = new MenuItem { Header = "View _Bookmarks/Clips" };
bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window);
@@ -141,6 +148,7 @@ namespace LibationAvalonia.Views
setNotDownloadMenuItem,
removeMenuItem,
locateFileMenuItem,
convertToMp3MenuItem,
new Separator(),
bookRecordMenuItem
});

View File

@@ -11,13 +11,7 @@
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<!--

View File

@@ -121,6 +121,12 @@ namespace LibationFileManager
[Description("Lame encoder target. true = Bitrate, false = Quality")]
public bool LameTargetBitrate { get => GetNonString(defaultValue: false); set => SetNonString(value); }
[Description("Maximum audio sample rate")]
public AAXClean.SampleRate MaxSampleRate { get => GetNonString(defaultValue: AAXClean.SampleRate.Hz_44100); set => SetNonString(value); }
[Description("Lame encoder quality")]
public NAudio.Lame.EncoderQuality LameEncoderQuality { get => GetNonString(defaultValue: NAudio.Lame.EncoderQuality.High); set => SetNonString(value); }
[Description("Lame encoder downsamples to mono")]
public bool LameDownsampleMono { get => GetNonString(defaultValue: true); set => SetNonString(value); }

View File

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

View File

@@ -11,8 +11,8 @@ namespace LibationFileManager
public void SetFolderIcon(string image, 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 void InstallUpdate(string updateBundle) => throw new PlatformNotSupportedException();
public void InstallUpgrade(string updateBundle) => throw new PlatformNotSupportedException();
}
}

View File

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

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibationUiBase
{
public class SampleRateSelection
{
public AAXClean.SampleRate SampleRate { get; }
public SampleRateSelection(AAXClean.SampleRate sampleRate)
{
SampleRate = sampleRate;
}
public override string ToString() => $"{(int)SampleRate} Hz";
}
}

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

View File

@@ -10,25 +10,27 @@ namespace LibationWinForms.Login
{
private Account _account { get; }
public string DeviceName { get; } = "Libation";
public WinformLoginCallback(Account 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))
return Task.FromResult(dialog.Code);
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))
return Task.FromResult(dialog.Answer);
return Task.FromResult<string>(null);
return Task.FromResult((dialog.Password, dialog.Answer));
return Task.FromResult<(string, string)>((null,null));
}
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)

View File

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

View File

@@ -1,6 +1,7 @@
using System;
using LibationFileManager;
using System.Linq;
using LibationUiBase;
namespace LibationWinForms.Dialogs
{
@@ -26,6 +27,25 @@ namespace LibationWinForms.Dialogs
Configuration.ClipBookmarkFormat.Json
});
maxSampleRateCb.Items.AddRange(
new object[]
{
new SampleRateSelection(AAXClean.SampleRate.Hz_44100),
new SampleRateSelection(AAXClean.SampleRate.Hz_32000),
new SampleRateSelection(AAXClean.SampleRate.Hz_24000),
new SampleRateSelection(AAXClean.SampleRate.Hz_22050),
new SampleRateSelection(AAXClean.SampleRate.Hz_16000),
new SampleRateSelection(AAXClean.SampleRate.Hz_12000)
});
encoderQualityCb.Items.AddRange(
new object[]
{
NAudio.Lame.EncoderQuality.High,
NAudio.Lame.EncoderQuality.Standard,
NAudio.Lame.EncoderQuality.Fast,
});
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
createCueSheetCbox.Checked = config.CreateCueSheet;
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
@@ -42,6 +62,8 @@ namespace LibationWinForms.Dialogs
lameTargetBitrateRb.Checked = config.LameTargetBitrate;
lameTargetQualityRb.Checked = !config.LameTargetBitrate;
maxSampleRateCb.SelectedItem = maxSampleRateCb.Items.Cast<SampleRateSelection>().Single(s => s.SampleRate == config.MaxSampleRate);
encoderQualityCb.SelectedItem = config.LameEncoderQuality;
lameDownsampleMonoCbox.Checked = config.LameDownsampleMono;
lameBitrateTb.Value = config.LameBitrate;
lameConstantBitrateCbox.Checked = config.LameConstantBitrate;
@@ -75,6 +97,9 @@ namespace LibationWinForms.Dialogs
config.MoveMoovToBeginning = moveMoovAtomCbox.Checked;
config.LameTargetBitrate = lameTargetBitrateRb.Checked;
config.MaxSampleRate = ((SampleRateSelection)maxSampleRateCb.SelectedItem).SampleRate;
config.LameEncoderQuality = (NAudio.Lame.EncoderQuality)encoderQualityCb.SelectedItem;
encoderQualityCb.SelectedItem = config.LameEncoderQuality;
config.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
config.LameBitrate = lameBitrateTb.Value;
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;

View File

@@ -115,6 +115,10 @@
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
this.maxSampleRateCb = new System.Windows.Forms.ComboBox();
this.encoderQualityCb = new System.Windows.Forms.ComboBox();
this.label20 = new System.Windows.Forms.Label();
this.label21 = new System.Windows.Forms.Label();
this.badBookGb.SuspendLayout();
this.tabControl.SuspendLayout();
this.tab1ImportantSettings.SuspendLayout();
@@ -743,6 +747,10 @@
//
// lameOptionsGb
//
this.lameOptionsGb.Controls.Add(this.label20);
this.lameOptionsGb.Controls.Add(this.label21);
this.lameOptionsGb.Controls.Add(this.encoderQualityCb);
this.lameOptionsGb.Controls.Add(this.maxSampleRateCb);
this.lameOptionsGb.Controls.Add(this.lameDownsampleMonoCbox);
this.lameOptionsGb.Controls.Add(this.lameBitrateGb);
this.lameOptionsGb.Controls.Add(this.label1);
@@ -757,8 +765,7 @@
//
// lameDownsampleMonoCbox
//
this.lameDownsampleMonoCbox.AutoSize = true;
this.lameDownsampleMonoCbox.Location = new System.Drawing.Point(234, 35);
this.lameDownsampleMonoCbox.Location = new System.Drawing.Point(237, 30);
this.lameDownsampleMonoCbox.Name = "lameDownsampleMonoCbox";
this.lameDownsampleMonoCbox.Size = new System.Drawing.Size(184, 34);
this.lameDownsampleMonoCbox.TabIndex = 1;
@@ -776,9 +783,9 @@
this.lameBitrateGb.Controls.Add(this.label11);
this.lameBitrateGb.Controls.Add(this.label3);
this.lameBitrateGb.Controls.Add(this.lameBitrateTb);
this.lameBitrateGb.Location = new System.Drawing.Point(6, 84);
this.lameBitrateGb.Location = new System.Drawing.Point(6, 104);
this.lameBitrateGb.Name = "lameBitrateGb";
this.lameBitrateGb.Size = new System.Drawing.Size(421, 101);
this.lameBitrateGb.Size = new System.Drawing.Size(421, 102);
this.lameBitrateGb.TabIndex = 0;
this.lameBitrateGb.TabStop = false;
this.lameBitrateGb.Text = "Bitrate";
@@ -786,7 +793,7 @@
// LameMatchSourceBRCbox
//
this.LameMatchSourceBRCbox.AutoSize = true;
this.LameMatchSourceBRCbox.Location = new System.Drawing.Point(260, 77);
this.LameMatchSourceBRCbox.Location = new System.Drawing.Point(275, 76);
this.LameMatchSourceBRCbox.Name = "LameMatchSourceBRCbox";
this.LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19);
this.LameMatchSourceBRCbox.TabIndex = 3;
@@ -883,7 +890,7 @@
this.label1.AutoSize = true;
this.label1.Enabled = false;
this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point);
this.label1.Location = new System.Drawing.Point(6, 298);
this.label1.Location = new System.Drawing.Point(6, 325);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(172, 15);
this.label1.TabIndex = 1;
@@ -904,9 +911,9 @@
this.lameQualityGb.Controls.Add(this.label14);
this.lameQualityGb.Controls.Add(this.label2);
this.lameQualityGb.Controls.Add(this.lameVBRQualityTb);
this.lameQualityGb.Location = new System.Drawing.Point(6, 186);
this.lameQualityGb.Location = new System.Drawing.Point(6, 212);
this.lameQualityGb.Name = "lameQualityGb";
this.lameQualityGb.Size = new System.Drawing.Size(421, 109);
this.lameQualityGb.Size = new System.Drawing.Size(421, 103);
this.lameQualityGb.TabIndex = 0;
this.lameQualityGb.TabStop = false;
this.lameQualityGb.Text = "Quality";
@@ -986,7 +993,7 @@
// label13
//
this.label13.AutoSize = true;
this.label13.Location = new System.Drawing.Point(376, 81);
this.label13.Location = new System.Drawing.Point(376, 80);
this.label13.Name = "label13";
this.label13.Size = new System.Drawing.Size(39, 15);
this.label13.TabIndex = 1;
@@ -995,7 +1002,7 @@
// label10
//
this.label10.AutoSize = true;
this.label10.Location = new System.Drawing.Point(6, 81);
this.label10.Location = new System.Drawing.Point(6, 80);
this.label10.Name = "label10";
this.label10.Size = new System.Drawing.Size(43, 15);
this.label10.TabIndex = 1;
@@ -1036,7 +1043,7 @@
this.groupBox2.Controls.Add(this.lameTargetBitrateRb);
this.groupBox2.Location = new System.Drawing.Point(6, 22);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(222, 56);
this.groupBox2.Size = new System.Drawing.Size(214, 47);
this.groupBox2.TabIndex = 0;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Target";
@@ -1044,7 +1051,7 @@
// lameTargetQualityRb
//
this.lameTargetQualityRb.AutoSize = true;
this.lameTargetQualityRb.Location = new System.Drawing.Point(138, 23);
this.lameTargetQualityRb.Location = new System.Drawing.Point(139, 22);
this.lameTargetQualityRb.Name = "lameTargetQualityRb";
this.lameTargetQualityRb.Size = new System.Drawing.Size(63, 19);
this.lameTargetQualityRb.TabIndex = 0;
@@ -1056,7 +1063,7 @@
// lameTargetBitrateRb
//
this.lameTargetBitrateRb.AutoSize = true;
this.lameTargetBitrateRb.Location = new System.Drawing.Point(6, 23);
this.lameTargetBitrateRb.Location = new System.Drawing.Point(6, 22);
this.lameTargetBitrateRb.Name = "lameTargetBitrateRb";
this.lameTargetBitrateRb.Size = new System.Drawing.Size(59, 19);
this.lameTargetBitrateRb.TabIndex = 0;
@@ -1112,6 +1119,42 @@
this.createCueSheetCbox.UseVisualStyleBackColor = true;
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
//
// maxSampleRateCb
//
this.maxSampleRateCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.maxSampleRateCb.FormattingEnabled = true;
this.maxSampleRateCb.Location = new System.Drawing.Point(119, 75);
this.maxSampleRateCb.Name = "maxSampleRateCb";
this.maxSampleRateCb.Size = new System.Drawing.Size(101, 23);
this.maxSampleRateCb.TabIndex = 2;
//
// encoderQualityCb
//
this.encoderQualityCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.encoderQualityCb.FormattingEnabled = true;
this.encoderQualityCb.Location = new System.Drawing.Point(337, 75);
this.encoderQualityCb.Name = "encoderQualityCb";
this.encoderQualityCb.Size = new System.Drawing.Size(90, 23);
this.encoderQualityCb.TabIndex = 2;
//
// label20
//
this.label20.AutoSize = true;
this.label20.Location = new System.Drawing.Point(12, 78);
this.label20.Name = "label20";
this.label20.Size = new System.Drawing.Size(101, 15);
this.label20.TabIndex = 3;
this.label20.Text = "Max Sample Rate:";
//
// label21
//
this.label21.AutoSize = true;
this.label21.Location = new System.Drawing.Point(239, 78);
this.label21.Name = "label21";
this.label21.Size = new System.Drawing.Size(94, 15);
this.label21.TabIndex = 3;
this.label21.Text = "Encoder Quality:";
//
// SettingsDialog
//
this.AcceptButton = this.saveBtn;
@@ -1253,5 +1296,9 @@
private System.Windows.Forms.ComboBox clipsBookmarksFormatCb;
private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox;
private System.Windows.Forms.CheckBox moveMoovAtomCbox;
private System.Windows.Forms.ComboBox encoderQualityCb;
private System.Windows.Forms.ComboBox maxSampleRateCb;
private System.Windows.Forms.Label label21;
private System.Windows.Forms.Label label20;
}
}

View File

@@ -71,7 +71,9 @@
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
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.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.pdfsCountsLbl = new LibationWinForms.FormattableToolStripStatusLabel();
@@ -418,7 +420,9 @@
//
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(40, 40);
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.visibleCountLbl,
this.upgradeLbl,
this.upgradePb,
this.visibleCountLbl,
this.springLbl,
this.backupsCountsLbl,
this.pdfsCountsLbl});
@@ -429,10 +433,21 @@
this.statusStrip1.Size = new System.Drawing.Size(1025, 22);
this.statusStrip1.TabIndex = 6;
this.statusStrip1.Text = "statusStrip1";
//
// visibleCountLbl
//
this.visibleCountLbl.FormatText = "Visible: {0}";
//
// upgradePb
//
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.Size = new System.Drawing.Size(61, 17);
this.visibleCountLbl.Text = "Visible: {0}";
@@ -520,6 +535,7 @@
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged);
this.productsDisplay.LiberateClicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_LiberateClicked);
this.productsDisplay.ConvertToMp3Clicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_ConvertToMp3Clicked);
this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
//
// toggleQueueHideBtn
@@ -670,5 +686,7 @@
private System.Windows.Forms.Button removeBooksBtn;
private System.Windows.Forms.Button doneRemovingBtn;
private System.Windows.Forms.ToolStripMenuItem setPdfDownloadedManualToolStripMenuItem;
}
private System.Windows.Forms.ToolStripProgressBar upgradePb;
private System.Windows.Forms.ToolStripStatusLabel upgradeLbl;
}
}

View File

@@ -56,6 +56,23 @@ namespace LibationWinForms
}
}
private void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook libraryBook)
{
try
{
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated)
{
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
SetQueueCollapseState(false);
processBookQueue1.AddConvertMp3(libraryBook);
}
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
}
}
private void SetQueueCollapseState(bool collapsed)
{
if (collapsed && !splitContainer1.Panel2Collapsed)

View File

@@ -17,11 +17,8 @@ namespace LibationWinForms
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
var hours = 0;
var minutes = 5;
var seconds = 0;
var _5_minutes = new TimeSpan(hours, minutes, seconds);
autoScanTimer = new InterruptableTimer(_5_minutes);
autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
// 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 (_, __) =>
@@ -50,7 +47,7 @@ namespace LibationWinForms
// load init state to menu checkbox
Load += updateAutoScanLibraryToolStripMenuItem;
// if enabled: begin on load
Load += startAutoScan;
Shown += startAutoScan;
// if new 'default' account is added, run autoscan
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_ProcessQueue();
Configure_Filter();
Configure_Upgrade();
// misc which belongs in winforms app but doesn't have a UI element
Configure_NonUI();

View File

@@ -41,6 +41,7 @@
this.productsGrid.TabIndex = 0;
this.productsGrid.VisibleCountChanged += new System.EventHandler<int>(this.productsGrid_VisibleCountChanged);
this.productsGrid.LiberateClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_LiberateClicked);
this.productsGrid.ConvertToMp3Clicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_ConvertToMp3Clicked);
this.productsGrid.CoverClicked += new LibationWinForms.GridView.GridEntryClickedEventHandler(this.productsGrid_CoverClicked);
this.productsGrid.DetailsClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_DetailsClicked);
this.productsGrid.DescriptionClicked += new LibationWinForms.GridView.GridEntryRectangleClickedEventHandler(this.productsGrid_DescriptionClicked);

View File

@@ -19,6 +19,7 @@ namespace LibationWinForms.GridView
public event EventHandler<int> VisibleCountChanged;
public event EventHandler<int> RemovableCountChanged;
public event EventHandler<LibraryBook> LiberateClicked;
public event EventHandler<LibraryBook> ConvertToMp3Clicked;
public event EventHandler InitialLoaded;
private bool hasBeenDisplayed;
@@ -204,6 +205,12 @@ namespace LibationWinForms.GridView
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
}
private void productsGrid_ConvertToMp3Clicked(LibraryBookEntry liveGridEntry)
{
if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error)
ConvertToMp3Clicked?.Invoke(this, liveGridEntry.LibraryBook);
}
private void productsGrid_RemovableCountChanged(object sender, EventArgs e)
{
RemovableCountChanged?.Invoke(sender, productsGrid.GetAllBookEntries().Count(lbe => lbe.Remove is RemoveStatus.Removed));

View File

@@ -22,6 +22,7 @@ namespace LibationWinForms.GridView
/// <summary>Number of visible rows has changed</summary>
public event EventHandler<int> VisibleCountChanged;
public event LibraryBookEntryClickedEventHandler LiberateClicked;
public event LibraryBookEntryClickedEventHandler ConvertToMp3Clicked;
public event GridEntryClickedEventHandler CoverClicked;
public event LibraryBookEntryClickedEventHandler DetailsClicked;
public event GridEntryRectangleClickedEventHandler DescriptionClicked;
@@ -176,6 +177,13 @@ namespace LibationWinForms.GridView
}
};
var convertToMp3MenuItem = new ToolStripMenuItem
{
Text = "&Convert to Mp3",
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated
};
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(entry as LibraryBookEntry);
var bookRecordMenuItem = new ToolStripMenuItem { Text = "View &Bookmarks/Clips" };
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
@@ -184,6 +192,7 @@ namespace LibationWinForms.GridView
stopLightContextMenu.Items.Add(setNotDownloadMenuItem);
stopLightContextMenu.Items.Add(removeMenuItem);
stopLightContextMenu.Items.Add(locateFileMenuItem);
stopLightContextMenu.Items.Add(convertToMp3MenuItem);
stopLightContextMenu.Items.Add(new ToolStripSeparator());
stopLightContextMenu.Items.Add(bookRecordMenuItem);

View File

@@ -13,19 +13,12 @@
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<StartupObject />
<IsPublishable>true</IsPublishable>
<!-- Version is now in AppScaffolding.csproj -->
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -44,7 +37,6 @@
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.6" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" />
</ItemGroup>

View File

@@ -51,9 +51,6 @@ namespace LibationWinForms
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
#if !DEBUG
checkForUpdate();
#endif
// logging is init'd here
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
}
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()
{
// 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>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -31,7 +25,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
<ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -23,12 +23,12 @@ namespace LinuxConfigApp
public void SetFolderIcon(string image, 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.
public bool CanUpdate => Directory.Exists("/usr/lib/libation");
public void InstallUpdate(string updateBundle)
public bool CanUpgrade => Directory.Exists("/usr/lib/libation");
public void InstallUpgrade(string upgradeBundle)
{
RunAsRoot("apt", $"install '{updateBundle}'");
RunAsRoot("apt", $"install '{upgradeBundle}'");
}
public Process RunAsRoot(string exe, string args)

View File

@@ -11,13 +11,7 @@
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -31,7 +25,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
<ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,15 +13,15 @@ namespace MacOSConfigApp
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
//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
public bool CanUpdate => Directory.Exists(AppPath);
//the running process, so don't upgrade unless it's "installed" in /Applications
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
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
//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 System.Collections.Generic;
using SixLabors.ImageSharp;
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 System.IO;
using System;
using Dinah.Core;
namespace WindowsConfigApp
{
@@ -21,11 +18,11 @@ namespace WindowsConfigApp
try
{
var icon = ImageReader.ToIcon(image);
var icon = Image.Load(File.ReadAllBytes(image)).ToIcon();
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
{
@@ -36,8 +33,9 @@ namespace WindowsConfigApp
public void DeleteFolderIcon(string directory)
=> 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 thisDir = Path.GetDirectoryName(thisExe);
@@ -45,7 +43,10 @@ namespace WindowsConfigApp
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)

View File

@@ -2,24 +2,15 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup>
<!--
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>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -33,11 +24,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
<ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />