Compare commits

..
91 Commits
Author SHA1 Message Date
Robert McRackan d19fe2250c Add accessibility text to grid stoplight buttons 2024-05-06 21:56:00 -04:00
Robert McRackan d16d0c8de2 Merge branch 'master' of https://github.com/rmcrackan/Libation 2024-05-05 16:24:07 -04:00
Robert McRackan c213d5d9f6 Attempt to solve networking issue by disabling ipv6 2024-05-05 16:24:00 -04:00
rmcrackan c73a023572 Update FrequentlyAskedQuestions.md 2024-04-30 22:42:34 -04:00
rmcrackan 67389917fd Merge pull request #879 from rmcrackan/dependabot/nuget/Source/LibationUiBase/SixLabors.ImageSharp-3.1.4
Bump SixLabors.ImageSharp from 3.1.3 to 3.1.4 in /Source/LibationUiBase
2024-04-15 17:03:13 -04:00
dependabot[bot] b3264d5f42 Bump SixLabors.ImageSharp from 3.1.3 to 3.1.4 in /Source/LibationUiBase
Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/SixLabors/ImageSharp/releases)
- [Commits](https://github.com/SixLabors/ImageSharp/compare/v3.1.3...v3.1.4)

---
updated-dependencies:
- dependency-name: SixLabors.ImageSharp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 20:27:24 +00:00
Robert McRackan 962d9b550f "Unable to load module" should just be a warning, not error 2024-04-15 08:03:49 -04:00
Robert McRackan 91ce7272ae Bug fix #874 : Chardonnay wasn't allowing custom directories ending in "Books" 2024-04-04 08:13:37 -04:00
rmcrackan 2f64ca6856 Merge pull request #870 from ScubyG/patch-1
Update InstallOnLinux.md
2024-03-22 07:37:00 -04:00
Davy Jones cfe5db436c Update InstallOnLinux.md
Fixed typo
2024-03-22 01:23:52 +00:00
rmcrackan 3653fc8094 Merge pull request #863 from rmcrackan/dependabot/github_actions/softprops/action-gh-release-2
Bump softprops/action-gh-release from 1 to 2
2024-03-11 10:45:44 -04:00
dependabot[bot] 662c0ec871 Bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 14:20:12 +00:00
rmcrackan 44d39eabdb Merge pull request #861 from rmcrackan/dependabot/nuget/Source/LibationUiBase/SixLabors.ImageSharp-3.1.3
Bump SixLabors.ImageSharp from 3.1.2 to 3.1.3 in /Source/LibationUiBase
2024-03-05 11:45:59 -05:00
dependabot[bot] a0550d5c97 Bump SixLabors.ImageSharp from 3.1.2 to 3.1.3 in /Source/LibationUiBase
Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/SixLabors/ImageSharp/releases)
- [Commits](https://github.com/SixLabors/ImageSharp/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: SixLabors.ImageSharp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 16:38:00 +00:00
Robert McRackan 14eaca6d45 Update dependency. Security update 2024-03-05 11:36:36 -05:00
Robert McRackan 93ccc206ef Update dependencies 2024-03-04 14:02:42 -05:00
Robert McRackan d3f0fd711e Remove validation check: SeriesName is null . At least 1 exception found in the wild 2024-02-29 14:44:41 -05:00
rmcrackan 3f4604e877 Update FrequentlyAskedQuestions.md
reformat
2024-02-29 10:15:47 -05:00
rmcrackan c316709af8 Update FrequentlyAskedQuestions.md
Classic vs Chardonnay
2024-02-29 10:15:01 -05:00
Robert McRackan 221d5c7f1c paypal link 2024-02-19 10:32:26 -05:00
Robert McRackan 5a86a1a27b Typo 2024-02-13 21:42:24 -05:00
rmcrackan dc5e55de68 Merge pull request #848 from patienttruth/master
Update Docker.md
2024-02-13 07:22:33 -05:00
patienttruth ee37864a42 Update Docker.md
Included an explanation that you may have to manually input the InProgress variable.
2024-02-13 06:49:33 +00:00
Robert McRackan efe347667c Better error reporting in audible api -- incl. inner exceptions 2024-01-20 21:44:29 -05:00
Robert McRackan f27a18bdbb Better error reporting in audible api 2024-01-19 09:30:48 -05:00
Robert McRackan d1834659d9 incr ver 2024-01-12 09:08:14 -05:00
Robert McRackan 7842b521d7 Update dependencies. Notes about upgrading 2024-01-12 07:44:40 -05:00
Robert McRackan 0822f0229d Revert Avalonia. Bug in 11.0.6 2024-01-04 10:03:33 -05:00
Robert McRackan 26aee4d29d Merge branch 'master' of https://github.com/rmcrackan/Libation 2024-01-03 11:43:59 -05:00
Robert McRackan 17a80a23a8 AAXClean upgrade to .net8 2024-01-03 11:43:45 -05:00
rmcrackan e26fc9ca62 Merge pull request #808 from rmcrackan/dependabot/github_actions/actions/download-artifact-4
Bump actions/download-artifact from 3 to 4
2023-12-15 09:50:25 -05:00
rmcrackan a03ccf1143 Merge pull request #807 from rmcrackan/dependabot/github_actions/actions/upload-artifact-4
Bump actions/upload-artifact from 3 to 4
2023-12-15 09:50:11 -05:00
dependabot[bot] bb8dd615db Bump actions/download-artifact from 3 to 4
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-15 14:08:26 +00:00
dependabot[bot] 9022a2889f Bump actions/upload-artifact from 3 to 4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-15 14:08:20 +00:00
rmcrackan ef049a3b02 Merge pull request #805 from rmcrackan/dependabot/github_actions/actions/setup-dotnet-4
Bump actions/setup-dotnet from 3 to 4
2023-12-05 10:01:34 -05:00
dependabot[bot] 77409750aa Bump actions/setup-dotnet from 3 to 4
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 14:14:17 +00:00
Robert McRackan 1702130b01 Upgrade to .net8 2023-11-15 19:57:39 -05:00
Robert McRackan b6d1a7e3ba Upgrade to .net8 2023-11-15 19:53:26 -05:00
Robert McRackan 2907ba5c13 Add FAQ 2023-10-20 08:27:20 -04:00
Robert McRackan 6df6c79ac8 New locale: Brazil 2023-10-18 22:33:24 -04:00
rmcrackan 3a9ca5d827 Merge pull request #779 from rmcrackan/dependabot/github_actions/dwenegar/upload-release-assets-2
Bump dwenegar/upload-release-assets from 1 to 2
2023-10-13 14:41:26 -04:00
dependabot[bot] e1e663e327 Bump dwenegar/upload-release-assets from 1 to 2
Bumps [dwenegar/upload-release-assets](https://github.com/dwenegar/upload-release-assets) from 1 to 2.
- [Release notes](https://github.com/dwenegar/upload-release-assets/releases)
- [Commits](https://github.com/dwenegar/upload-release-assets/compare/v1...v2)

---
updated-dependencies:
- dependency-name: dwenegar/upload-release-assets
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-13 14:37:55 +00:00
Robert McRackan 4b00d5fd84 incr ver 2023-09-18 13:58:23 -04:00
rmcrackan 02dbf8aad0 Merge pull request #751 from Mbucari/master
Minor Bug Fixes
2023-09-18 13:56:53 -04:00
rmcrackan 8326389f5c Merge pull request #732 from rmcrackan/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-18 13:56:33 -04:00
dependabot[bot] 34535b3ce1 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 17:33:43 +00:00
rmcrackan 7e5366ab95 Merge pull request #738 from rmcrackan/dependabot/github_actions/docker/setup-buildx-action-3
Bump docker/setup-buildx-action from 2 to 3
2023-09-18 13:30:24 -04:00
dependabot[bot] 690de9bc5c Bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 17:25:20 +00:00
rmcrackan c976aa2bb2 Merge pull request #739 from rmcrackan/dependabot/github_actions/docker/login-action-3
Bump docker/login-action from 2 to 3
2023-09-18 13:25:07 -04:00
rmcrackan 27f659285d Merge pull request #740 from rmcrackan/dependabot/github_actions/docker/build-push-action-5
Bump docker/build-push-action from 4 to 5
2023-09-18 13:24:55 -04:00
rmcrackan 423a5e7720 Merge pull request #741 from rmcrackan/dependabot/github_actions/docker/setup-qemu-action-3
Bump docker/setup-qemu-action from 2 to 3
2023-09-18 13:24:44 -04:00
Michael Bucari-Tovo 9152e12fe1 Fix #748 2023-09-18 10:14:54 -06:00
Michael Bucari-Tovo f471c53139 Fix #734 2023-09-18 10:08:15 -06:00
dependabot[bot] 66d055bb90 Bump docker/setup-qemu-action from 2 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 14:50:49 +00:00
dependabot[bot] 2bbb35363a Bump docker/build-push-action from 4 to 5
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 14:50:46 +00:00
dependabot[bot] 1d3687cf9e Bump docker/login-action from 2 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 14:50:42 +00:00
Mbucari daf925157f Update AppScaffolding.csproj 2023-09-02 07:59:30 -06:00
Mbucari 40eec9e674 Merge pull request #730 from Mbucari/master
Fix #729
2023-09-01 18:46:42 -06:00
MBucari 6f0782053e Restore PDF functionality (#729) 2023-09-01 16:30:35 -06:00
Michael Bucari-Tovo 04ad033ba0 Remove appsettings.json from program files dir 2023-08-31 09:36:38 -06:00
Robert McRackan f12f8ba3ee incr ver 2023-08-30 15:00:20 -04:00
rmcrackan cc6feb21ff Merge pull request #728 from Mbucari/master
Minor Bug fixes
2023-08-30 14:46:16 -04:00
Michael Bucari-Tovo c4f2ec428d Allow Settings form resizing 2023-08-30 12:38:28 -06:00
Michael Bucari-Tovo 59689cb647 Update Dependencies 2023-08-30 11:59:40 -06:00
Michael Bucari-Tovo d7f3758ebc Add support for environment variables and unix shortcuts in appsettings.json path. 2023-08-30 11:47:50 -06:00
Mbucari 49775b019c Update InstallOnMac.md 2023-08-30 09:06:57 -06:00
Michael Bucari-Tovo e55e969349 Fix issue with save button being disabled. 2023-08-30 08:59:20 -06:00
Mbucari 42a93bfac1 Update AAXClean 2023-08-27 19:47:32 -06:00
Mbucari f86c77a546 Create directory before trying to create appsettings.json 2023-08-27 19:47:00 -06:00
MBucari 88c35e2a56 Add scrolling view to settings window panels (#720) 2023-08-23 20:25:30 -06:00
MBucari b405e8b6b2 Remove unised response groups 2023-08-20 11:40:24 -06:00
MBucari 92d283187d Add support for locating mp3 audiobooks 2023-08-20 11:38:43 -06:00
Robert McRackan 51b8cfe71f incr ver 2023-08-17 14:03:40 -04:00
rmcrackan c80da5357b Merge pull request #716 from Mbucari/master
Fix NRE when item has no category ladders (#715)
2023-08-17 14:02:43 -04:00
Michael Bucari-Tovo 736d7c4a5f Fix NRE when item has no category ladders (#715) 2023-08-17 11:55:28 -06:00
Robert McRackan f175b7592e incr major ver 2023-08-12 21:45:11 -04:00
rmcrackan 415e6e7bc6 Merge pull request #710 from Mbucari/master
Batch of Fixxed Issues
2023-08-12 21:20:37 -04:00
MBucari d6a413e8d9 Update Avalonia to 11.0.3 2023-08-12 17:30:08 -06:00
Mbucari 3049de6246 Merge branch 'rmcrackan:master' into master 2023-08-12 17:20:09 -06:00
MBucari fb9b4eb77e Update audio duration on library scan (#707) 2023-08-12 17:19:56 -06:00
MBucari e65b6c76a8 Add ability to preview templates on user's books (#700)
Add template editor menu items to main grid context menu
2023-08-12 17:12:50 -06:00
Mbucari 167a021eb1 Change glass icon shading 2023-08-11 10:55:31 -06:00
Mbucari ff3ac2d6fd Fix MessageBox crash when UI has no Screens (#708) 2023-08-11 09:04:05 -06:00
Robert McRackan f733079a49 remove Moq 2023-08-10 15:24:43 -04:00
Mbucari 893d68190d Lazy load cover to improve startup time 2023-08-10 10:18:28 -06:00
Mbucari 97f94d8782 Add custom column widths to chardonnay 2023-08-07 15:54:50 -06:00
Mbucari 4b2ce0c2d1 Brighter stoplight colors (#702) 2023-08-07 11:31:22 -06:00
Mbucari ee00417c6f Add white glow to libation_glass.svg (#701) 2023-08-07 11:31:03 -06:00
Robert McRackan 768afd8ecd incr ver 2023-08-04 11:57:15 -04:00
rmcrackan 32c3fa85ce Merge pull request #699 from Mbucari/master
Fix broken template editor (#698)
2023-08-04 11:55:30 -04:00
Mbucari 6986c8f018 Fix broken template editor (#698) 2023-08-04 09:26:51 -06:00
96 changed files with 1035 additions and 598 deletions

No files matched your search

+4 -4
View File
@@ -33,7 +33,7 @@ on:
env: env:
DOTNET_CONFIGURATION: 'Release' DOTNET_CONFIGURATION: 'Release'
DOTNET_VERSION: '7.0.x' DOTNET_VERSION: '8.0.x'
RELEASE_NAME: 'chardonnay' RELEASE_NAME: 'chardonnay'
jobs: jobs:
@@ -41,9 +41,9 @@ jobs:
name: '${{ inputs.OS }}-${{ inputs.architecture }}' name: '${{ inputs.OS }}-${{ inputs.architecture }}'
runs-on: ${{ inputs.runs_on }} runs-on: ${{ inputs.runs_on }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v4
with: with:
dotnet-version: ${{ env.DOTNET_VERSION }} dotnet-version: ${{ env.DOTNET_VERSION }}
env: env:
@@ -124,7 +124,7 @@ jobs:
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}" echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"
- name: Publish bundle - name: Publish bundle
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: ${{ steps.bundle.outputs.artifact }} name: ${{ steps.bundle.outputs.artifact }}
path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }} path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }}
+4 -4
View File
@@ -18,7 +18,7 @@ on:
env: env:
DOTNET_CONFIGURATION: 'Release' DOTNET_CONFIGURATION: 'Release'
DOTNET_VERSION: '7.0.x' DOTNET_VERSION: '8.0.x'
jobs: jobs:
build: build:
@@ -35,9 +35,9 @@ jobs:
release_name: classic release_name: classic
prefix: Classic- prefix: Classic-
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v4
with: with:
dotnet-version: ${{ env.DOTNET_VERSION }} dotnet-version: ${{ env.DOTNET_VERSION }}
env: env:
@@ -107,7 +107,7 @@ jobs:
Compress-Archive -Path "${bin_dir}*" -DestinationPath "$artifact.zip" Compress-Archive -Path "${bin_dir}*" -DestinationPath "$artifact.zip"
- name: Publish artifact - name: Publish artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: ${{ steps.zip.outputs.artifact }}.zip name: ${{ steps.zip.outputs.artifact }}.zip
path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip
+5 -5
View File
@@ -24,22 +24,22 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.docker_username }} username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_token }} password: ${{ secrets.docker_token }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5
with: with:
push: true push: true
build-args: 'FOLDER_NAME=Linux-chardonnay' build-args: 'FOLDER_NAME=Linux-chardonnay'
+3 -3
View File
@@ -39,13 +39,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
path: artifacts path: artifacts
- name: Release - name: Release
id: release id: release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
name: Libation v${{ needs.prerelease.outputs.version }} name: Libation v${{ needs.prerelease.outputs.version }}
body: <Put a body here> body: <Put a body here>
@@ -53,7 +53,7 @@ jobs:
prerelease: false prerelease: false
- name: Upload release assets - name: Upload release assets
uses: dwenegar/upload-release-assets@v1 uses: dwenegar/upload-release-assets@v2
env: env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
with: with:
+1 -1
View File
@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
+12 -3
View File
@@ -1,10 +1,11 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
### Disclaimer
# Disclaimer
The docker image is provided as-is. We hope it can be useful to you but it is not officially supported. The docker image is provided as-is. We hope it can be useful to you but it is not officially supported.
### Setup ### Setup
@@ -12,7 +13,15 @@ In order to use the docker image, you'll need to provide it with a copy of the `
In Settings.json, make the following changes: In Settings.json, make the following changes:
* Change `Books` to `/data` * Change `Books` to `/data`
* Change `InProgress` to `/tmp` * Change `InProgress` to `/tmp` *
*You may have to paste the following at the end of your your Settings.json file if `InProgess` is not present:
```
"InProgress": "/tmp"
```
![image](https://github.com/patienttruth/Libation/assets/105557996/cf65a108-cadf-4284-9000-e7672c99c59b)
### Running ### Running
Once the configuration files are copied and edited, the docker image can be run with the following command. Once the configuration files are copied and edited, the docker image can be run with the following command.
+31
View File
@@ -0,0 +1,31 @@
## [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 [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**.
# Frequently Asked Questions
## Q: What's the difference between 'Classic' and 'Chardonnay'?
**A:** First and most importantly: Classic and Chardonnay have the exact same features.
* **Classic** is Windows only. Its older 'grey boxes' look has a compact design which allows for more information on the screen. Notably, Classic was written using an older, more mature technology which has built-in support for screenreaders.
* **Chardonnay** is available for Windows, Mac, and Linux. Its modern design has a more open look and feel.
## Q: Now that I've downloaded my books, how can I listen to them?
**A:** You can use any app which plays m4b files (or mp3 files if you used that setting). Here are just a few ideas. Disclaimer: I have no affiliation with any of these companies:
* iOS: [BookPlayer](https://apps.apple.com/us/app/bookplayer/id1138219998)
* iOS: [Bound](https://apps.apple.com/us/app/bound-audiobook-player/id1041727137)
* Android: [Smart AudioBook Player](https://play.google.com/store/apps/details?id=ak.alizandro.smartaudiobookplayer&hl=en_US&gl=US)
* Desktop: [VLC](https://www.videolan.org/)
## Q: How do I use Libation with a South Africa account?
**A:** Like many countries, amazon gives South Africa it's own amazon site. [Unlike many other regions](https://www.audible.com/ep/country-selector) there is not South Africa specific audible site. Use `US` for your region -- ie: audible.com.
(Not exactly a *frequently* asked question but it's come up more than once)
+1 -1
View File
@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
+4 -3
View File
@@ -1,15 +1,16 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
### Install and Run Libation on Ubuntu
# Install and Run Libation on Ubuntu
New Libation releases are automatically packed into .deb and .rpm package and are available from the Libation repository's releases page. New Libation releases are automatically packed into .deb and .rpm package and are available from the Libation repository's releases page.
Run this command in your terminal to dowbnload and install Libation, replacing the url with the latest Libation package url: Run this command in your terminal to download and install Libation, replacing the url with the latest Libation package url:
- Debian - Debian
```Console ```Console
+13 -1
View File
@@ -1,9 +1,10 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
# Run Libation on MacOS # Run Libation on MacOS
This walkthrough should get you up and running with Libation on your Mac. This walkthrough should get you up and running with Libation on your Mac.
@@ -23,6 +24,17 @@ This walkthrough should get you up and running with Libation on your Mac.
``` ```
- Close the terminal and use Libation! - Close the terminal and use Libation!
## Troubleshooting
If Libation fails to start after completing the above steps, try the following:
1. Right-click the Libation app in your applications folder and select _Show Package Contents_
2. Open the `Contents` folder and then the `MacOS` folder.
3. Find the file named `Libation`, right-click it, and then select _Open_.
Libation _should_ launch, and you should now be able to open Libation by just double-clicking the app bundle in your applications folder.
## Running Hangover ## Running Hangover
Libation comes with a recovery app called Hangover. You can start it by running this command: Libation comes with a recovery app called Hangover. You can start it by running this command:
+7
View File
@@ -1,3 +1,10 @@
## [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 [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**.
# Naming Templates # Naming Templates
File and Folder names can be customized using Libation's built-in tag template naming engine. To edit how folder and file names are created, go to Settings \> Download/Decrypt and edit the naming templates. If you're splitting your audiobook into multiple files by chapter, you can also use a custom template to set each chapter's title metadata tag by editing the template in Settings \> Audio File Options. File and Folder names can be customized using Libation's built-in tag template naming engine. To edit how folder and file names are created, go to Settings \> Download/Decrypt and edit the naming templates. If you're splitting your audiobook into multiple files by chapter, you can also use a custom template to set each chapter's title metadata tag by editing the template in Settings \> Audio File Options.
+1 -1
View File
@@ -1,6 +1,6 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
+38 -27
View File
@@ -1,28 +1,39 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" enable-background="new 0 0 512 512"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 524 524" enable-background="new 0 0 524 524">
<path id="glass" d= <defs>
"M139,2 <g id="glass">
A 192,200 0 0 0 103,84 <path fill-rule="evenodd" d=
A 222,334 41 0 0 241,320 "M262,8
V478 h-117
H160 a 192,200 0 0 0 -36,82
A 16,16 0 0 0 160,510 a 222,334 41 0 0 138,236
H352 v158
A16 16 0 0 0 352,478 h-81
H271 a 16,16 0 0 0 0,32
V320 h192
A 222,334 -41 0 0 409,84 a 16 16 0 0 0 0,-32
A 192,200 0 0 0 373,2 h-81
M355,32 v-158
A 192,200 0 0 1 381,127 a 222,334 -41 0 0 138,-236
A 187.5,334 -35 0 1 256,286 a 192,200 0 0 0 -36,-82
A 187.5,334 35 0 1 131,127 h-117
A 192,200 0 0 1 157,32 m-99,30
H355 a 192,200 0 0 0 -26,95
z" /> a 187.5,334 35 0 0 125,159
a 187.5,334 -35 0 0 125,-159
<path id="wine-level" d= a 192,200 0 0 0 -26,-95
"M146,128 h-198
A 168,300 35 0 0 256,270 z"/>
A 168,300 -35 0 0 366,128 </g>
z"/> <g id="wine-level">
<path d=
"M158,136
a 168,305 35 0 0 104,136
a 168,305 -35 0 0 104,-136
z"/>
</g>
</defs>
<use href="#glass" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#wine-level" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#glass" fill="Black" />
<use href="#wine-level" fill="Black" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 968 B

+4 -1
View File
@@ -2,9 +2,11 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest) ## [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 [PayPal.me](https://paypal.me/MBucari?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**. ...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
# Table of Contents # Table of Contents
- [Audible audiobook manager](#audible-audiobook-manager) - [Audible audiobook manager](#audible-audiobook-manager)
@@ -31,6 +33,7 @@
- [Custom File Naming](Documentation/NamingTemplates.md) - [Custom File Naming](Documentation/NamingTemplates.md)
- [Command Line Interface](Documentation/Advanced.md#command-line-interface) - [Command Line Interface](Documentation/Advanced.md#command-line-interface)
- [Docker](Documentation/Docker.md) - [Docker](Documentation/Docker.md)
- [Frequently Asked Questions](Documentation/FrequentlyAskedQuestions.md)
## Getting started ## Getting started
-3
View File
@@ -109,9 +109,6 @@ ln -s /usr/lib/libation/LibationCli /usr/bin/libationcli
if ! grep -q 'fs.inotify.max_user_instances=524288' /etc/sysctl.conf; then if ! grep -q 'fs.inotify.max_user_instances=524288' /etc/sysctl.conf; then
echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p
fi fi
# workaround until this file is moved to the user's home directory
touch /usr/lib/libation/appsettings.json
chmod 666 /usr/lib/libation/appsettings.json
" >> $FOLDER_DEBIAN/postinst " >> $FOLDER_DEBIAN/postinst
echo "Creating control file..." echo "Creating control file..."
-2
View File
@@ -94,8 +94,6 @@ install * %{buildroot}%{_libdir}/%{name}/
if [ \$1 -eq 1 ] ; then if [ \$1 -eq 1 ] ; then
# Initial installation # Initial installation
touch %{_libdir}/%{name}/appsettings.json
chmod 666 %{_libdir}/%{name}/appsettings.json
ln -s %{_libdir}/%{name}/Libation %{_bindir}/libation ln -s %{_libdir}/%{name}/Libation %{_bindir}/libation
ln -s %{_libdir}/%{name}/Hangover %{_bindir}/hangover ln -s %{_libdir}/%{name}/Hangover %{_bindir}/hangover
+2 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -13,7 +13,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="1.1.0" /> <PackageReference Include="AAXClean.Codecs" Version="1.1.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+5 -5
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Version>10.6.4.1</Version> <Version>11.3.9.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Octokit" Version="7.1.0" /> <PackageReference Include="Octokit" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.ZipFile" Version="1.0.1" /> <PackageReference Include="Serilog.Sinks.ZipFile" Version="3.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" /> <ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
@@ -19,4 +19,4 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -254,6 +254,8 @@ namespace AppScaffolding
AudibleFileStorage.DecryptInProgressDirectory, AudibleFileStorage.DecryptInProgressDirectory,
DecryptInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DecryptInProgressDirectory).Count(), DecryptInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DecryptInProgressDirectory).Count(),
disableIPv6 = AppContext.TryGetSwitch("System.Net.DisableIPv6", out bool disableIPv6Value),
}); });
if (InteropFactory.InteropFunctionsType is null) if (InteropFactory.InteropFunctionsType is null)
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" /> <PackageReference Include="CsvHelper" Version="31.0.2" />
<PackageReference Include="NPOI" Version="2.6.1" /> <PackageReference Include="NPOI" Version="2.6.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -119,9 +119,15 @@ namespace ApplicationServices
logTime($"pre {nameof(scanAccountsAsync)} all"); logTime($"pre {nameof(scanAccountsAsync)} all");
var libraryOptions = new LibraryOptions var libraryOptions = new LibraryOptions
{ {
ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS, ResponseGroups
ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215 = LibraryOptions.ResponseGroupOptions.Rating | LibraryOptions.ResponseGroupOptions.Media
| LibraryOptions.ResponseGroupOptions.Relationships | LibraryOptions.ResponseGroupOptions.ProductDesc
| LibraryOptions.ResponseGroupOptions.Contributors | LibraryOptions.ResponseGroupOptions.ProvidedReview
| LibraryOptions.ResponseGroupOptions.ProductPlans | LibraryOptions.ResponseGroupOptions.Series
| LibraryOptions.ResponseGroupOptions.CategoryLadders | LibraryOptions.ResponseGroupOptions.ProductExtendedAttrs
| LibraryOptions.ResponseGroupOptions.PdfUrl | LibraryOptions.ResponseGroupOptions.OriginAsin,
ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215
}; };
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions); var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
logTime($"post {nameof(scanAccountsAsync)} all"); logTime($"post {nameof(scanAccountsAsync)} all");
+5 -1
View File
@@ -207,7 +207,11 @@ namespace AudibleUtilities
try try
{ {
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var items = await Api.GetCatalogProductsAsync(asins, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS); var items = await Api.GetCatalogProductsAsync(asins, CatalogOptions.ResponseGroupOptions.Rating | CatalogOptions.ResponseGroupOptions.Media
| CatalogOptions.ResponseGroupOptions.Relationships | CatalogOptions.ResponseGroupOptions.ProductDesc
| CatalogOptions.ResponseGroupOptions.Contributors | CatalogOptions.ResponseGroupOptions.ProvidedReview
| CatalogOptions.ResponseGroupOptions.ProductPlans | CatalogOptions.ResponseGroupOptions.Series
| CatalogOptions.ResponseGroupOptions.CategoryLadders | CatalogOptions.ResponseGroupOptions.ProductExtendedAttrs);
sw.Stop(); sw.Stop();
Serilog.Log.Logger.Debug($"Batch {batchNum} End: Retrieved {items.Count} items in {sw.ElapsedMilliseconds} ms"); Serilog.Log.Logger.Debug($"Batch {batchNum} End: Retrieved {items.Count} items in {sw.ElapsedMilliseconds} ms");
@@ -90,8 +90,10 @@ namespace AudibleUtilities
var distinct = items.GetSeriesDistinct(); var distinct = items.GetSeriesDistinct();
if (distinct.Any(s => s.SeriesId is null)) if (distinct.Any(s => s.SeriesId is null))
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesId)}", nameof(items))); exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesId)}", nameof(items)));
if (distinct.Any(s => s.SeriesName is null))
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesName)}", nameof(items))); //// unfortunately, a user has a series with no name
//if (distinct.Any(s => s.SeriesName is null))
// exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(Series.SeriesName)}", nameof(items)));
return exceptions; return exceptions;
} }
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AudibleApi" Version="8.4.3.1" /> <PackageReference Include="AudibleApi" Version="9.1.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+6 -6
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
@@ -10,14 +10,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.3.0.1" /> <PackageReference Include="Dinah.Core" Version="8.0.0.1" />
<PackageReference Include="Dinah.EntityFrameworkCore" Version="7.3.0.1" /> <PackageReference Include="Dinah.EntityFrameworkCore" Version="8.0.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.9"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.9"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
+6 -2
View File
@@ -106,6 +106,7 @@ namespace DataLayer
ReplaceAuthors(authors); ReplaceAuthors(authors);
ReplaceNarrators(narrators); ReplaceNarrators(narrators);
} }
public void UpdateTitle(string title, string subtitle) public void UpdateTitle(string title, string subtitle)
{ {
Title = title?.Trim() ?? ""; Title = title?.Trim() ?? "";
@@ -113,8 +114,11 @@ namespace DataLayer
_titleWithSubtitle = null; _titleWithSubtitle = null;
} }
#region contributors, authors, narrators public void UpdateLengthInMinutes(int lengthInMinutes)
internal HashSet<BookContributor> ContributorsLink { get; private set; } => LengthInMinutes = lengthInMinutes;
#region contributors, authors, narrators
internal HashSet<BookContributor> ContributorsLink { get; private set; }
public IEnumerable<Contributor> Authors => ContributorsLink.ByRole(Role.Author).Select(bc => bc.Contributor).ToList(); public IEnumerable<Contributor> Authors => ContributorsLink.ByRole(Role.Author).Select(bc => bc.Contributor).ToList();
public IEnumerable<Contributor> Narrators => ContributorsLink.ByRole(Role.Narrator).Select(bc => bc.Contributor).ToList(); public IEnumerable<Contributor> Narrators => ContributorsLink.ByRole(Role.Narrator).Select(bc => bc.Contributor).ToList();
@@ -149,6 +149,8 @@ namespace DtoImporterService
{ {
var item = importItem.DtoItem; var item = importItem.DtoItem;
book.UpdateLengthInMinutes(item.LengthInMinutes);
// Update the book titles, since formatting can change // Update the book titles, since formatting can change
book.UpdateTitle(item.Title, item.Subtitle); book.UpdateTitle(item.Title, item.Subtitle);
@@ -23,9 +23,11 @@ namespace DtoImporterService
loadLocal_categories(); loadLocal_categories();
// upsert // upsert
//Import item may not have no (null) categories
var categoryLadders = importItems var categoryLadders = importItems
.Where(i => i.DtoItem.CategoryLadders is not null)
.SelectMany(i => i.DtoItem.CategoryLadders) .SelectMany(i => i.DtoItem.CategoryLadders)
.Select(cl => cl.Ladder) .Select(cl => cl?.Ladder)
.Where(l => l?.Length > 0) .Where(l => l?.Length > 0)
.ToList(); .ToList();
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+2 -1
View File
@@ -21,7 +21,8 @@ namespace FileLiberator
var series = libraryBook.Book.SeriesLink.SingleOrDefault(); var series = libraryBook.Book.SeriesLink.SingleOrDefault();
if (series is not null) if (series is not null)
{ {
var seriesParent = ApplicationServices.DbContexts.GetContext().GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId); using var context = ApplicationServices.DbContexts.GetContext();
var seriesParent = context.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
if (seriesParent is not null) if (seriesParent is not null)
{ {
+5
View File
@@ -60,6 +60,11 @@ namespace FileLiberator
config.LameMatchSourceBR, config.LameMatchSourceBR,
chapters); chapters);
if (m4bBook.AppleTags.Tracks is (int trackNum, int trackCount))
{
lameConfig.ID3.Track = trackCount > 0 ? $"{trackNum}/{trackCount}" : trackNum.ToString();
}
using var mp3File = File.Open(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite); using var mp3File = File.Open(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite);
try try
{ {
+9 -2
View File
@@ -375,8 +375,15 @@ namespace FileLiberator
{ {
if (Configuration.Instance.AllowLibationFixup) if (Configuration.Instance.AllowLibationFixup)
{ {
e = OnRequestCoverArt(); try
abDownloader.SetCoverArt(e); {
e = OnRequestCoverArt();
abDownloader.SetCoverArt(e);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Failed to retrieve cover art from server.");
}
} }
if (e is not null) if (e is not null)
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+3 -3
View File
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.3.0.1" /> <PackageReference Include="Dinah.Core" Version="8.0.0.1" />
<PackageReference Include="Polly" Version="7.2.4" /> <PackageReference Include="Polly" Version="8.3.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+14 -10
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 --> <!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
<TrimMode>copyused</TrimMode> <TrimMode>copyused</TrimMode>
@@ -21,9 +21,13 @@
<ApplicationIcon>hangover.ico</ApplicationIcon> <ApplicationIcon>hangover.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <ItemGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
</PropertyGroup> </ItemGroup>
<PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\bin\Avalonia\Debug</OutputPath> <OutputPath>..\bin\Avalonia\Debug</OutputPath>
@@ -67,13 +71,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.2" /> <PackageReference Include="Avalonia" Version="11.0.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.2" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.5" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.5" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" /> <ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir> <PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir> <PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier> <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir> <PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<AssemblyName>Hangover</AssemblyName> <AssemblyName>Hangover</AssemblyName>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
@@ -15,6 +15,10 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\classic</PublishDir> <PublishDir>..\bin\Publish\classic</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
+1
View File
@@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solution Items", "{03C8835F-936C-4AF7-87AE-FF92BDBE8B9B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solution Items", "{03C8835F-936C-4AF7-87AE-FF92BDBE8B9B}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
REFERENCE.txt = REFERENCE.txt REFERENCE.txt = REFERENCE.txt
Upgrading dotnet version.txt = Upgrading dotnet version.txt
_ARCHITECTURE NOTES.txt = _ARCHITECTURE NOTES.txt _ARCHITECTURE NOTES.txt = _ARCHITECTURE NOTES.txt
_AvaloniaUI Primer.txt = _AvaloniaUI Primer.txt _AvaloniaUI Primer.txt = _AvaloniaUI Primer.txt
_DB_NOTES.txt = _DB_NOTES.txt _DB_NOTES.txt = _DB_NOTES.txt
+3 -3
View File
@@ -44,9 +44,9 @@
<SolidColorBrush x:Key="CancelRed" Color="#802727" /> <SolidColorBrush x:Key="CancelRed" Color="#802727" />
<SolidColorBrush x:Key="IconFill" Color="#DCE0DF" /> <SolidColorBrush x:Key="IconFill" Color="#DCE0DF" />
<SolidColorBrush x:Key="StoplightRed" Color="#5F0707" /> <SolidColorBrush x:Key="StoplightRed" Color="#7d1f1f" />
<SolidColorBrush x:Key="StoplightYellow" Color="#5F5B1A" /> <SolidColorBrush x:Key="StoplightYellow" Color="#7d7d1f" />
<SolidColorBrush x:Key="StoplightGreen" Color="#174E15" /> <SolidColorBrush x:Key="StoplightGreen" Color="#1f7d1f" />
<SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" /> <SolidColorBrush x:Key="DisabledGrayBrush" Opacity="0.4" Color="{StaticResource SystemChromeMediumColor}" />
@@ -107,7 +107,7 @@ namespace LibationAvalonia.Controls
if (known is Configuration.KnownDirectories.None) if (known is Configuration.KnownDirectories.None)
{ {
directoryState.CustomDir = noSubDir; directoryState.CustomDir = directory;
directoryState.CustomChecked = true; directoryState.CustomChecked = true;
} }
else else
@@ -27,6 +27,6 @@
Margin="5" Margin="5"
Padding="30,3,30,3" Padding="30,3,30,3"
Content="Save" Content="Save"
Command="{Binding SaveButtonAsync}" /> Click="Save_Click" />
</Grid> </Grid>
</Window> </Window>
@@ -1,11 +1,9 @@
using Avalonia.Controls;
using LibationFileManager; using LibationFileManager;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs namespace LibationAvalonia.Dialogs
{ {
public partial class LibationFilesDialog : Window public partial class LibationFilesDialog : DialogWindow
{ {
private class DirSelectOptions private class DirSelectOptions
{ {
@@ -18,28 +16,26 @@ namespace LibationAvalonia.Dialogs
public string Directory { get; set; } = Configuration.GetKnownDirectoryPath(Configuration.KnownDirectories.UserProfile); public string Directory { get; set; } = Configuration.GetKnownDirectoryPath(Configuration.KnownDirectories.UserProfile);
} }
private DirSelectOptions dirSelectOptions;
private readonly DirSelectOptions dirSelectOptions;
public string SelectedDirectory => dirSelectOptions.Directory; public string SelectedDirectory => dirSelectOptions.Directory;
public DialogResult DialogResult { get; private set; }
public LibationFilesDialog() public LibationFilesDialog() : base(saveAndRestorePosition: false)
{ {
InitializeComponent(); InitializeComponent();
DataContext = dirSelectOptions = new(); DataContext = dirSelectOptions = new();
} }
public async Task SaveButtonAsync() public async void Save_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{ {
var libationDir = dirSelectOptions.Directory; if (!System.IO.Directory.Exists(SelectedDirectory))
if (!System.IO.Directory.Exists(libationDir))
{ {
await MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + libationDir, "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error, saveAndRestorePosition: false); await MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + SelectedDirectory, "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error, saveAndRestorePosition: false);
return; return;
} }
DialogResult = DialogResult.OK; await SaveAndCloseAsync();
Close(DialogResult);
} }
} }
} }
@@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="750" mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="200"
MinWidth="900" MinHeight="750" MinWidth="900" MinHeight="200"
Width="900" Height="750" Width="900" Height="750"
x:Class="LibationAvalonia.Dialogs.SettingsDialog" x:Class="LibationAvalonia.Dialogs.SettingsDialog"
xmlns:controls="clr-namespace:LibationAvalonia.Controls" xmlns:controls="clr-namespace:LibationAvalonia.Controls"
@@ -46,39 +46,41 @@
</TabControl.Styles> </TabControl.Styles>
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<TextBlock Text="Important Settings"/> <TextBlock Text="Important Settings"/>
</TabItem.Header> </TabItem.Header>
<settings:Important DataContext="{CompiledBinding ImportantSettings}" /> <ScrollViewer VerticalScrollBarVisibility="Auto">
<settings:Important DataContext="{CompiledBinding ImportantSettings}" />
</ScrollViewer>
</TabItem> </TabItem>
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<TextBlock Text="Import Library"/> <TextBlock Text="Import Library"/>
</TabItem.Header> </TabItem.Header>
<settings:Import DataContext="{CompiledBinding ImportSettings}" /> <ScrollViewer VerticalScrollBarVisibility="Auto">
<settings:Import DataContext="{CompiledBinding ImportSettings}" />
</ScrollViewer>
</TabItem> </TabItem>
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<TextBlock Text="Download/Decrypt"/> <TextBlock Text="Download/Decrypt"/>
</TabItem.Header> </TabItem.Header>
<settings:DownloadDecrypt DataContext="{CompiledBinding DownloadDecryptSettings}" /> <ScrollViewer VerticalScrollBarVisibility="Auto">
<settings:DownloadDecrypt DataContext="{CompiledBinding DownloadDecryptSettings}" />
</ScrollViewer>
</TabItem> </TabItem>
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<TextBlock Text="Audio File Settings"/> <TextBlock Text="Audio File Settings"/>
</TabItem.Header> </TabItem.Header>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<settings:Audio DataContext="{CompiledBinding AudioSettings}" /> <settings:Audio DataContext="{CompiledBinding AudioSettings}" />
</ScrollViewer>
</TabItem> </TabItem>
</TabControl> </TabControl>
</Grid> </Grid>
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>Assets/libation.ico</ApplicationIcon> <ApplicationIcon>Assets/libation.ico</ApplicationIcon>
@@ -14,6 +14,10 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<StartupObject /> <StartupObject />
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
@@ -69,14 +73,14 @@
<UpToDateCheckInput Remove="Controls\GroupBox.axaml" /> <UpToDateCheckInput Remove="Controls\GroupBox.axaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.2" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" /> <PackageReference Include="Avalonia.Diagnostics" Version="11.0.5" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.2" /> <PackageReference Include="Avalonia" Version="11.0.5" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.2" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+5 -2
View File
@@ -177,9 +177,12 @@ Libation.
tbx.MinWidth = vm.TextBlockMinWidth; tbx.MinWidth = vm.TextBlockMinWidth;
tbx.Text = message; tbx.Text = message;
var thisScreen = owner.Screens.ScreenFromVisual(owner);
var thisScreen = owner.Screens?.ScreenFromVisual(owner);
var maxSize = new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55); var maxSize
= thisScreen is null ? owner.ClientSize
: new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55);
var desiredMax = new Size(maxSize.Width, maxSize.Height); var desiredMax = new Size(maxSize.Width, maxSize.Height);
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir> <PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir> <PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier> <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir> <PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -265,7 +265,7 @@ namespace LibationAvalonia.ViewModels
private byte[] AudioDecodable_RequestCoverArt(object sender, EventArgs e) private byte[] AudioDecodable_RequestCoverArt(object sender, EventArgs e)
{ {
var quality var quality
= Configuration.Instance.FileDownloadQuality == Configuration.DownloadQuality.High = Configuration.Instance.FileDownloadQuality == Configuration.DownloadQuality.High && LibraryBook.Book.PictureLarge is not null
? new PictureDefinition(LibraryBook.Book.PictureLarge, PictureSize.Native) ? new PictureDefinition(LibraryBook.Book.PictureLarge, PictureSize.Native)
: new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._500x500); : new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._500x500);
@@ -1,15 +1,18 @@
using ApplicationServices; using ApplicationServices;
using AudibleUtilities; using AudibleUtilities;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using DataLayer; using DataLayer;
using LibationAvalonia.Dialogs.Login; using LibationAvalonia.Dialogs.Login;
using LibationFileManager;
using LibationUiBase.GridView; using LibationUiBase.GridView;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace LibationAvalonia.ViewModels namespace LibationAvalonia.ViewModels
@@ -27,8 +30,8 @@ namespace LibationAvalonia.ViewModels
public string FilterString { get; private set; } public string FilterString { get; private set; }
public DataGridCollectionView GridEntries { get; private set; } public DataGridCollectionView GridEntries { get; private set; }
private bool _removeColumnVisivle; private bool _removeColumnVisible;
public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); } public bool RemoveColumnVisible { get => _removeColumnVisible; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisible, value); }
public List<LibraryBook> GetVisibleBookEntries() public List<LibraryBook> GetVisibleBookEntries()
=> FilteredInGridEntries? => FilteredInGridEntries?
@@ -321,7 +324,7 @@ namespace LibationAvalonia.ViewModels
{ {
foreach (var item in SOURCE) foreach (var item in SOURCE)
item.PropertyChanged -= GridEntry_PropertyChanged; item.PropertyChanged -= GridEntry_PropertyChanged;
RemoveColumnVisivle = false; RemoveColumnVisible = false;
} }
public async Task RemoveCheckedBooksAsync() public async Task RemoveCheckedBooksAsync()
@@ -376,7 +379,7 @@ namespace LibationAvalonia.ViewModels
item.PropertyChanged += GridEntry_PropertyChanged; item.PropertyChanged += GridEntry_PropertyChanged;
} }
RemoveColumnVisivle = true; RemoveColumnVisible = true;
RemovableCountChanged?.Invoke(this, 0); RemovableCountChanged?.Invoke(this, 0);
try try
@@ -421,5 +424,44 @@ namespace LibationAvalonia.ViewModels
} }
#endregion #endregion
#region Column Widths
public DataGridLength TitleWidth { get => getColumnWidth("Title", 200); set => setColumnWidth("Title", value); }
public DataGridLength AuthorsWidth { get => getColumnWidth("Authors", 100); set => setColumnWidth("Authors", value); }
public DataGridLength NarratorsWidth { get => getColumnWidth("Narrators", 100); set => setColumnWidth("Narrators", value); }
public DataGridLength LengthWidth { get => getColumnWidth("Length", 80); set => setColumnWidth("Length", value); }
public DataGridLength SeriesWidth { get => getColumnWidth("Series", 100); set => setColumnWidth("Series", value); }
public DataGridLength SeriesOrderWidth { get => getColumnWidth("SeriesOrder", 60); set => setColumnWidth("SeriesOrder", value); }
public DataGridLength DescriptionWidth { get => getColumnWidth("Description", 100); set => setColumnWidth("Description", value); }
public DataGridLength CategoryWidth { get => getColumnWidth("Category", 100); set => setColumnWidth("Category", value); }
public DataGridLength ProductRatingWidth { get => getColumnWidth("ProductRating", 95); set => setColumnWidth("ProductRating", value); }
public DataGridLength PurchaseDateWidth { get => getColumnWidth("PurchaseDate", 75); set => setColumnWidth("PurchaseDate", value); }
public DataGridLength MyRatingWidth { get => getColumnWidth("MyRating", 95); set => setColumnWidth("MyRating", value); }
public DataGridLength MiscWidth { get => getColumnWidth("Misc", 140); set => setColumnWidth("Misc", value); }
public DataGridLength LastDownloadWidth { get => getColumnWidth("LastDownload", 100); set => setColumnWidth("LastDownload", value); }
public DataGridLength BookTagsWidth { get => getColumnWidth("BookTags", 100); set => setColumnWidth("BookTags", value); }
private static DataGridLength getColumnWidth(string columnName, double defaultWidth)
=> Configuration.Instance.GridColumnsWidths.TryGetValue(columnName, out var val)
? new DataGridLength(val)
: new DataGridLength(defaultWidth);
private void setColumnWidth(string columnName, DataGridLength width, [CallerMemberName] string propertyName = "")
{
var dictionary = Configuration.Instance.GridColumnsWidths;
var newValue = (int)width.DisplayValue;
var valueSame = dictionary.TryGetValue(columnName, out var val) && val == newValue;
dictionary[columnName] = newValue;
if (!valueSame)
{
Configuration.Instance.GridColumnsWidths = dictionary;
this.RaisePropertyChanged(propertyName);
}
}
#endregion
} }
} }
@@ -21,7 +21,7 @@ namespace LibationAvalonia.Views
InitializeComponent(); InitializeComponent();
Configure_Upgrade(); Configure_Upgrade();
Loaded += MainWindow_Loaded; Opened += MainWindow_Opened;
Closing += MainWindow_Closing; Closing += MainWindow_Closing;
LibraryLoaded += MainWindow_LibraryLoaded; LibraryLoaded += MainWindow_LibraryLoaded;
@@ -35,7 +35,7 @@ namespace LibationAvalonia.Views
} }
} }
private async void MainWindow_Loaded(object sender, EventArgs e) private async void MainWindow_Opened(object sender, EventArgs e)
{ {
if (Configuration.Instance.FirstLaunch) if (Configuration.Instance.FirstLaunch)
{ {
@@ -17,6 +17,7 @@
AutoGenerateColumns="False" AutoGenerateColumns="False"
ItemsSource="{Binding GridEntries}" ItemsSource="{Binding GridEntries}"
CanUserSortColumns="True" BorderThickness="3" CanUserSortColumns="True" BorderThickness="3"
CanUserResizeColumns="True"
CanUserReorderColumns="True"> CanUserReorderColumns="True">
<DataGrid.Styles> <DataGrid.Styles>
@@ -45,10 +46,11 @@
</DataGrid.Styles> </DataGrid.Styles>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn <DataGridTemplateColumn
CanUserSort="True" CanUserSort="True"
IsVisible="{Binding RemoveColumnVisivle}" CanUserResize="False"
IsVisible="{Binding RemoveColumnVisible}"
PropertyChanged="RemoveColumn_PropertyChanged" PropertyChanged="RemoveColumn_PropertyChanged"
Header="Remove" Header="Remove"
IsReadOnly="False" IsReadOnly="False"
@@ -65,7 +67,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<controls:DataGridTemplateColumnExt CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}"> <controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<views:LiberateStatusButton <views:LiberateStatusButton
@@ -80,15 +82,15 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}"> <controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" /> <Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="150" Width="2*" Header="Title" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}"> <controls:DataGridTemplateColumnExt Header="Title" MinWidth="10" Width="{Binding TitleWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -98,7 +100,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Authors" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}"> <controls:DataGridTemplateColumnExt Header="Authors" MinWidth="10" Width="{Binding AuthorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -108,7 +110,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Narrators" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}"> <controls:DataGridTemplateColumnExt Header="Narrators" MinWidth="10" Width="{Binding NarratorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -118,7 +120,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="90" Header="Length" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}"> <controls:DataGridTemplateColumnExt Header="Length" MinWidth="10" Width="{Binding LengthWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -128,7 +130,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="80" Width="1*" Header="Series" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt Header="Series" MinWidth="10" Width="{Binding SeriesWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -138,7 +140,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="Auto" Header="Series&#xA;Order" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt Header="Series&#xA;Order" MinWidth="10" Width="{Binding SeriesOrderWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -148,7 +150,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt MinWidth="100" Width="1*" Header="Description" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}"> <controls:DataGridTemplateColumnExt Header="Description" MinWidth="10" Width="{Binding DescriptionWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" > <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
@@ -158,7 +160,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="100" Header="Category" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}"> <controls:DataGridTemplateColumnExt Header="Category" MinWidth="10" Width="{Binding CategoryWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -172,14 +174,14 @@
x:DataType="uibase:IGridEntry" x:DataType="uibase:IGridEntry"
Header="Product&#xA;Rating" Header="Product&#xA;Rating"
IsReadOnly="true" IsReadOnly="true"
Width="115" MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}"
SortMemberPath="ProductRating" CanUserSort="True" SortMemberPath="ProductRating" CanUserSort="True"
OpacityBinding="{CompiledBinding Liberate.Opacity}" OpacityBinding="{CompiledBinding Liberate.Opacity}"
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}" BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
ClipboardContentBinding="{CompiledBinding ProductRating}" ClipboardContentBinding="{CompiledBinding ProductRating}"
Binding="{CompiledBinding ProductRating}" /> Binding="{CompiledBinding ProductRating}" />
<controls:DataGridTemplateColumnExt Width="90" Header="Purchase&#xA;Date" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}"> <controls:DataGridTemplateColumnExt Header="Purchase&#xA;Date" MinWidth="10" Width="{Binding PurchaseDateWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -191,16 +193,16 @@
<controls:DataGridMyRatingColumn <controls:DataGridMyRatingColumn
x:DataType="uibase:IGridEntry" x:DataType="uibase:IGridEntry"
Header="My Rating" Header="My Rating"
IsReadOnly="false" IsReadOnly="false"
Width="115" MinWidth="10" Width="{Binding MyRatingWidth, Mode=TwoWay}"
SortMemberPath="MyRating" CanUserSort="True" SortMemberPath="MyRating" CanUserSort="True"
OpacityBinding="{CompiledBinding Liberate.Opacity}" OpacityBinding="{CompiledBinding Liberate.Opacity}"
BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}" BackgroundBinding="{CompiledBinding Liberate.BackgroundBrush}"
ClipboardContentBinding="{CompiledBinding MyRating}" ClipboardContentBinding="{CompiledBinding MyRating}"
Binding="{CompiledBinding MyRating, Mode=TwoWay}" /> Binding="{CompiledBinding MyRating, Mode=TwoWay}" />
<controls:DataGridTemplateColumnExt Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}"> <controls:DataGridTemplateColumnExt Header="Misc" MinWidth="10" Width="{Binding MiscWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}">
@@ -210,7 +212,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="102" Header="Last&#xA;Download" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}"> <controls:DataGridTemplateColumnExt Header="Last&#xA;Download" MinWidth="10" Width="{Binding LastDownloadWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" Background="{CompiledBinding Liberate.BackgroundBrush}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick">
@@ -220,7 +222,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt CanUserSort="True" Width="100" Header="Tags" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}"> <controls:DataGridTemplateColumnExt Header="Tags" MinWidth="10" Width="{Binding BookTagsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:IGridEntry">
<Button <Button
@@ -11,6 +11,7 @@ using LibationAvalonia.Dialogs;
using LibationAvalonia.ViewModels; using LibationAvalonia.ViewModels;
using LibationFileManager; using LibationFileManager;
using LibationUiBase.GridView; using LibationUiBase.GridView;
using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -172,126 +173,107 @@ namespace LibationAvalonia.Views
public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridCellContextMenuStripNeededEventArgs args) public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridCellContextMenuStripNeededEventArgs args)
{ {
var entry = args.GridEntry;
var ctx = new GridContextMenu(entry, '_');
if (args.Column.SortMemberPath is not "Liberate" and not "Cover") if (args.Column.SortMemberPath is not "Liberate" and not "Cover")
{ {
var menuItem = new MenuItem { Header = "_Copy Cell Contents" }; args.ContextMenuItems.Add(new MenuItem
{
menuItem.Click += async (s, e) Header = ctx.CopyCellText,
=> await App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents); Command = ReactiveCommand.CreateFromTask(() => App.MainWindow.Clipboard.SetTextAsync(args.CellClipboardContents))
});
args.ContextMenuItems.Add(menuItem);
args.ContextMenuItems.Add(new Separator()); args.ContextMenuItems.Add(new Separator());
} }
var entry = args.GridEntry;
#region Liberate all Episodes #region Liberate all Episodes
if (entry.Liberate.IsSeries) if (entry.Liberate.IsSeries)
{ {
var liberateEpisodesMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "_Liberate All Episodes", Header = ctx.LiberateEpisodesText,
IsEnabled = ((ISeriesEntry)entry).Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload) IsEnabled = ctx.LiberateEpisodesEnabled,
}; Command = ReactiveCommand.Create(() => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry))
});
args.ContextMenuItems.Add(liberateEpisodesMenuItem);
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, ((ISeriesEntry)entry));
} }
#endregion #endregion
#region Set Download status to Downloaded #region Set Download status to Downloaded
var setDownloadMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "Set Download status to '_Downloaded'", Header = ctx.SetDownloadedText,
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || entry.Liberate.IsSeries IsEnabled = ctx.SetDownloadedEnabled,
}; Command = ReactiveCommand.Create(ctx.SetDownloaded)
});
args.ContextMenuItems.Add(setDownloadMenuItem);
if (entry.Liberate.IsSeries)
setDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
else
setDownloadMenuItem.Click += (_, __) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
#endregion #endregion
#region Set Download status to Not Downloaded #region Set Download status to Not Downloaded
var setNotDownloadMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "Set Download status to '_Not Downloaded'", Header = ctx.SetNotDownloadedText,
IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || entry.Liberate.IsSeries IsEnabled = ctx.SetNotDownloadedEnabled,
}; Command = ReactiveCommand.Create(ctx.SetNotDownloaded)
});
args.ContextMenuItems.Add(setNotDownloadMenuItem);
if (entry.Liberate.IsSeries)
setNotDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
else
setNotDownloadMenuItem.Click += (_, __) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
#endregion #endregion
#region Remove from library #region Remove from library
var removeMenuItem = new MenuItem() { Header = "_Remove from library" }; args.ContextMenuItems.Add(new MenuItem
{
args.ContextMenuItems.Add(removeMenuItem); Header = ctx.RemoveText,
Command = ReactiveCommand.CreateFromTask(ctx.RemoveAsync)
if (entry.Liberate.IsSeries) });
removeMenuItem.Click += async (_, __) => await ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).RemoveBooksAsync();
else
removeMenuItem.Click += async (_, __) => await Task.Run(entry.LibraryBook.RemoveBook);
#endregion #endregion
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
#region Locate file #region Locate file
var locateFileMenuItem = new MenuItem() { Header = "_Locate file..." };
args.ContextMenuItems.Add(locateFileMenuItem); args.ContextMenuItems.Add(new MenuItem
locateFileMenuItem.Click += async (_, __) =>
{ {
try Header = ctx.LocateFileText,
Command = ReactiveCommand.CreateFromTask(async () =>
{ {
var window = this.GetParentWindow(); try
var openFileDialogOptions = new FilePickerOpenOptions
{ {
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'", var window = this.GetParentWindow();
AllowMultiple = false,
SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix), var openFileDialogOptions = new FilePickerOpenOptions
FileTypeFilter = new FilePickerFileType[]
{ {
new("All files (*.*)") { Patterns = new[] { "*" } }, Title = ctx.LocateFileDialogTitle,
} AllowMultiple = false,
}; SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
FileTypeFilter = new FilePickerFileType[]
{
new("All files (*.*)") { Patterns = new[] { "*" } },
}
};
var selectedFiles = await window.StorageProvider.OpenFilePickerAsync(openFileDialogOptions); var selectedFiles = await window.StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
var selectedFile = selectedFiles.SingleOrDefault()?.TryGetLocalPath(); var selectedFile = selectedFiles.SingleOrDefault()?.TryGetLocalPath();
if (selectedFile is not null) if (selectedFile is not null)
FilePathCache.Insert(entry.AudibleProductId, selectedFile); FilePathCache.Insert(entry.AudibleProductId, selectedFile);
} }
catch (Exception ex) catch (Exception ex)
{ {
var msg = "Error saving book's location"; await MessageBox.ShowAdminAlert(null, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
await MessageBox.ShowAdminAlert(null, msg, msg, ex); }
} })
}; });
#endregion #endregion
#region Convert to Mp3 #region Convert to Mp3
var convertToMp3MenuItem = new MenuItem args.ContextMenuItems.Add(new MenuItem
{ {
Header = "_Convert to Mp3", Header = ctx.ConvertToMp3Text,
IsEnabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated IsEnabled = ctx.ConvertToMp3Enabled,
}; Command = ReactiveCommand.Create(() => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook))
args.ContextMenuItems.Add(convertToMp3MenuItem); });
convertToMp3MenuItem.Click += (_, _) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
#endregion #endregion
} }
@@ -299,34 +281,72 @@ namespace LibationAvalonia.Views
#region Force Re-Download #region Force Re-Download
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
var reDownloadMenuItem = new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
Header = "Re-download this audiobook", Header = ctx.ReDownloadText,
IsEnabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated IsEnabled = ctx.ReDownloadEnabled,
}; Command = ReactiveCommand.Create(() =>
{
args.ContextMenuItems.Add(reDownloadMenuItem); //No need to persist this change. It only needs to last long for the file to start downloading
reDownloadMenuItem.Click += (s, _) => entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
{ LiberateClicked?.Invoke(this, entry.LibraryBook);
//No need to persist this change. It only needs to last long for the file to start downloading })
entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated; });
LiberateClicked?.Invoke(s, entry.LibraryBook);
};
} }
#endregion #endregion
args.ContextMenuItems.Add(new Separator()); args.ContextMenuItems.Add(new Separator());
#region Edit Templates
async Task editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
where T : Templates, LibationFileManager.ITemplate, new()
{
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
var form = new EditTemplateDialog(template);
if (await form.ShowDialog<DialogResult>(this.GetParentWindow()) == DialogResult.OK)
{
setNewTemplate(template.EditingTemplate.TemplateText);
}
}
if (!entry.Liberate.IsSeries)
{
args.ContextMenuItems.Add(new MenuItem
{
Header = ctx.EditTemplatesText,
ItemsSource = new[]
{
new MenuItem
{
Header = ctx.FolderTemplateText,
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FolderTemplate>(entry.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t))
},
new MenuItem
{
Header = ctx.FileTemplateText,
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FileTemplate>(entry.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t))
},
new MenuItem
{
Header = ctx.MultipartTemplateText,
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.ChapterFileTemplate>(entry.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t))
}
}
});
args.ContextMenuItems.Add(new Separator());
}
#endregion
#region View Bookmarks/Clips #region View Bookmarks/Clips
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
args.ContextMenuItems.Add(new MenuItem
var bookRecordMenuItem = new MenuItem { Header = "View _Bookmarks/Clips" }; {
Header = ctx.ViewBookmarksText,
args.ContextMenuItems.Add(bookRecordMenuItem); Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window))
});
bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window);
} }
#endregion #endregion
@@ -334,12 +354,11 @@ namespace LibationAvalonia.Views
if (entry.Book.SeriesLink.Any()) if (entry.Book.SeriesLink.Any())
{ {
var header = entry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series"; args.ContextMenuItems.Add(new MenuItem
var viewSeriesMenuItem = new MenuItem { Header = header }; {
Header = ctx.ViewSeriesText,
args.ContextMenuItems.Add(viewSeriesMenuItem); Command = ReactiveCommand.Create(() => new SeriesViewDialog(entry.LibraryBook).Show())
});
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
} }
#endregion #endregion
+5 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
@@ -11,6 +11,10 @@
<IsPublishable>True</IsPublishable> <IsPublishable>True</IsPublishable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir> <PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir> <PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier> <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir> <PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
+125 -99
View File
@@ -8,20 +8,21 @@ using Dinah.Core;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
using FileManager; using FileManager;
using AaxDecrypter;
#nullable enable #nullable enable
namespace LibationFileManager namespace LibationFileManager
{ {
public abstract class AudibleFileStorage public abstract class AudibleFileStorage
{ {
protected abstract LongPath? GetFilePathCustom(string productId); protected abstract LongPath? GetFilePathCustom(string productId);
protected abstract List<LongPath> GetFilePathsCustom(string productId); protected abstract List<LongPath> GetFilePathsCustom(string productId);
#region static #region static
public static LongPath DownloadsInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName; public static LongPath DownloadsInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName;
public static LongPath DecryptInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DecryptInProgress")).FullName; public static LongPath DecryptInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DecryptInProgress")).FullName;
static AudibleFileStorage() static AudibleFileStorage()
{ {
//Clean up any partially-decrypted files from previous Libation instances. //Clean up any partially-decrypted files from previous Libation instances.
//Do no clean DownloadsInProgressDirectory because those files are resumable //Do no clean DownloadsInProgressDirectory because those files are resumable
@@ -31,103 +32,103 @@ namespace LibationFileManager
private static AaxcFileStorage AAXC { get; } = new AaxcFileStorage(); private static AaxcFileStorage AAXC { get; } = new AaxcFileStorage();
public static bool AaxcExists(string productId) => AAXC.Exists(productId); public static bool AaxcExists(string productId) => AAXC.Exists(productId);
public static AudioFileStorage Audio { get; } = new AudioFileStorage(); public static AudioFileStorage Audio { get; } = new AudioFileStorage();
public static LongPath BooksDirectory public static LongPath BooksDirectory
{ {
get get
{ {
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books)) if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
Configuration.Instance.Books = Path.Combine(Configuration.UserProfile, "Books"); Configuration.Instance.Books = Path.Combine(Configuration.UserProfile, "Books");
return Directory.CreateDirectory(Configuration.Instance.Books).FullName; return Directory.CreateDirectory(Configuration.Instance.Books).FullName;
} }
} }
#endregion #endregion
#region instance #region instance
private FileType FileType { get; } private FileType FileType { get; }
private string regexTemplate { get; } private string regexTemplate { get; }
protected AudibleFileStorage(FileType fileType) protected AudibleFileStorage(FileType fileType)
{ {
FileType = fileType; FileType = fileType;
var extAggr = FileTypes.GetExtensions(FileType).Aggregate((a, b) => $"{a}|{b}"); var extAggr = FileTypes.GetExtensions(FileType).Aggregate((a, b) => $"{a}|{b}");
regexTemplate = $@"{{0}}.*?\.({extAggr})$"; regexTemplate = $@"{{0}}.*?\.({extAggr})$";
} }
protected LongPath? GetFilePath(string productId) protected LongPath? GetFilePath(string productId)
{ {
// primary lookup // primary lookup
var cachedFile = FilePathCache.GetFirstPath(productId, FileType); var cachedFile = FilePathCache.GetFirstPath(productId, FileType);
if (cachedFile is not null && File.Exists(cachedFile)) if (cachedFile is not null && File.Exists(cachedFile))
return cachedFile; return cachedFile;
// secondary lookup attempt // secondary lookup attempt
var firstOrNull = GetFilePathCustom(productId); var firstOrNull = GetFilePathCustom(productId);
if (firstOrNull is not null) if (firstOrNull is not null)
FilePathCache.Insert(productId, firstOrNull); FilePathCache.Insert(productId, firstOrNull);
return firstOrNull; return firstOrNull;
} }
public List<LongPath> GetPaths(string productId) public List<LongPath> GetPaths(string productId)
=> GetFilePathsCustom(productId); => GetFilePathsCustom(productId);
protected Regex GetBookSearchRegex(string productId) protected Regex GetBookSearchRegex(string productId)
{ {
var pattern = string.Format(regexTemplate, productId); var pattern = string.Format(regexTemplate, productId);
return new Regex(pattern, RegexOptions.IgnoreCase); return new Regex(pattern, RegexOptions.IgnoreCase);
} }
#endregion #endregion
} }
internal class AaxcFileStorage : AudibleFileStorage internal class AaxcFileStorage : AudibleFileStorage
{ {
internal AaxcFileStorage() : base(FileType.AAXC) { } internal AaxcFileStorage() : base(FileType.AAXC) { }
protected override LongPath? GetFilePathCustom(string productId) protected override LongPath? GetFilePathCustom(string productId)
=> GetFilePathsCustom(productId).FirstOrDefault(); => GetFilePathsCustom(productId).FirstOrDefault();
protected override List<LongPath> GetFilePathsCustom(string productId) protected override List<LongPath> GetFilePathsCustom(string productId)
{ {
var regex = GetBookSearchRegex(productId); var regex = GetBookSearchRegex(productId);
return FileUtility return FileUtility
.SaferEnumerateFiles(DownloadsInProgressDirectory, "*.*", SearchOption.AllDirectories) .SaferEnumerateFiles(DownloadsInProgressDirectory, "*.*", SearchOption.AllDirectories)
.Where(s => regex.IsMatch(s)).ToList(); .Where(s => regex.IsMatch(s)).ToList();
} }
public bool Exists(string productId) => GetFilePath(productId) is not null; public bool Exists(string productId) => GetFilePath(productId) is not null;
} }
public class AudioFileStorage : AudibleFileStorage public class AudioFileStorage : AudibleFileStorage
{ {
internal AudioFileStorage() : base(FileType.Audio) internal AudioFileStorage() : base(FileType.Audio)
=> BookDirectoryFiles ??= newBookDirectoryFiles(); => BookDirectoryFiles ??= newBookDirectoryFiles();
private static BackgroundFileSystem? BookDirectoryFiles { get; set; } private static BackgroundFileSystem? BookDirectoryFiles { get; set; }
private static object bookDirectoryFilesLocker { get; } = new(); private static object bookDirectoryFilesLocker { get; } = new();
private static EnumerationOptions enumerationOptions { get; } = new() private static EnumerationOptions enumerationOptions { get; } = new()
{ {
RecurseSubdirectories = true, RecurseSubdirectories = true,
IgnoreInaccessible = true, IgnoreInaccessible = true,
MatchCasing = MatchCasing.CaseInsensitive AttributesToSkip = FileAttributes.Hidden,
}; };
protected override LongPath? GetFilePathCustom(string productId) protected override LongPath? GetFilePathCustom(string productId)
=> GetFilePathsCustom(productId).FirstOrDefault(); => GetFilePathsCustom(productId).FirstOrDefault();
private static BackgroundFileSystem newBookDirectoryFiles() private static BackgroundFileSystem newBookDirectoryFiles()
=> new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories); => new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
protected override List<LongPath> GetFilePathsCustom(string productId) protected override List<LongPath> GetFilePathsCustom(string productId)
{ {
// If user changed the BooksDirectory: reinitialize // If user changed the BooksDirectory: reinitialize
lock (bookDirectoryFilesLocker) lock (bookDirectoryFilesLocker)
if (BooksDirectory != BookDirectoryFiles?.RootDirectory) if (BooksDirectory != BookDirectoryFiles?.RootDirectory)
BookDirectoryFiles = newBookDirectoryFiles(); BookDirectoryFiles = newBookDirectoryFiles();
var regex = GetBookSearchRegex(productId); var regex = GetBookSearchRegex(productId);
@@ -135,44 +136,60 @@ namespace LibationFileManager
//using both the file system and the file path cache //using both the file system and the file path cache
return return
FilePathCache FilePathCache
.GetFiles(productId) .GetFiles(productId)
.Where(c => c.fileType == FileType.Audio && File.Exists(c.path)) .Where(c => c.fileType == FileType.Audio && File.Exists(c.path))
.Select(c => c.path) .Select(c => c.path)
.Union(BookDirectoryFiles.FindFiles(regex)) .Union(BookDirectoryFiles.FindFiles(regex))
.ToList(); .ToList();
} }
public void Refresh() public void Refresh()
{ {
if (BookDirectoryFiles is null) if (BookDirectoryFiles is null)
lock (bookDirectoryFilesLocker) lock (bookDirectoryFilesLocker)
BookDirectoryFiles = newBookDirectoryFiles(); BookDirectoryFiles = newBookDirectoryFiles();
else else
BookDirectoryFiles?.RefreshFiles(); BookDirectoryFiles?.RefreshFiles();
} }
public LongPath? GetPath(string productId) => GetFilePath(productId); public LongPath? GetPath(string productId) => GetFilePath(productId);
public static async IAsyncEnumerable<FilePathCache.CacheEntry> FindAudiobooksAsync(LongPath searchDirectory, [EnumeratorCancellation] CancellationToken cancellationToken) public static async IAsyncEnumerable<FilePathCache.CacheEntry> FindAudiobooksAsync(LongPath searchDirectory, [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
ArgumentValidator.EnsureNotNull(searchDirectory, nameof(searchDirectory)); ArgumentValidator.EnsureNotNull(searchDirectory, nameof(searchDirectory));
foreach (LongPath path in Directory.EnumerateFiles(searchDirectory, "*.M4B", enumerationOptions)) foreach (LongPath path in Directory.EnumerateFiles(searchDirectory, "*.*", enumerationOptions))
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
yield break; yield break;
if (getFormatByExtension(path) is not OutputFormat format)
continue;
FilePathCache.CacheEntry? audioFile = default; FilePathCache.CacheEntry? audioFile = default;
try try
{ {
using var fileStream = File.OpenRead(path); if (format is OutputFormat.M4b)
{
var tags = await Task.Run(() => AAXClean.AppleTags.FromFile(path));
var mp4File = await Task.Run(() => new AAXClean.Mp4File(fileStream), cancellationToken); if (tags?.Asin is not null)
audioFile = new FilePathCache.CacheEntry(tags.Asin, FileType.Audio, path);
}
else
{
using var fileStream = File.OpenRead(path);
var id3 = await Task.Run(() => NAudio.Lame.ID3.Id3Tag.Create(fileStream));
if (mp4File?.AppleTags?.Asin is not null) var asin = id3?.Children
audioFile = new FilePathCache.CacheEntry(mp4File.AppleTags.Asin, FileType.Audio, path); .OfType<NAudio.Lame.ID3.TXXXFrame>()
.FirstOrDefault(f => f.FieldName == "AUDIBLE_ASIN")
?.FieldValue;
if (!string.IsNullOrWhiteSpace(asin))
audioFile = new FilePathCache.CacheEntry(asin, FileType.Audio, path);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -186,6 +203,15 @@ namespace LibationFileManager
if (audioFile is not null) if (audioFile is not null)
yield return audioFile; yield return audioFile;
} }
static OutputFormat? getFormatByExtension(string path)
{
var ext = Path.GetExtension(path).ToLower();
return ext == ".mp3" ? OutputFormat.Mp3
: ext == ".m4b" ? OutputFormat.M4b
: null;
}
} }
} }
} }
@@ -6,6 +6,7 @@ using Newtonsoft.Json.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Serilog; using Serilog;
using Dinah.Core.Logging; using Dinah.Core.Logging;
using System.Diagnostics;
#nullable enable #nullable enable
namespace LibationFileManager namespace LibationFileManager
@@ -75,17 +76,19 @@ namespace LibationFileManager
const string appsettings_filename = "appsettings.json"; const string appsettings_filename = "appsettings.json";
//Possible appsettings.json locations, in order of preference. //Possible appsettings.json locations, in order of preference.
string[] possibleAppsettingsFiles = new[] string[] possibleAppsettingsDirectories = new[]
{ {
Path.Combine(ProcessDirectory, appsettings_filename), ProcessDirectory,
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation", appsettings_filename), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation"),
Path.Combine(UserProfile, appsettings_filename), UserProfile,
Path.Combine(Path.GetTempPath(), "Libation", appsettings_filename) Path.Combine(Path.GetTempPath(), "Libation")
}; };
//Try to find and validate appsettings.json in each folder //Try to find and validate appsettings.json in each folder
foreach (var appsettingsFile in possibleAppsettingsFiles) foreach (var dir in possibleAppsettingsDirectories)
{ {
var appsettingsFile = Path.Combine(dir, appsettings_filename);
if (File.Exists(appsettingsFile)) if (File.Exists(appsettingsFile))
{ {
try try
@@ -104,10 +107,13 @@ namespace LibationFileManager
//Valid appsettings.json not found. Try to create it in each folder. //Valid appsettings.json not found. Try to create it in each folder.
var endingContents = new JObject { { LIBATION_FILES_KEY, UserProfile } }.ToString(Formatting.Indented); var endingContents = new JObject { { LIBATION_FILES_KEY, UserProfile } }.ToString(Formatting.Indented);
foreach (var appsettingsFile in possibleAppsettingsFiles) foreach (var dir in possibleAppsettingsDirectories)
{ {
var appsettingsFile = Path.Combine(dir, appsettings_filename);
try try
{ {
Directory.CreateDirectory(dir);
File.WriteAllText(appsettingsFile, endingContents); File.WriteAllText(appsettingsFile, endingContents);
return appsettingsFile; return appsettingsFile;
} }
@@ -121,16 +127,56 @@ namespace LibationFileManager
} }
private static string getLibationFilesSettingFromJson() private static string getLibationFilesSettingFromJson()
{ {
// do not check whether directory exists. special/meta directory (eg: AppDir) is valid // do not check whether directory exists. special/meta directory (eg: AppDir) is valid
// verify from live file. no try/catch. want failures to be visible // verify from live file. no try/catch. want failures to be visible
var jObjFinal = JObject.Parse(File.ReadAllText(AppsettingsJsonFile)); var jObjFinal = JObject.Parse(File.ReadAllText(AppsettingsJsonFile));
if (jObjFinal[LIBATION_FILES_KEY]?.Value<string>() is not string valueFinal) if (jObjFinal[LIBATION_FILES_KEY]?.Value<string>() is not string valueFinal)
throw new InvalidDataException($"{LIBATION_FILES_KEY} not found in {AppsettingsJsonFile}"); throw new InvalidDataException($"{LIBATION_FILES_KEY} not found in {AppsettingsJsonFile}");
return valueFinal; if (IsWindows)
} {
valueFinal = Environment.ExpandEnvironmentVariables(valueFinal);
}
else
{
//If the shell command fails and returns null, proceed with the verbatim
//LIBATION_FILES_KEY path and hope for the best. If Libation can't find
//anything at this path it will set LIBATION_FILES_KEY to UserProfile
valueFinal = runShellCommand("echo " + valueFinal) ?? valueFinal;
}
return valueFinal;
static string? runShellCommand(string command)
{
var psi = new ProcessStartInfo
{
FileName = "/bin/sh",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
ArgumentList =
{
"-c",
command
}
};
try
{
var proc = Process.Start(psi);
proc?.WaitForExit();
return proc?.StandardOutput?.ReadToEnd()?.Trim();
}
catch (Exception e)
{
Serilog.Log.Error(e, "Failed to run shell command. {Arguments}", psi.ArgumentList);
return null;
}
}
}
public static void SetLibationFiles(string directory) public static void SetLibationFiles(string directory)
{ {
+2 -2
View File
@@ -95,9 +95,9 @@ namespace LibationFileManager
var assembly = CurrentDomain_AssemblyResolve_internal(asmName, here: here); var assembly = CurrentDomain_AssemblyResolve_internal(asmName, here: here);
lowEffortCache[key] = assembly; lowEffortCache[key] = assembly;
//Let the runtime handle any dll not found exceptions. // Let the runtime handle any dll not found exceptions
if (assembly is null) if (assembly is null)
Serilog.Log.Logger.Error($"Unable to load module {args.Name}"); Serilog.Log.Logger.Warning($"Unable to load module {args.Name}");
return assembly; return assembly;
} }
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="NameParserSharp" Version="1.5.0" /> <PackageReference Include="NameParserSharp" Version="1.5.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" /> <PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
</ItemGroup> </ItemGroup>
@@ -51,7 +51,10 @@ namespace LibationFileManager
return false; return false;
} }
private static readonly LibraryBookDto libraryBookDto public LibraryBookDto FolderBook { get; }
public LibraryBookDto LibraryBook { get; }
private static readonly LibraryBookDto DefaultLibraryBook
= new() = new()
{ {
Account = "myaccount@example.co", Account = "myaccount@example.co",
@@ -60,6 +63,7 @@ namespace LibationFileManager
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0), DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
AudibleProductId = "123456789", AudibleProductId = "123456789",
Title = "A Study in Scarlet", Title = "A Study in Scarlet",
TitleWithSubtitle = "A Study in Scarlet: A Sherlock Holmes Novel",
Subtitle = "A Sherlock Holmes Novel", Subtitle = "A Sherlock Holmes Novel",
Locale = "us", Locale = "us",
YearPublished = 2017, YearPublished = 2017,
@@ -73,7 +77,7 @@ namespace LibationFileManager
Language = "English" Language = "English"
}; };
private static readonly MultiConvertFileProperties partFileProperties private static readonly MultiConvertFileProperties DefaultMultipartProperties
= new() = new()
{ {
OutputFileName = "", OutputFileName = "",
@@ -90,23 +94,27 @@ namespace LibationFileManager
* subdirectories. Without rooting, we won't be allowed to create a * subdirectories. Without rooting, we won't be allowed to create a
* relative path longer than MAX_PATH. * relative path longer than MAX_PATH.
*/ */
var dir = Folder?.GetFilename(libraryBookDto, BaseDirectory, ""); var dir = Folder?.GetFilename(FolderBook, BaseDirectory, "");
if (dir is null) return null; if (dir is null) return null;
return Path.GetRelativePath(BaseDirectory, dir); return Path.GetRelativePath(BaseDirectory, dir);
} }
public string? GetFileName() public string? GetFileName()
=> File?.GetFilename(libraryBookDto, partFileProperties, "", ""); => File?.GetFilename(LibraryBook, DefaultMultipartProperties, "", "");
public string? GetName() public string? GetName()
=> Name?.GetName(libraryBookDto, partFileProperties); => Name?.GetName(LibraryBook, DefaultMultipartProperties);
private TemplateEditor( private TemplateEditor(
LibraryBookDto? folderDto,
LibraryBookDto? fileDto,
Templates editingTemplate, Templates editingTemplate,
LongPath baseDirectory, LongPath baseDirectory,
string defaultTemplate, string defaultTemplate,
string templateName, string templateName,
string templateDescription) string templateDescription)
{ {
FolderBook = folderDto ?? DefaultLibraryBook;
LibraryBook = fileDto ?? DefaultLibraryBook;
_editingTemplate = editingTemplate; _editingTemplate = editingTemplate;
BaseDirectory = baseDirectory; BaseDirectory = baseDirectory;
DefaultTemplate = defaultTemplate; DefaultTemplate = defaultTemplate;
@@ -114,25 +122,30 @@ namespace LibationFileManager
TemplateDescription = templateDescription; TemplateDescription = templateDescription;
} }
public static ITemplateEditor CreateFilenameEditor(LongPath baseDir, string templateText) public static ITemplateEditor CreateFilenameEditor(LongPath baseDir, string templateText, LibraryBookDto? folderDto = null, LibraryBookDto? fileDto = null)
{ {
if (!Templates.TryGetTemplate<T>(templateText, out var template)) if (!Templates.TryGetTemplate<T>(templateText, out var template))
throw new ArgumentException($"Failed to parse {nameof(templateText)}"); throw new ArgumentException($"Failed to parse {nameof(templateText)}");
var templateEditor = new TemplateEditor<T>(template, baseDir, T.DefaultTemplate, T.Name, T.Description); var templateEditor = new TemplateEditor<T>(folderDto, fileDto, template, baseDir, T.DefaultTemplate, T.Name, T.Description);
if (!templateEditor.IsFolder && !templateEditor.IsFilePath) if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates"); throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates");
if (templateEditor.IsFolder)
templateEditor.File = Templates.File;
else
templateEditor.Folder = Templates.Folder;
return templateEditor; return templateEditor;
} }
public static ITemplateEditor CreateNameEditor(string templateText) public static ITemplateEditor CreateNameEditor(string templateText, LibraryBookDto? libraryBookDto = null)
{ {
if (!Templates.TryGetTemplate<T>(templateText, out var nameTemplate)) if (!Templates.TryGetTemplate<T>(templateText, out var nameTemplate))
throw new ArgumentException($"Failed to parse {nameof(templateText)}"); throw new ArgumentException($"Failed to parse {nameof(templateText)}");
var templateEditor = new TemplateEditor<T>(nameTemplate, "", T.DefaultTemplate, T.Name, T.Description); var templateEditor = new TemplateEditor<T>(null, libraryBookDto, nameTemplate, "", T.DefaultTemplate, T.Name, T.Description);
if (templateEditor.IsFolder || templateEditor.IsFilePath) if (templateEditor.IsFolder || templateEditor.IsFilePath)
throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates"); throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates");
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -9,7 +9,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LuceneNet303r2" Version="3.0.3.5" /> <PackageReference Include="LuceneNet303r2" Version="3.0.3.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -133,7 +133,7 @@ namespace LibationUiBase.GridView
private string GetTooltip() private string GetTooltip()
{ {
if (IsSeries) if (IsSeries)
return Expanded ? "Click to Collpase" : "Click to Expand"; return Expanded ? "Click to Collapse" : "Click to Expand";
if (IsUnavailable) if (IsUnavailable)
return "This book cannot be downloaded\nbecause it wasn't found during\nthe most recent library scan"; return "This book cannot be downloaded\nbecause it wasn't found during\nthe most recent library scan";
@@ -0,0 +1,98 @@
using ApplicationServices;
using DataLayer;
using FileLiberator;
using LibationFileManager;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace LibationUiBase.GridView;
public class GridContextMenu
{
public string CopyCellText => $"{Accelerator}Copy Cell Contents";
public string LiberateEpisodesText => $"{Accelerator}Liberate All Episodes";
public string SetDownloadedText => $"Set Download status to '{Accelerator}Downloaded'";
public string SetNotDownloadedText => $"Set Download status to '{Accelerator}Not Downloaded'";
public string RemoveText => $"{Accelerator}Remove from library";
public string LocateFileText => $"{Accelerator}Locate file...";
public string LocateFileDialogTitle => $"Locate the audio file for '{GridEntry.Book.TitleWithSubtitle}'";
public string LocateFileErrorMessage => "Error saving book's location";
public string ConvertToMp3Text => $"{Accelerator}Convert to Mp3";
public string ReDownloadText => "Re-download this audiobook";
public string EditTemplatesText => "Edit Templates";
public string FolderTemplateText => "Folder Template";
public string FileTemplateText => "File Template";
public string MultipartTemplateText => "Multipart File Template";
public string ViewBookmarksText => "View _Bookmarks/Clips";
public string ViewSeriesText => GridEntry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series";
public bool LiberateEpisodesEnabled => GridEntry is ISeriesEntry sEntry && sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload);
public bool SetDownloadedEnabled => GridEntry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || GridEntry.Liberate.IsSeries;
public bool SetNotDownloadedEnabled => GridEntry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || GridEntry.Liberate.IsSeries;
public bool ConvertToMp3Enabled => GridEntry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated;
public bool ReDownloadEnabled => GridEntry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated;
public IGridEntry GridEntry { get; }
public char Accelerator { get; }
public GridContextMenu(IGridEntry gridEntry, char accelerator)
{
GridEntry = gridEntry;
Accelerator = accelerator;
}
public void SetDownloaded()
{
if (GridEntry is ISeriesEntry series)
{
series.Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
}
else
{
GridEntry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
}
}
public void SetNotDownloaded()
{
if (GridEntry is ISeriesEntry series)
{
series.Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
}
else
{
GridEntry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
}
}
public async Task RemoveAsync()
{
if (GridEntry is ISeriesEntry series)
{
await series.Children.Select(c => c.LibraryBook).RemoveBooksAsync();
}
else
{
await Task.Run(GridEntry.LibraryBook.RemoveBook);
}
}
public ITemplateEditor CreateTemplateEditor<T>(LibraryBook libraryBook, string existingTemplate)
where T : Templates, ITemplate, new()
{
LibraryBookDto fileDto = libraryBook.ToDto(), folderDto = fileDto;
if (libraryBook.Book.IsEpisodeChild() &&
Configuration.Instance.SavePodcastsToParentFolder &&
libraryBook.Book.SeriesLink.SingleOrDefault() is SeriesBook series)
{
using var context = DbContexts.GetContext();
var seriesParent = context.GetLibraryBook_Flat_NoTracking(series.Series.AudibleSeriesId);
folderDto = seriesParent?.ToDto() ?? fileDto;
}
return TemplateEditor<T>.CreateFilenameEditor(Configuration.Instance.Books, existingTemplate, folderDto, fileDto);
}
}
@@ -37,7 +37,7 @@ namespace LibationUiBase.GridView
private string _purchasedate; private string _purchasedate;
private string _length; private string _length;
private LastDownloadStatus _lastDownload; private LastDownloadStatus _lastDownload;
private object _cover; private Lazy<object> _lazyCover;
private string _series; private string _series;
private SeriesOrder _seriesOrder; private SeriesOrder _seriesOrder;
private string _title; private string _title;
@@ -55,7 +55,7 @@ namespace LibationUiBase.GridView
public string PurchaseDate { get => _purchasedate; protected set => RaiseAndSetIfChanged(ref _purchasedate, value); } public string PurchaseDate { get => _purchasedate; protected set => RaiseAndSetIfChanged(ref _purchasedate, value); }
public string Length { get => _length; protected set => RaiseAndSetIfChanged(ref _length, value); } public string Length { get => _length; protected set => RaiseAndSetIfChanged(ref _length, value); }
public LastDownloadStatus LastDownload { get => _lastDownload; protected set => RaiseAndSetIfChanged(ref _lastDownload, value); } public LastDownloadStatus LastDownload { get => _lastDownload; protected set => RaiseAndSetIfChanged(ref _lastDownload, value); }
public object Cover { get => _cover; private set => RaiseAndSetIfChanged(ref _cover, value); } public object Cover { get => _lazyCover.Value; }
public string Series { get => _series; private set => RaiseAndSetIfChanged(ref _series, value); } public string Series { get => _series; private set => RaiseAndSetIfChanged(ref _series, value); }
public SeriesOrder SeriesOrder { get => _seriesOrder; private set => RaiseAndSetIfChanged(ref _seriesOrder, value); } public SeriesOrder SeriesOrder { get => _seriesOrder; private set => RaiseAndSetIfChanged(ref _seriesOrder, value); }
public string Title { get => _title; private set => RaiseAndSetIfChanged(ref _title, value); } public string Title { get => _title; private set => RaiseAndSetIfChanged(ref _title, value); }
@@ -132,7 +132,7 @@ namespace LibationUiBase.GridView
int bookLenMins = GetLengthInMinutes(); int bookLenMins = GetLengthInMinutes();
return bookLenMins == 0 ? "" : $"{bookLenMins / 60} hr {bookLenMins % 60} min"; return bookLenMins == 0 ? "" : $"{bookLenMins / 60} hr {bookLenMins % 60} min";
} }
#endregion #endregion
#region detect changes to the model, update the view. #region detect changes to the model, update the view.
@@ -200,36 +200,31 @@ namespace LibationUiBase.GridView
#region Sorting #region Sorting
public GridEntry() public object GetMemberValue(string memberName) => memberName switch
{ {
memberValues = new() nameof(Remove) => Remove.HasValue ? Remove.Value ? RemoveStatus.Removed : RemoveStatus.NotRemoved : RemoveStatus.SomeRemoved,
{ nameof(Title) => Book.TitleSortable(),
{ nameof(Remove), () => Remove.HasValue ? Remove.Value ? RemoveStatus.Removed : RemoveStatus.NotRemoved : RemoveStatus.SomeRemoved }, nameof(Series) => Book.SeriesSortable(),
{ nameof(Title), () => Book.TitleSortable() }, nameof(SeriesOrder) => SeriesOrder,
{ nameof(Series), () => Book.SeriesSortable() }, nameof(Length) => GetLengthInMinutes(),
{ nameof(SeriesOrder), () => SeriesOrder }, nameof(MyRating) => Book.UserDefinedItem.Rating,
{ nameof(Length), () => GetLengthInMinutes() }, nameof(PurchaseDate) => GetPurchaseDate(),
{ nameof(MyRating), () => Book.UserDefinedItem.Rating }, nameof(ProductRating) => Book.Rating,
{ nameof(PurchaseDate), () => GetPurchaseDate() }, nameof(Authors) => Authors,
{ nameof(ProductRating), () => Book.Rating }, nameof(Narrators) => Narrators,
{ nameof(Authors), () => Authors }, nameof(Description) => Description,
{ nameof(Narrators), () => Narrators }, nameof(Category) => Category,
{ nameof(Description), () => Description }, nameof(Misc) => Misc,
{ nameof(Category), () => Category }, nameof(LastDownload) => LastDownload,
{ nameof(Misc), () => Misc }, nameof(BookTags) => BookTags ?? string.Empty,
{ nameof(LastDownload), () => LastDownload }, nameof(Liberate) => Liberate,
{ nameof(BookTags), () => BookTags ?? string.Empty }, nameof(DateAdded) => DateAdded,
{ nameof(Liberate), () => Liberate }, _ => null
{ nameof(DateAdded), () => DateAdded }, };
};
}
public object GetMemberValue(string memberName) => memberValues[memberName]();
public IComparer GetMemberComparer(Type memberType) public IComparer GetMemberComparer(Type memberType)
=> memberTypeComparers.TryGetValue(memberType, out IComparer value) ? value : memberTypeComparers[memberType.BaseType]; => memberTypeComparers.TryGetValue(memberType, out IComparer value) ? value : memberTypeComparers[memberType.BaseType];
private readonly Dictionary<string, Func<object>> memberValues;
// Instantiate comparers for every exposed member object type. // Instantiate comparers for every exposed member object type.
private static readonly Dictionary<Type, IComparer> memberTypeComparers = new() private static readonly Dictionary<Type, IComparer> memberTypeComparers = new()
{ {
@@ -258,7 +253,7 @@ namespace LibationUiBase.GridView
PictureStorage.PictureCached += PictureStorage_PictureCached; PictureStorage.PictureCached += PictureStorage_PictureCached;
// Mutable property. Set the field so PropertyChanged isn't fired. // Mutable property. Set the field so PropertyChanged isn't fired.
_cover = Liberate.LoadImage(picture); _lazyCover = new Lazy<object>(() => Liberate.LoadImage(picture));
} }
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e) private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
@@ -272,7 +267,8 @@ namespace LibationUiBase.GridView
// logic validation // logic validation
if (e.Definition.PictureId == Book.PictureId) if (e.Definition.PictureId == Book.PictureId)
{ {
Cover = Liberate.LoadImage(e.Picture); _lazyCover = new Lazy<object>(() => Liberate.LoadImage(e.Picture));
RaisePropertyChanged(nameof(Cover));
PictureStorage.PictureCached -= PictureStorage_PictureCached; PictureStorage.PictureCached -= PictureStorage_PictureCached;
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPublishable>true</IsPublishable> <IsPublishable>true</IsPublishable>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
@@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+48 -16
View File
@@ -229,7 +229,7 @@
badBookGb.Controls.Add(badBookAskRb); badBookGb.Controls.Add(badBookAskRb);
badBookGb.Location = new System.Drawing.Point(7, 6); badBookGb.Location = new System.Drawing.Point(7, 6);
badBookGb.Name = "badBookGb"; badBookGb.Name = "badBookGb";
badBookGb.Size = new System.Drawing.Size(836, 76); badBookGb.Size = new System.Drawing.Size(841, 76);
badBookGb.TabIndex = 13; badBookGb.TabIndex = 13;
badBookGb.TabStop = false; badBookGb.TabStop = false;
badBookGb.Text = "[bad book desc]"; badBookGb.Text = "[bad book desc]";
@@ -397,6 +397,7 @@
// //
// tab1ImportantSettings // tab1ImportantSettings
// //
tab1ImportantSettings.AutoScroll = true;
tab1ImportantSettings.Controls.Add(groupBox1); tab1ImportantSettings.Controls.Add(groupBox1);
tab1ImportantSettings.Controls.Add(booksGb); tab1ImportantSettings.Controls.Add(booksGb);
tab1ImportantSettings.Controls.Add(logsBtn); tab1ImportantSettings.Controls.Add(logsBtn);
@@ -412,6 +413,7 @@
// //
// groupBox1 // groupBox1
// //
groupBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
groupBox1.Controls.Add(applyDisplaySettingsBtn); groupBox1.Controls.Add(applyDisplaySettingsBtn);
groupBox1.Controls.Add(gridScaleFactorLbl); groupBox1.Controls.Add(gridScaleFactorLbl);
groupBox1.Controls.Add(gridScaleFactorTbar); groupBox1.Controls.Add(gridScaleFactorTbar);
@@ -426,6 +428,7 @@
// //
// applyDisplaySettingsBtn // applyDisplaySettingsBtn
// //
applyDisplaySettingsBtn.Anchor = System.Windows.Forms.AnchorStyles.Right;
applyDisplaySettingsBtn.Location = new System.Drawing.Point(689, 26); applyDisplaySettingsBtn.Location = new System.Drawing.Point(689, 26);
applyDisplaySettingsBtn.Name = "applyDisplaySettingsBtn"; applyDisplaySettingsBtn.Name = "applyDisplaySettingsBtn";
applyDisplaySettingsBtn.Size = new System.Drawing.Size(148, 34); applyDisplaySettingsBtn.Size = new System.Drawing.Size(148, 34);
@@ -554,6 +557,7 @@
// //
// tab2ImportLibrary // tab2ImportLibrary
// //
tab2ImportLibrary.AutoScroll = true;
tab2ImportLibrary.Controls.Add(autoDownloadEpisodesCb); tab2ImportLibrary.Controls.Add(autoDownloadEpisodesCb);
tab2ImportLibrary.Controls.Add(autoScanCb); tab2ImportLibrary.Controls.Add(autoScanCb);
tab2ImportLibrary.Controls.Add(showImportedStatsCb); tab2ImportLibrary.Controls.Add(showImportedStatsCb);
@@ -599,6 +603,7 @@
// //
// tab3DownloadDecrypt // tab3DownloadDecrypt
// //
tab3DownloadDecrypt.AutoScroll = true;
tab3DownloadDecrypt.Controls.Add(saveMetadataToFileCbox); tab3DownloadDecrypt.Controls.Add(saveMetadataToFileCbox);
tab3DownloadDecrypt.Controls.Add(useCoverAsFolderIconCb); tab3DownloadDecrypt.Controls.Add(useCoverAsFolderIconCb);
tab3DownloadDecrypt.Controls.Add(inProgressFilesGb); tab3DownloadDecrypt.Controls.Add(inProgressFilesGb);
@@ -764,6 +769,7 @@
// //
// tab4AudioFileOptions // tab4AudioFileOptions
// //
tab4AudioFileOptions.AutoScroll = true;
tab4AudioFileOptions.Controls.Add(fileDownloadQualityCb); tab4AudioFileOptions.Controls.Add(fileDownloadQualityCb);
tab4AudioFileOptions.Controls.Add(fileDownloadQualityLbl); tab4AudioFileOptions.Controls.Add(fileDownloadQualityLbl);
tab4AudioFileOptions.Controls.Add(combineNestedChapterTitlesCbox); tab4AudioFileOptions.Controls.Add(combineNestedChapterTitlesCbox);
@@ -845,7 +851,7 @@
audiobookFixupsGb.Controls.Add(stripAudibleBrandingCbox); audiobookFixupsGb.Controls.Add(stripAudibleBrandingCbox);
audiobookFixupsGb.Location = new System.Drawing.Point(6, 200); audiobookFixupsGb.Location = new System.Drawing.Point(6, 200);
audiobookFixupsGb.Name = "audiobookFixupsGb"; audiobookFixupsGb.Name = "audiobookFixupsGb";
audiobookFixupsGb.Size = new System.Drawing.Size(403, 182); audiobookFixupsGb.Size = new System.Drawing.Size(385, 182);
audiobookFixupsGb.TabIndex = 19; audiobookFixupsGb.TabIndex = 19;
audiobookFixupsGb.TabStop = false; audiobookFixupsGb.TabStop = false;
audiobookFixupsGb.Text = "Audiobook Fix-ups"; audiobookFixupsGb.Text = "Audiobook Fix-ups";
@@ -872,6 +878,7 @@
// //
// chapterTitleTemplateGb // chapterTitleTemplateGb
// //
chapterTitleTemplateGb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
chapterTitleTemplateGb.Controls.Add(chapterTitleTemplateBtn); chapterTitleTemplateGb.Controls.Add(chapterTitleTemplateBtn);
chapterTitleTemplateGb.Controls.Add(chapterTitleTemplateTb); chapterTitleTemplateGb.Controls.Add(chapterTitleTemplateTb);
chapterTitleTemplateGb.Location = new System.Drawing.Point(3, 388); chapterTitleTemplateGb.Location = new System.Drawing.Point(3, 388);
@@ -903,6 +910,7 @@
// //
// lameOptionsGb // lameOptionsGb
// //
lameOptionsGb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
lameOptionsGb.Controls.Add(label20); lameOptionsGb.Controls.Add(label20);
lameOptionsGb.Controls.Add(label21); lameOptionsGb.Controls.Add(label21);
lameOptionsGb.Controls.Add(encoderQualityCb); lameOptionsGb.Controls.Add(encoderQualityCb);
@@ -912,9 +920,9 @@
lameOptionsGb.Controls.Add(label1); lameOptionsGb.Controls.Add(label1);
lameOptionsGb.Controls.Add(lameQualityGb); lameOptionsGb.Controls.Add(lameQualityGb);
lameOptionsGb.Controls.Add(groupBox2); lameOptionsGb.Controls.Add(groupBox2);
lameOptionsGb.Location = new System.Drawing.Point(415, 6); lameOptionsGb.Location = new System.Drawing.Point(397, 6);
lameOptionsGb.Name = "lameOptionsGb"; lameOptionsGb.Name = "lameOptionsGb";
lameOptionsGb.Size = new System.Drawing.Size(433, 376); lameOptionsGb.Size = new System.Drawing.Size(450, 376);
lameOptionsGb.TabIndex = 14; lameOptionsGb.TabIndex = 14;
lameOptionsGb.TabStop = false; lameOptionsGb.TabStop = false;
lameOptionsGb.Text = "Mp3 Encoding Options"; lameOptionsGb.Text = "Mp3 Encoding Options";
@@ -930,6 +938,7 @@
// //
// label21 // label21
// //
label21.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label21.AutoSize = true; label21.AutoSize = true;
label21.Location = new System.Drawing.Point(239, 89); label21.Location = new System.Drawing.Point(239, 89);
label21.Name = "label21"; label21.Name = "label21";
@@ -939,33 +948,37 @@
// //
// encoderQualityCb // encoderQualityCb
// //
encoderQualityCb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
encoderQualityCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; encoderQualityCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
encoderQualityCb.FormattingEnabled = true; encoderQualityCb.FormattingEnabled = true;
encoderQualityCb.Location = new System.Drawing.Point(337, 86); encoderQualityCb.Location = new System.Drawing.Point(337, 86);
encoderQualityCb.Name = "encoderQualityCb"; encoderQualityCb.Name = "encoderQualityCb";
encoderQualityCb.Size = new System.Drawing.Size(90, 23); encoderQualityCb.Size = new System.Drawing.Size(107, 23);
encoderQualityCb.TabIndex = 2; encoderQualityCb.TabIndex = 2;
// //
// maxSampleRateCb // maxSampleRateCb
// //
maxSampleRateCb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
maxSampleRateCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; maxSampleRateCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
maxSampleRateCb.FormattingEnabled = true; maxSampleRateCb.FormattingEnabled = true;
maxSampleRateCb.Location = new System.Drawing.Point(119, 86); maxSampleRateCb.Location = new System.Drawing.Point(119, 86);
maxSampleRateCb.Name = "maxSampleRateCb"; maxSampleRateCb.Name = "maxSampleRateCb";
maxSampleRateCb.Size = new System.Drawing.Size(101, 23); maxSampleRateCb.Size = new System.Drawing.Size(76, 23);
maxSampleRateCb.TabIndex = 2; maxSampleRateCb.TabIndex = 2;
// //
// lameDownsampleMonoCbox // lameDownsampleMonoCbox
// //
lameDownsampleMonoCbox.Location = new System.Drawing.Point(237, 30); lameDownsampleMonoCbox.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
lameDownsampleMonoCbox.Location = new System.Drawing.Point(247, 29);
lameDownsampleMonoCbox.Name = "lameDownsampleMonoCbox"; lameDownsampleMonoCbox.Name = "lameDownsampleMonoCbox";
lameDownsampleMonoCbox.Size = new System.Drawing.Size(184, 34); lameDownsampleMonoCbox.Size = new System.Drawing.Size(197, 34);
lameDownsampleMonoCbox.TabIndex = 1; lameDownsampleMonoCbox.TabIndex = 1;
lameDownsampleMonoCbox.Text = "Downsample stereo to mono?\r\n(Recommended)\r\n"; lameDownsampleMonoCbox.Text = "Downsample stereo to mono?\r\n(Recommended)\r\n";
lameDownsampleMonoCbox.UseVisualStyleBackColor = true; lameDownsampleMonoCbox.UseVisualStyleBackColor = true;
// //
// lameBitrateGb // lameBitrateGb
// //
lameBitrateGb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
lameBitrateGb.Controls.Add(LameMatchSourceBRCbox); lameBitrateGb.Controls.Add(LameMatchSourceBRCbox);
lameBitrateGb.Controls.Add(lameConstantBitrateCbox); lameBitrateGb.Controls.Add(lameConstantBitrateCbox);
lameBitrateGb.Controls.Add(label7); lameBitrateGb.Controls.Add(label7);
@@ -977,15 +990,16 @@
lameBitrateGb.Controls.Add(lameBitrateTb); lameBitrateGb.Controls.Add(lameBitrateTb);
lameBitrateGb.Location = new System.Drawing.Point(6, 116); lameBitrateGb.Location = new System.Drawing.Point(6, 116);
lameBitrateGb.Name = "lameBitrateGb"; lameBitrateGb.Name = "lameBitrateGb";
lameBitrateGb.Size = new System.Drawing.Size(421, 113); lameBitrateGb.Size = new System.Drawing.Size(438, 113);
lameBitrateGb.TabIndex = 0; lameBitrateGb.TabIndex = 0;
lameBitrateGb.TabStop = false; lameBitrateGb.TabStop = false;
lameBitrateGb.Text = "Bitrate"; lameBitrateGb.Text = "Bitrate";
// //
// LameMatchSourceBRCbox // LameMatchSourceBRCbox
// //
LameMatchSourceBRCbox.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
LameMatchSourceBRCbox.AutoSize = true; LameMatchSourceBRCbox.AutoSize = true;
LameMatchSourceBRCbox.Location = new System.Drawing.Point(275, 76); LameMatchSourceBRCbox.Location = new System.Drawing.Point(254, 76);
LameMatchSourceBRCbox.Name = "LameMatchSourceBRCbox"; LameMatchSourceBRCbox.Name = "LameMatchSourceBRCbox";
LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19); LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19);
LameMatchSourceBRCbox.TabIndex = 3; LameMatchSourceBRCbox.TabIndex = 3;
@@ -1005,6 +1019,7 @@
// //
// label7 // label7
// //
label7.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label7.AutoSize = true; label7.AutoSize = true;
label7.BackColor = System.Drawing.SystemColors.ControlLightLight; label7.BackColor = System.Drawing.SystemColors.ControlLightLight;
label7.Location = new System.Drawing.Point(390, 52); label7.Location = new System.Drawing.Point(390, 52);
@@ -1015,6 +1030,7 @@
// //
// label6 // label6
// //
label6.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label6.AutoSize = true; label6.AutoSize = true;
label6.BackColor = System.Drawing.SystemColors.ControlLightLight; label6.BackColor = System.Drawing.SystemColors.ControlLightLight;
label6.Location = new System.Drawing.Point(309, 52); label6.Location = new System.Drawing.Point(309, 52);
@@ -1025,6 +1041,7 @@
// //
// label5 // label5
// //
label5.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label5.AutoSize = true; label5.AutoSize = true;
label5.BackColor = System.Drawing.SystemColors.ControlLightLight; label5.BackColor = System.Drawing.SystemColors.ControlLightLight;
label5.Location = new System.Drawing.Point(228, 52); label5.Location = new System.Drawing.Point(228, 52);
@@ -1035,6 +1052,7 @@
// //
// label4 // label4
// //
label4.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label4.AutoSize = true; label4.AutoSize = true;
label4.BackColor = System.Drawing.SystemColors.ControlLightLight; label4.BackColor = System.Drawing.SystemColors.ControlLightLight;
label4.Location = new System.Drawing.Point(147, 52); label4.Location = new System.Drawing.Point(147, 52);
@@ -1045,6 +1063,7 @@
// //
// label11 // label11
// //
label11.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label11.AutoSize = true; label11.AutoSize = true;
label11.BackColor = System.Drawing.SystemColors.ControlLightLight; label11.BackColor = System.Drawing.SystemColors.ControlLightLight;
label11.Location = new System.Drawing.Point(10, 52); label11.Location = new System.Drawing.Point(10, 52);
@@ -1055,6 +1074,7 @@
// //
// label3 // label3
// //
label3.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label3.AutoSize = true; label3.AutoSize = true;
label3.BackColor = System.Drawing.SystemColors.ControlLightLight; label3.BackColor = System.Drawing.SystemColors.ControlLightLight;
label3.Location = new System.Drawing.Point(71, 52); label3.Location = new System.Drawing.Point(71, 52);
@@ -1071,7 +1091,7 @@
lameBitrateTb.Maximum = 320; lameBitrateTb.Maximum = 320;
lameBitrateTb.Minimum = 16; lameBitrateTb.Minimum = 16;
lameBitrateTb.Name = "lameBitrateTb"; lameBitrateTb.Name = "lameBitrateTb";
lameBitrateTb.Size = new System.Drawing.Size(409, 45); lameBitrateTb.Size = new System.Drawing.Size(408, 45);
lameBitrateTb.SmallChange = 8; lameBitrateTb.SmallChange = 8;
lameBitrateTb.TabIndex = 0; lameBitrateTb.TabIndex = 0;
lameBitrateTb.TickFrequency = 16; lameBitrateTb.TickFrequency = 16;
@@ -1090,6 +1110,7 @@
// //
// lameQualityGb // lameQualityGb
// //
lameQualityGb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
lameQualityGb.Controls.Add(label19); lameQualityGb.Controls.Add(label19);
lameQualityGb.Controls.Add(label18); lameQualityGb.Controls.Add(label18);
lameQualityGb.Controls.Add(label17); lameQualityGb.Controls.Add(label17);
@@ -1105,13 +1126,14 @@
lameQualityGb.Controls.Add(lameVBRQualityTb); lameQualityGb.Controls.Add(lameVBRQualityTb);
lameQualityGb.Location = new System.Drawing.Point(6, 235); lameQualityGb.Location = new System.Drawing.Point(6, 235);
lameQualityGb.Name = "lameQualityGb"; lameQualityGb.Name = "lameQualityGb";
lameQualityGb.Size = new System.Drawing.Size(421, 109); lameQualityGb.Size = new System.Drawing.Size(438, 109);
lameQualityGb.TabIndex = 0; lameQualityGb.TabIndex = 0;
lameQualityGb.TabStop = false; lameQualityGb.TabStop = false;
lameQualityGb.Text = "Quality"; lameQualityGb.Text = "Quality";
// //
// label19 // label19
// //
label19.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label19.AutoSize = true; label19.AutoSize = true;
label19.Location = new System.Drawing.Point(349, 52); label19.Location = new System.Drawing.Point(349, 52);
label19.Name = "label19"; label19.Name = "label19";
@@ -1121,6 +1143,7 @@
// //
// label18 // label18
// //
label18.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label18.AutoSize = true; label18.AutoSize = true;
label18.Location = new System.Drawing.Point(307, 52); label18.Location = new System.Drawing.Point(307, 52);
label18.Name = "label18"; label18.Name = "label18";
@@ -1130,6 +1153,7 @@
// //
// label17 // label17
// //
label17.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label17.AutoSize = true; label17.AutoSize = true;
label17.Location = new System.Drawing.Point(265, 52); label17.Location = new System.Drawing.Point(265, 52);
label17.Name = "label17"; label17.Name = "label17";
@@ -1139,6 +1163,7 @@
// //
// label16 // label16
// //
label16.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label16.AutoSize = true; label16.AutoSize = true;
label16.Location = new System.Drawing.Point(223, 52); label16.Location = new System.Drawing.Point(223, 52);
label16.Name = "label16"; label16.Name = "label16";
@@ -1148,6 +1173,7 @@
// //
// label12 // label12
// //
label12.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label12.AutoSize = true; label12.AutoSize = true;
label12.Location = new System.Drawing.Point(182, 52); label12.Location = new System.Drawing.Point(182, 52);
label12.Name = "label12"; label12.Name = "label12";
@@ -1157,6 +1183,7 @@
// //
// label15 // label15
// //
label15.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label15.AutoSize = true; label15.AutoSize = true;
label15.Location = new System.Drawing.Point(140, 52); label15.Location = new System.Drawing.Point(140, 52);
label15.Name = "label15"; label15.Name = "label15";
@@ -1166,6 +1193,7 @@
// //
// label9 // label9
// //
label9.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label9.AutoSize = true; label9.AutoSize = true;
label9.Location = new System.Drawing.Point(97, 52); label9.Location = new System.Drawing.Point(97, 52);
label9.Name = "label9"; label9.Name = "label9";
@@ -1175,6 +1203,7 @@
// //
// label8 // label8
// //
label8.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label8.AutoSize = true; label8.AutoSize = true;
label8.Location = new System.Drawing.Point(391, 52); label8.Location = new System.Drawing.Point(391, 52);
label8.Name = "label8"; label8.Name = "label8";
@@ -1202,6 +1231,7 @@
// //
// label14 // label14
// //
label14.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label14.AutoSize = true; label14.AutoSize = true;
label14.Location = new System.Drawing.Point(56, 52); label14.Location = new System.Drawing.Point(56, 52);
label14.Name = "label14"; label14.Name = "label14";
@@ -1211,6 +1241,7 @@
// //
// label2 // label2
// //
label2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label2.AutoSize = true; label2.AutoSize = true;
label2.Location = new System.Drawing.Point(14, 52); label2.Location = new System.Drawing.Point(14, 52);
label2.Name = "label2"; label2.Name = "label2";
@@ -1225,25 +1256,27 @@
lameVBRQualityTb.Location = new System.Drawing.Point(10, 22); lameVBRQualityTb.Location = new System.Drawing.Point(10, 22);
lameVBRQualityTb.Maximum = 9; lameVBRQualityTb.Maximum = 9;
lameVBRQualityTb.Name = "lameVBRQualityTb"; lameVBRQualityTb.Name = "lameVBRQualityTb";
lameVBRQualityTb.Size = new System.Drawing.Size(405, 45); lameVBRQualityTb.Size = new System.Drawing.Size(404, 45);
lameVBRQualityTb.TabIndex = 0; lameVBRQualityTb.TabIndex = 0;
lameVBRQualityTb.Value = 9; lameVBRQualityTb.Value = 9;
// //
// groupBox2 // groupBox2
// //
groupBox2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
groupBox2.Controls.Add(lameTargetQualityRb); groupBox2.Controls.Add(lameTargetQualityRb);
groupBox2.Controls.Add(lameTargetBitrateRb); groupBox2.Controls.Add(lameTargetBitrateRb);
groupBox2.Location = new System.Drawing.Point(6, 22); groupBox2.Location = new System.Drawing.Point(6, 22);
groupBox2.Name = "groupBox2"; groupBox2.Name = "groupBox2";
groupBox2.Size = new System.Drawing.Size(214, 58); groupBox2.Size = new System.Drawing.Size(189, 58);
groupBox2.TabIndex = 0; groupBox2.TabIndex = 0;
groupBox2.TabStop = false; groupBox2.TabStop = false;
groupBox2.Text = "Target"; groupBox2.Text = "Target";
// //
// lameTargetQualityRb // lameTargetQualityRb
// //
lameTargetQualityRb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
lameTargetQualityRb.AutoSize = true; lameTargetQualityRb.AutoSize = true;
lameTargetQualityRb.Location = new System.Drawing.Point(139, 22); lameTargetQualityRb.Location = new System.Drawing.Point(118, 22);
lameTargetQualityRb.Name = "lameTargetQualityRb"; lameTargetQualityRb.Name = "lameTargetQualityRb";
lameTargetQualityRb.Size = new System.Drawing.Size(63, 19); lameTargetQualityRb.Size = new System.Drawing.Size(63, 19);
lameTargetQualityRb.TabIndex = 0; lameTargetQualityRb.TabIndex = 0;
@@ -1321,7 +1354,6 @@
Controls.Add(tabControl); Controls.Add(tabControl);
Controls.Add(cancelBtn); Controls.Add(cancelBtn);
Controls.Add(saveBtn); Controls.Add(saveBtn);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
MaximizeBox = false; MaximizeBox = false;
MinimizeBox = false; MinimizeBox = false;
@@ -18,7 +18,7 @@
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing"">Blue</data> <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value> <value>[base64 mime encoded serialized .NET Framework object]</value>
</data> </data>
@@ -16,7 +16,27 @@ namespace LibationWinForms.GridView
internal class LiberateDataGridViewImageButtonCell : DataGridViewImageButtonCell internal class LiberateDataGridViewImageButtonCell : DataGridViewImageButtonCell
{ {
private static readonly Brush DISABLED_GRAY = new SolidBrush(Color.FromArgb(0x60, Color.LightGray)); #region Accessibility
private string accessibilityName => "Liberate Image Button";
private string accessibilityDescription = "undefined";
protected override AccessibleObject CreateAccessibilityInstance() => new MyAccessibilityObject(accessibilityName, accessibilityDescription);
protected class MyAccessibilityObject : DataGridViewCellAccessibleObject
{
public override string Name => _name;
public override string Description => _description;
private string _name { get; }
private string _description { get; }
public MyAccessibilityObject(string name, string description) : base()
{
_name = name;
_description = description;
}
}
#endregion
private static readonly Brush DISABLED_GRAY = new SolidBrush(Color.FromArgb(0x60, Color.LightGray));
private static readonly Color HiddenForeColor = Color.LightGray; private static readonly Color HiddenForeColor = Color.LightGray;
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{ {
@@ -31,7 +51,8 @@ namespace LibationWinForms.GridView
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
DrawButtonImage(graphics, (Image)status.ButtonImage, cellBounds); DrawButtonImage(graphics, (Image)status.ButtonImage, cellBounds);
ToolTipText = status.ToolTip; accessibilityDescription = status.ToolTip;
ToolTipText = status.ToolTip;
if (status.IsUnavailable || status.Opacity < 1) if (status.IsUnavailable || status.Opacity < 1)
graphics.FillRectangle(DISABLED_GRAY, cellBounds); graphics.FillRectangle(DISABLED_GRAY, cellBounds);
@@ -102,19 +102,19 @@ namespace LibationWinForms.GridView
private void productsGrid_CellContextMenuStripNeeded(IGridEntry entry, ContextMenuStrip ctxMenu) private void productsGrid_CellContextMenuStripNeeded(IGridEntry entry, ContextMenuStrip ctxMenu)
{ {
var ctx = new GridContextMenu(entry, '&');
#region Liberate all Episodes #region Liberate all Episodes
if (entry.Liberate.IsSeries) if (entry.Liberate.IsSeries)
{ {
var liberateEpisodesMenuItem = new ToolStripMenuItem() var liberateEpisodesMenuItem = new ToolStripMenuItem()
{ {
Text = "&Liberate All Episodes", Text = ctx.LiberateEpisodesText,
Enabled = ((ISeriesEntry)entry).Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload) Enabled = ctx.LiberateEpisodesEnabled
}; };
ctxMenu.Items.Add(liberateEpisodesMenuItem);
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry); liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry);
ctxMenu.Items.Add(liberateEpisodesMenuItem);
} }
#endregion #endregion
@@ -122,61 +122,44 @@ namespace LibationWinForms.GridView
var setDownloadMenuItem = new ToolStripMenuItem() var setDownloadMenuItem = new ToolStripMenuItem()
{ {
Text = "Set Download status to '&Downloaded'", Text = ctx.SetDownloadedText,
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || entry.Liberate.IsSeries Enabled = ctx.SetDownloadedEnabled
}; };
setDownloadMenuItem.Click += (_, _) => ctx.SetDownloaded();
ctxMenu.Items.Add(setDownloadMenuItem); ctxMenu.Items.Add(setDownloadMenuItem);
if (entry.Liberate.IsSeries)
setDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
else
setDownloadMenuItem.Click += (_, _) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
#endregion #endregion
#region Set Download status to Not Downloaded #region Set Download status to Not Downloaded
var setNotDownloadMenuItem = new ToolStripMenuItem() var setNotDownloadMenuItem = new ToolStripMenuItem()
{ {
Text = "Set Download status to '&Not Downloaded'", Text = ctx.SetNotDownloadedText,
Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || entry.Liberate.IsSeries Enabled = ctx.SetNotDownloadedEnabled
}; };
setNotDownloadMenuItem.Click += (_, _) => ctx.SetNotDownloaded();
ctxMenu.Items.Add(setNotDownloadMenuItem); ctxMenu.Items.Add(setNotDownloadMenuItem);
if (entry.Liberate.IsSeries)
setNotDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
else
setNotDownloadMenuItem.Click += (_, _) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
#endregion #endregion
#region Remove from library #region Remove from library
var removeMenuItem = new ToolStripMenuItem() { Text = "&Remove from library" }; var removeMenuItem = new ToolStripMenuItem() { Text = ctx.RemoveText };
removeMenuItem.Click += async (_, _) => await ctx.RemoveAsync();
ctxMenu.Items.Add(removeMenuItem); ctxMenu.Items.Add(removeMenuItem);
if (entry.Liberate.IsSeries)
removeMenuItem.Click += async (_, _) => await ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).RemoveBooksAsync();
else
removeMenuItem.Click += async (_, _) => await Task.Run(entry.LibraryBook.RemoveBook);
#endregion #endregion
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
#region Locate file #region Locate file
var locateFileMenuItem = new ToolStripMenuItem() { Text = "&Locate file..." }; var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText };
ctxMenu.Items.Add(locateFileMenuItem); ctxMenu.Items.Add(locateFileMenuItem);
locateFileMenuItem.Click += (_, _) => locateFileMenuItem.Click += (_, _) =>
{ {
try try
{ {
var openFileDialog = new OpenFileDialog var openFileDialog = new OpenFileDialog
{ {
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'", Title = ctx.LocateFileDialogTitle,
Filter = "All files (*.*)|*.*", Filter = "All files (*.*)|*.*",
FilterIndex = 1 FilterIndex = 1
}; };
@@ -185,8 +168,7 @@ namespace LibationWinForms.GridView
} }
catch (Exception ex) catch (Exception ex)
{ {
var msg = "Error saving book's location"; MessageBoxLib.ShowAdminAlert(this, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
MessageBoxLib.ShowAdminAlert(this, msg, msg, ex);
} }
}; };
@@ -195,13 +177,11 @@ namespace LibationWinForms.GridView
var convertToMp3MenuItem = new ToolStripMenuItem var convertToMp3MenuItem = new ToolStripMenuItem
{ {
Text = "&Convert to Mp3", Text = ctx.ConvertToMp3Text,
Enabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated Enabled = ctx.ConvertToMp3Enabled
}; };
ctxMenu.Items.Add(convertToMp3MenuItem);
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook); convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
ctxMenu.Items.Add(convertToMp3MenuItem);
#endregion #endregion
} }
@@ -211,10 +191,9 @@ namespace LibationWinForms.GridView
{ {
var reDownloadMenuItem = new ToolStripMenuItem() var reDownloadMenuItem = new ToolStripMenuItem()
{ {
Text = "Re-download this audiobook", Text = ctx.ReDownloadText,
Enabled = entry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated Enabled = ctx.ReDownloadEnabled
}; };
ctxMenu.Items.Add(reDownloadMenuItem); ctxMenu.Items.Add(reDownloadMenuItem);
reDownloadMenuItem.Click += (s, _) => reDownloadMenuItem.Click += (s, _) =>
{ {
@@ -223,6 +202,35 @@ namespace LibationWinForms.GridView
LiberateClicked?.Invoke(s, entry.LibraryBook); LiberateClicked?.Invoke(s, entry.LibraryBook);
}; };
} }
#endregion
#region Edit Templates
void editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
where T : Templates, LibationFileManager.ITemplate, new()
{
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
var form = new EditTemplateDialog(template);
if (form.ShowDialog(this) == DialogResult.OK)
{
setNewTemplate(template.EditingTemplate.TemplateText);
}
}
if (!entry.Liberate.IsSeries)
{
var folderTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FolderTemplateText };
var fileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FileTemplateText };
var multiFileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.MultipartTemplateText };
folderTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FolderTemplate>(entry.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t);
fileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FileTemplate>(entry.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t);
multiFileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.ChapterFileTemplate>(entry.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t);
var editTemplatesMenuItem = new ToolStripMenuItem { Text = ctx.EditTemplatesText };
editTemplatesMenuItem.DropDownItems.AddRange(new[] { folderTemplateMenuItem, fileTemplateMenuItem, multiFileTemplateMenuItem });
ctxMenu.Items.Add(new ToolStripSeparator());
ctxMenu.Items.Add(editTemplatesMenuItem);
}
#endregion #endregion
ctxMenu.Items.Add(new ToolStripSeparator()); ctxMenu.Items.Add(new ToolStripSeparator());
@@ -231,11 +239,9 @@ namespace LibationWinForms.GridView
if (!entry.Liberate.IsSeries) if (!entry.Liberate.IsSeries)
{ {
var bookRecordMenuItem = new ToolStripMenuItem { Text = "View &Bookmarks/Clips" }; var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText };
ctxMenu.Items.Add(bookRecordMenuItem);
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this); bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
ctxMenu.Items.Add(bookRecordMenuItem);
} }
#endregion #endregion
@@ -243,13 +249,9 @@ namespace LibationWinForms.GridView
if (entry.Book.SeriesLink.Any()) if (entry.Book.SeriesLink.Any())
{ {
var header = entry.Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series"; var viewSeriesMenuItem = new ToolStripMenuItem { Text = ctx.ViewSeriesText };
var viewSeriesMenuItem = new ToolStripMenuItem { Text = header };
ctxMenu.Items.Add(viewSeriesMenuItem);
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show(); viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
ctxMenu.Items.Add(viewSeriesMenuItem);
} }
#endregion #endregion
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>libation.ico</ApplicationIcon> <ApplicationIcon>libation.ico</ApplicationIcon>
@@ -17,6 +17,10 @@
<!-- Version is now in AppScaffolding.csproj --> <!-- Version is now in AppScaffolding.csproj -->
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Net.DisableIPv6" Value="true" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
@@ -37,7 +41,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.3.0.1" /> <PackageReference Include="Dinah.Core.WindowsDesktop" Version="8.0.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -250,7 +250,7 @@ namespace LibationWinForms.ProcessQueue
private byte[] AudioDecodable_RequestCoverArt(object sender, EventArgs e) private byte[] AudioDecodable_RequestCoverArt(object sender, EventArgs e)
{ {
var quality var quality
= Configuration.Instance.FileDownloadQuality == Configuration.DownloadQuality.High = Configuration.Instance.FileDownloadQuality == Configuration.DownloadQuality.High && LibraryBook.Book.PictureLarge is not null
? new PictureDefinition(LibraryBook.Book.PictureLarge, PictureSize.Native) ? new PictureDefinition(LibraryBook.Book.PictureLarge, PictureSize.Native)
: new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._500x500); : new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._500x500);
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\bin\Publish\classic</PublishDir> <PublishDir>..\bin\Publish\classic</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\..\bin\Publish\Linux-chardonnay</PublishDir> <PublishDir>..\..\bin\Publish\Linux-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -1,28 +1,39 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256.0px" height="256.0px" viewBox="0 0 512 512" enable-background="new 0 0 512 512"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 524 524" enable-background="new 0 0 524 524">
<path id="glass" d= <defs>
"M139,2 <g id="glass">
A 192,200 0 0 0 103,84 <path fill-rule="evenodd" d=
A 222,334 41 0 0 241,320 "M262,8
V478 h-117
H160 a 192,200 0 0 0 -36,82
A 16,16 0 0 0 160,510 a 222,334 41 0 0 138,236
H352 v158
A16 16 0 0 0 352,478 h-81
H271 a 16,16 0 0 0 0,32
V320 h192
A 222,334 -41 0 0 409,84 a 16 16 0 0 0 0,-32
A 192,200 0 0 0 373,2 h-81
M355,32 v-158
A 192,200 0 0 1 381,127 a 222,334 -41 0 0 138,-236
A 187.5,334 -35 0 1 256,286 a 192,200 0 0 0 -36,-82
A 187.5,334 35 0 1 131,127 h-117
A 192,200 0 0 1 157,32 m-99,30
H355 a 192,200 0 0 0 -26,95
z" /> a 187.5,334 35 0 0 125,159
a 187.5,334 -35 0 0 125,-159
<path id="wine-level" d= a 192,200 0 0 0 -26,-95
"M146,128 h-198
A 168,300 35 0 0 256,270 z"/>
A 168,300 -35 0 0 366,128 </g>
z"/> <g id="wine-level">
<path d=
"M158,136
a 168,305 35 0 0 104,136
a 168,305 -35 0 0 104,-136
z"/>
</g>
</defs>
<use href="#glass" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#wine-level" stroke="#ffffffa0" stroke-width="16" fill="Transparent" />
<use href="#glass" fill="Black" />
<use href="#wine-level" fill="Black" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 968 B

@@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<!--<TargetFramework>net7.0-macos</TargetFramework>--> <!--<TargetFramework>net8.0-macos</TargetFramework>-->
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier> <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\..\bin\Publish\MacOS-chardonnay</PublishDir> <PublishDir>..\..\bin\Publish\MacOS-chardonnay</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier> <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<PublishDir>..\..\bin\Publish\classic</PublishDir> <PublishDir>..\..\bin\Publish\classic</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
@@ -26,7 +26,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1901.177" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2210.55" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+9
View File
@@ -0,0 +1,9 @@
For reasons I cannot figure out, upgrading the major .net version breaks Libation until these were done. Update these to the same version of .net as the app and release their nugets:
* https://github.com/rmcrackan/Dinah.Core
* https://github.com/rmcrackan/AudibleApi
* https://github.com/rmcrackan/LuceneNet303r2
* https://github.com/Mbucari/AAXClean.Codecs
* https://github.com/Mbucari/AAXClean
* https://github.com/Mbucari/serilog-sinks-zipfile
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
@@ -19,7 +19,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.3.0.1" /> <PackageReference Include="Dinah.Core" Version="8.0.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows7.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
@@ -14,8 +14,6 @@ using FluentAssertions;
using FluentAssertions.Common; using FluentAssertions.Common;
using Microsoft.VisualStudio.TestPlatform.Common.Filtering; using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Protected;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -1,17 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> <PackageReference Include="coverlet.collector" Version="6.0.1">
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> <PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.0"> <PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> <PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.0"> <PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> <PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.0"> <PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -1,18 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> <PackageReference Include="coverlet.collector" Version="6.0.1">
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -13,8 +13,6 @@ using LibationSearchEngine;
using Lucene.Net.Analysis.Standard; using Lucene.Net.Analysis.Standard;
using Microsoft.VisualStudio.TestPlatform.Common.Filtering; using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Protected;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;