Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cb22cfd24 | ||
|
|
e911344850 | ||
|
|
8ec7e5a9d2 | ||
|
|
e1f749c3da | ||
|
|
ba060d15aa | ||
|
|
93fde236c8 | ||
|
|
13aad1a7cb | ||
|
|
65c64c4504 | ||
|
|
14ba04c28b | ||
|
|
96e886d207 | ||
|
|
c7279574a9 | ||
|
|
a522e1ff7e | ||
|
|
9ebc4444bd | ||
|
|
525afdf050 | ||
|
|
983cdf6ad5 | ||
|
|
09bb32a435 | ||
|
|
817ef33fbd | ||
|
|
be52b496a6 | ||
|
|
c0c99db6fa | ||
|
|
a1c8fb5921 | ||
|
|
4576c0e193 | ||
|
|
d592e9435e | ||
|
|
9ce6cb54ab | ||
|
|
c15d49fc64 | ||
|
|
99be869aa9 | ||
|
|
a0e875a79c | ||
|
|
6134becc70 | ||
|
|
eadf7cff79 | ||
|
|
87ca76f9cb | ||
|
|
43d1019059 | ||
|
|
ed87ded77a | ||
|
|
56d4205360 | ||
|
|
1f839606ae | ||
|
|
6eebe652d4 | ||
|
|
5fff22a0e1 | ||
|
|
cd7040cdc7 | ||
|
|
97b792868f | ||
|
|
984f931f67 | ||
|
|
e0dd9b845a | ||
|
|
f1c8b320c2 | ||
|
|
9b7d0cd909 | ||
|
|
99592ff84e | ||
|
|
f97cfe77f9 | ||
|
|
2954cb961b | ||
|
|
1e29b98b82 | ||
|
|
8b76da0dbe | ||
|
|
0a749d2d88 | ||
|
|
9ed6c1fd0d | ||
|
|
9825e2b552 | ||
|
|
011efe3676 | ||
|
|
2bdcc221f5 | ||
|
|
21bedca367 | ||
|
|
074fe79ded | ||
|
|
ac8c090c4c | ||
|
|
ade693bebb | ||
|
|
9bc53e45cd | ||
|
|
7d4eaa11e7 | ||
|
|
4521c5d5ed | ||
|
|
eb39f994e1 | ||
|
|
c19833b34e | ||
|
|
6dcf456d06 | ||
|
|
8a87462cf5 | ||
|
|
9da2a44eff | ||
|
|
7af8d8aa70 | ||
|
|
4801f37e7c | ||
|
|
4f5df44d40 | ||
|
|
63e28b13c1 | ||
|
|
f92b2b65b2 | ||
|
|
f7a4a95e3b | ||
|
|
71b8e9e51c | ||
|
|
c6788ccb48 | ||
|
|
0503ee1404 | ||
|
|
303192c6c3 | ||
|
|
6e21e96aa2 | ||
|
|
d1d0a7e487 | ||
|
|
2fd8ea91e1 | ||
|
|
92ee0b2e6d | ||
|
|
f0b5ae1cdc | ||
|
|
eb659cc7d7 | ||
|
|
cdd5f229d3 | ||
|
|
29edfb7c3f | ||
|
|
c0cb454d45 | ||
|
|
970a77c9e9 | ||
|
|
3488c8e0f5 | ||
|
|
5133720cc8 | ||
|
|
4150746f45 | ||
|
|
3a95e1e72f | ||
|
|
c74e26e1af | ||
|
|
ae54c95d46 | ||
|
|
56e6bd164b | ||
|
|
9b28bdceaa | ||
|
|
b7f7d9004d | ||
|
|
1be0991e62 | ||
|
|
ff8a2e59c5 |
30
.github/workflows/build-linux.yml
vendored
@@ -8,37 +8,37 @@ on:
|
||||
inputs:
|
||||
version_override:
|
||||
type: string
|
||||
description: 'Version number override'
|
||||
description: "Version number override"
|
||||
required: false
|
||||
run_unit_tests:
|
||||
type: boolean
|
||||
description: 'Skip running unit tests'
|
||||
description: "Skip running unit tests"
|
||||
required: false
|
||||
default: true
|
||||
runs_on:
|
||||
type: string
|
||||
description: 'The GitHub hosted runner to use'
|
||||
description: "The GitHub hosted runner to use"
|
||||
required: true
|
||||
OS:
|
||||
type: string
|
||||
description: >
|
||||
The operating system targeted by the build.
|
||||
|
||||
|
||||
There must be a corresponding Bundle_$OS.sh script file in ./Scripts
|
||||
required: true
|
||||
architecture:
|
||||
type: string
|
||||
description: 'CPU architecture targeted by the build.'
|
||||
description: "CPU architecture targeted by the build."
|
||||
required: true
|
||||
|
||||
env:
|
||||
DOTNET_CONFIGURATION: 'Release'
|
||||
DOTNET_VERSION: '8.0.x'
|
||||
RELEASE_NAME: 'chardonnay'
|
||||
DOTNET_CONFIGURATION: "Release"
|
||||
DOTNET_VERSION: "9.0.x"
|
||||
RELEASE_NAME: "chardonnay"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: '${{ inputs.OS }}-${{ inputs.architecture }}'
|
||||
name: "${{ inputs.OS }}-${{ inputs.architecture }}"
|
||||
runs-on: ${{ inputs.runs_on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
version="$(grep -Eio -m 1 '<Version>.*</Version>' ./Source/AppScaffolding/AppScaffolding.csproj | sed -r 's/<\/?Version>//g')"
|
||||
fi
|
||||
echo "version=${version}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
|
||||
- name: Unit test
|
||||
if: ${{ inputs.run_unit_tests }}
|
||||
working-directory: ./Source
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
- name: Publish
|
||||
id: publish
|
||||
working-directory: ./Source
|
||||
run: |
|
||||
run: |
|
||||
if [[ "${{ inputs.OS }}" == "MacOS" ]]
|
||||
then
|
||||
display_os="macOS"
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
display_os="Linux"
|
||||
RUNTIME_ID="linux-${{ inputs.architecture }}"
|
||||
fi
|
||||
|
||||
|
||||
OUTPUT="bin/Publish/${display_os}-${{ inputs.architecture }}-${{ env.RELEASE_NAME }}"
|
||||
|
||||
|
||||
echo "display_os=${display_os}" >> $GITHUB_OUTPUT
|
||||
echo "Runtime Identifier: $RUNTIME_ID"
|
||||
echo "Output Directory: $OUTPUT"
|
||||
|
||||
|
||||
dotnet publish \
|
||||
LibationAvalonia/LibationAvalonia.csproj \
|
||||
--runtime $RUNTIME_ID \
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ inputs.architecture }}"
|
||||
artifact=$(ls ./bundle)
|
||||
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
|
||||
- name: Publish bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
12
.github/workflows/build-windows.yml
vendored
@@ -8,21 +8,21 @@ on:
|
||||
inputs:
|
||||
version_override:
|
||||
type: string
|
||||
description: 'Version number override'
|
||||
description: "Version number override"
|
||||
required: false
|
||||
run_unit_tests:
|
||||
type: boolean
|
||||
description: 'Skip running unit tests'
|
||||
description: "Skip running unit tests"
|
||||
required: false
|
||||
default: true
|
||||
|
||||
env:
|
||||
DOTNET_CONFIGURATION: 'Release'
|
||||
DOTNET_VERSION: '8.0.x'
|
||||
DOTNET_CONFIGURATION: "Release"
|
||||
DOTNET_VERSION: "9.0.x"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: '${{ matrix.os }}-${{ matrix.release_name }}'
|
||||
name: "${{ matrix.os }}-${{ matrix.release_name }}"
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -112,4 +112,4 @@ jobs:
|
||||
name: ${{ steps.zip.outputs.artifact }}.zip
|
||||
path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
retention-days: 7
|
||||
|
||||
13
.github/workflows/build.yml
vendored
@@ -8,22 +8,21 @@ on:
|
||||
inputs:
|
||||
version_override:
|
||||
type: string
|
||||
description: 'Version number override'
|
||||
description: "Version number override"
|
||||
required: false
|
||||
run_unit_tests:
|
||||
type: boolean
|
||||
description: 'Skip running unit tests'
|
||||
description: "Skip running unit tests"
|
||||
required: false
|
||||
default: true
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
|
||||
windows:
|
||||
uses: ./.github/workflows/build-windows.yml
|
||||
with:
|
||||
version_override: ${{ inputs.version_override }}
|
||||
run_unit_tests: ${{ inputs.run_unit_tests }}
|
||||
|
||||
|
||||
linux:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -36,7 +35,7 @@ jobs:
|
||||
OS: ${{ matrix.OS }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
run_unit_tests: ${{ inputs.run_unit_tests }}
|
||||
|
||||
|
||||
macos:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -47,4 +46,4 @@ jobs:
|
||||
runs_on: macos-latest
|
||||
OS: MacOS
|
||||
architecture: ${{ matrix.architecture }}
|
||||
run_unit_tests: ${{ inputs.run_unit_tests }}
|
||||
run_unit_tests: ${{ inputs.run_unit_tests }}
|
||||
|
||||
37
.github/workflows/docker.yml
vendored
@@ -8,7 +8,11 @@ on:
|
||||
inputs:
|
||||
version:
|
||||
type: string
|
||||
description: 'Version number'
|
||||
description: "Version number"
|
||||
required: true
|
||||
release:
|
||||
type: boolean
|
||||
description: "Is this a release build?"
|
||||
required: true
|
||||
secrets:
|
||||
docker_username:
|
||||
@@ -16,12 +20,10 @@ on:
|
||||
docker_token:
|
||||
required: true
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: ${{ secrets.docker_username }}/libation
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
build_and_push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -33,14 +35,29 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ inputs.release }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.docker_username }}
|
||||
password: ${{ secrets.docker_token }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Generate docker image tags
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
push: true
|
||||
build-args: 'FOLDER_NAME=Linux-chardonnay'
|
||||
tags: ${{ env.DOCKER_IMAGE }}:latest,${{ env.DOCKER_IMAGE }}:${{ inputs.version }}
|
||||
flavor: |
|
||||
latest=true
|
||||
images: |
|
||||
name=${{ secrets.docker_username }}/libation
|
||||
tags: |
|
||||
type=raw,value=${{ inputs.version }},enable=${{ inputs.release }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ steps.metadata.outputs.tags != ''}}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
|
||||
16
.github/workflows/release.yml
vendored
@@ -5,7 +5,7 @@ name: release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- "v*"
|
||||
jobs:
|
||||
prerelease:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Get tag version
|
||||
id: get_version
|
||||
run: |
|
||||
export TAG='${{ github.ref_name }}'
|
||||
export TAG="${{ github.ref_name }}"
|
||||
echo "version=${TAG#v}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
docker:
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
uses: ./.github/workflows/docker.yml
|
||||
with:
|
||||
version: ${{ needs.prerelease.outputs.version }}
|
||||
release: true
|
||||
secrets:
|
||||
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
docker_token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -33,21 +34,22 @@ jobs:
|
||||
with:
|
||||
version_override: ${{ needs.prerelease.outputs.version }}
|
||||
run_unit_tests: false
|
||||
|
||||
|
||||
release:
|
||||
needs: [prerelease,build]
|
||||
needs: [prerelease, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: "*(Classic-)Libation.*"
|
||||
|
||||
- name: Release
|
||||
id: release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: Libation v${{ needs.prerelease.outputs.version }}
|
||||
name: Libation ${{ needs.prerelease.outputs.version }}
|
||||
body: <Put a body here>
|
||||
draft: true
|
||||
prerelease: false
|
||||
@@ -55,7 +57,7 @@ jobs:
|
||||
- name: Upload release assets
|
||||
uses: dwenegar/upload-release-assets@v2
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
with:
|
||||
release_id: '${{ steps.release.outputs.id }}'
|
||||
release_id: "${{ steps.release.outputs.id }}"
|
||||
assets_path: ./artifacts
|
||||
|
||||
22
.github/workflows/validate-appstream-metainfo.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Validate MetaInfo
|
||||
"on":
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
paths:
|
||||
- .github/workflows/validate-appstream-metainfo.yml
|
||||
- Source/LoadByOS/LinuxConfigApp/com.getlibation.Libation.metainfo.xml
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths:
|
||||
- .github/workflows/validate-appstream-metainfo.yml
|
||||
- Source/LoadByOS/LinuxConfigApp/com.getlibation.Libation.metainfo.xml
|
||||
|
||||
jobs:
|
||||
validate-appstream-metainfo:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/flathub/flatpak-builder-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check the MetaInfo file
|
||||
run: flatpak-builder-lint appstream Source/LoadByOS/LinuxConfigApp/com.getlibation.Libation.metainfo.xml
|
||||
21
.github/workflows/validate-desktop-file.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Check desktop file
|
||||
"on":
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
paths:
|
||||
- .github/workflows/validate-desktop-file.yml
|
||||
- Source/LoadByOS/LinuxConfigApp/Libation.desktop
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths:
|
||||
- .github/workflows/validate-desktop-file.yml
|
||||
- Source/LoadByOS/LinuxConfigApp/Libation.desktop
|
||||
|
||||
jobs:
|
||||
validate-desktop-file:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: sudo apt --yes install desktop-file-utils
|
||||
- name: Check the desktop file
|
||||
run: desktop-file-validate Source/LoadByOS/LinuxConfigApp/Libation.desktop
|
||||
10
.github/workflows/validate.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# validate.yml
|
||||
# Validates that Libation will build on a pull request or push to master.
|
||||
# Validates that Libation will build on a pull request or push to master.
|
||||
---
|
||||
name: validate
|
||||
|
||||
@@ -12,3 +12,11 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
uses: ./.github/workflows/build.yml
|
||||
docker:
|
||||
uses: ./.github/workflows/docker.yml
|
||||
with:
|
||||
version: ${GITHUB_SHA}
|
||||
release: false
|
||||
secrets:
|
||||
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
docker_token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
3
Docker/appsettings.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"LibationFiles": "/config-internal"
|
||||
}
|
||||
@@ -1,68 +1,174 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Rewire echo to print date time
|
||||
echo() {
|
||||
if [[ -n $1 ]]; then
|
||||
printf "$(date '+%F %T'): %s\n" "$1"
|
||||
fi
|
||||
error() {
|
||||
log "ERROR" "$1"
|
||||
}
|
||||
|
||||
# ################################
|
||||
# Setup
|
||||
# ################################
|
||||
echo "Starting"
|
||||
if [[ -z "${SLEEP_TIME}" ]]; then
|
||||
echo "No sleep time passed in. Will run once and exit."
|
||||
else
|
||||
echo "Sleep time is set to ${SLEEP_TIME}"
|
||||
fi
|
||||
warn() {
|
||||
log "WARNING" "$1"
|
||||
}
|
||||
|
||||
echo ""
|
||||
info() {
|
||||
log "info" "$1"
|
||||
}
|
||||
|
||||
# Check if the config directory is passed in, and there is no link to it then create the link.
|
||||
if [ -d "/config" ] && [ ! -d "/root/Libation" ]; then
|
||||
echo "Linking config directory to the Libation config directory"
|
||||
ln -s /config/ /root/Libation
|
||||
fi
|
||||
debug() {
|
||||
if [ "${LOG_LEVEL}" = "debug" ]; then
|
||||
log "debug" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# If no config error and exit
|
||||
if [ ! -d "/config" ]; then
|
||||
echo "ERROR: No /config directory. You must pass in a volume containing your config mapped to /config"
|
||||
log() {
|
||||
LEVEL=$1
|
||||
MESSAGE=$2
|
||||
printf "$(date '+%F %T') %s: %s\n" "${LEVEL}" "${MESSAGE}"
|
||||
}
|
||||
|
||||
init_config_file() {
|
||||
FILE=$1
|
||||
FULLPATH=${LIBATION_CONFIG_DIR}/${FILE}
|
||||
if [ -f ${FULLPATH} ]; then
|
||||
info "loading ${FILE}"
|
||||
cp ${FULLPATH} ${LIBATION_CONFIG_INTERNAL}/
|
||||
return 0
|
||||
else
|
||||
warn "${FULLPATH} not found, creating empty file"
|
||||
echo "{}" > ${LIBATION_CONFIG_INTERNAL}/${FILE}
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_settings() {
|
||||
FILE=$1
|
||||
KEY=$2
|
||||
VALUE=$3
|
||||
info "setting ${KEY} to ${VALUE}"
|
||||
echo $(jq --arg k "${KEY}" --arg v "${VALUE}" '.[$k] = $v' ${LIBATION_CONFIG_INTERNAL}/${FILE}) > ${LIBATION_CONFIG_INTERNAL}/${FILE}.tmp
|
||||
mv ${LIBATION_CONFIG_INTERNAL}/${FILE}.tmp ${LIBATION_CONFIG_INTERNAL}/${FILE}
|
||||
}
|
||||
|
||||
is_mounted() {
|
||||
DIR=$1
|
||||
if grep -qs "${DIR} " /proc/mounts;
|
||||
then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
create_db() {
|
||||
DBFILE=$1
|
||||
if [ -f "${DBFILE}" ]; then
|
||||
warn "prexisting database found when creating"
|
||||
return 0
|
||||
else
|
||||
if ! touch "${DBFILE}"; then
|
||||
error "unable to create database, check permissions on host"
|
||||
exit 1
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
setup_db() {
|
||||
DBPATH=$1
|
||||
dbpattern="*.db"
|
||||
|
||||
debug "using database directory ${DBPATH}"
|
||||
|
||||
# Figure out the right databse file
|
||||
if [[ -z "${LIBATION_DB_FILE}" ]];
|
||||
then
|
||||
dbCount=$(find "${DBPATH}" -maxdepth 1 -type f -name "${dbpattern}" | wc -l)
|
||||
if [ "${dbCount}" -gt 1 ];
|
||||
then
|
||||
error "too many database files found, set LIBATION_DB_FILE to the filename you wish to use"
|
||||
exit 1
|
||||
elif [ "${dbCount}" -eq 1 ];
|
||||
then
|
||||
files=( ${DBPATH}/${dbpattern} )
|
||||
FILE=${files[0]}
|
||||
else
|
||||
FILE="${DBPATH}/LibationContext.db"
|
||||
fi
|
||||
else
|
||||
FILE="${DBPATH}/${LIBATION_DB_FILE}"
|
||||
fi
|
||||
|
||||
debug "planning to use database ${FILE}"
|
||||
|
||||
if [ -f "${FILE}" ]; then
|
||||
info "database found at ${FILE}"
|
||||
elif [ ${LIBATION_CREATE_DB} = "true" ];
|
||||
then
|
||||
warn "database not found, creating one at ${FILE}"
|
||||
create_db ${FILE}
|
||||
else
|
||||
error "database not found and creation is disabled"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
ln -s "${FILE}" "${LIBATION_CONFIG_INTERNAL}/LibationContext.db"
|
||||
}
|
||||
|
||||
# If user passes in db from a /db/ folder and a db does not already exist / is not already linked
|
||||
FILE=/db/LibationContext.db
|
||||
if [ -f "${FILE}" ] && [ ! -f "/config/LibationContext.db" ]; then
|
||||
echo "Linking passed in Libation database from /db/ to the Libation config directory"
|
||||
ln -s $FILE /config/LibationContext.db
|
||||
fi
|
||||
run() {
|
||||
info "scanning accounts"
|
||||
/libation/LibationCli scan
|
||||
info "liberating books"
|
||||
/libation/LibationCli liberate
|
||||
}
|
||||
|
||||
# Confirm we have a db in the config direcotry.
|
||||
if [ ! -f "/config/LibationContext.db" ]; then
|
||||
echo "ERROR: No Libation database detected, exiting."
|
||||
exit 1
|
||||
fi
|
||||
main() {
|
||||
info "initializing libation"
|
||||
init_config_file AccountsSettings.json
|
||||
init_config_file Settings.json
|
||||
|
||||
info "loading settings"
|
||||
update_settings Settings.json Books /data
|
||||
update_settings Settings.json InProgress /tmp
|
||||
|
||||
# ################################
|
||||
# Loop and liberate
|
||||
# ################################
|
||||
while true
|
||||
do
|
||||
echo ""
|
||||
echo "Scanning accounts"
|
||||
/libation/LibationCli scan
|
||||
echo "Liberating books"
|
||||
/libation/LibationCli liberate
|
||||
echo ""
|
||||
info "loading database"
|
||||
# If user provides a separate database mount, use that
|
||||
if is_mounted "${LIBATION_DB_DIR}";
|
||||
then
|
||||
DB_LOCATION=${LIBATION_DB_DIR}
|
||||
# Otherwise, use the config directory
|
||||
else
|
||||
DB_LOCATION=${LIBATION_CONFIG_DIR}
|
||||
fi
|
||||
setup_db ${DB_LOCATION}
|
||||
|
||||
# Try to warn if books dir wasn't mounted in
|
||||
if ! is_mounted "${LIBATION_BOOKS_DIR}";
|
||||
then
|
||||
warn "${LIBATION_BOOKS_DIR} does not appear to be mounted, books will not be saved"
|
||||
fi
|
||||
|
||||
# Let the user know what the run type will be
|
||||
if [[ -z "${SLEEP_TIME}" ]]; then
|
||||
SLEEP_TIME=-1
|
||||
fi
|
||||
|
||||
if [ "${SLEEP_TIME}" == -1 ]; then
|
||||
info "running once"
|
||||
else
|
||||
info "running every ${SLEEP_TIME}"
|
||||
fi
|
||||
|
||||
# loop
|
||||
while true
|
||||
do
|
||||
run
|
||||
|
||||
# Liberate only once if SLEEP_TIME was set to -1
|
||||
if [ "${SLEEP_TIME}" = -1 ]; then
|
||||
if [ "${SLEEP_TIME}" == -1 ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Sleeping for ${SLEEP_TIME}"
|
||||
sleep "${SLEEP_TIME}"
|
||||
done
|
||||
done
|
||||
|
||||
echo "Exiting"
|
||||
info "exiting"
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
45
Dockerfile
@@ -1,22 +1,39 @@
|
||||
# Dockerfile
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 as build-env
|
||||
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY Source /Source
|
||||
RUN dotnet publish -c Release -o /Source/bin/Publish/Linux-chardonnay /Source/LibationCli/LibationCli.csproj -p:PublishProfile=/Source/LibationCli/Properties/PublishProfiles/LinuxProfile.pubxml
|
||||
COPY Docker/liberate.sh /Source/bin/Publish/Linux-chardonnay
|
||||
RUN dotnet publish \
|
||||
/Source/LibationCli/LibationCli.csproj \
|
||||
--arch ${TARGETARCH} \
|
||||
--configuration Release \
|
||||
--output /Source/bin/Publish/Linux-chardonnay \
|
||||
-p:PublishProfile=/Source/LibationCli/Properties/PublishProfiles/LinuxProfile.pubxml
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime:9.0
|
||||
ARG USER_UID=1001
|
||||
ARG USER_GID=1001
|
||||
|
||||
# Set the character set that will be used for folder and filenames when liberating
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
ENV SLEEP_TIME=-1
|
||||
ENV LIBATION_CONFIG_INTERNAL=/config-internal
|
||||
ENV LIBATION_CONFIG_DIR=/config
|
||||
ENV LIBATION_DB_DIR=/db
|
||||
ENV LIBATION_DB_FILE=
|
||||
ENV LIBATION_CREATE_DB=true
|
||||
ENV LIBATION_BOOKS_DIR=/data
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime:8.0
|
||||
RUN apt-get update && apt-get -y upgrade && \
|
||||
apt-get install -y jq && \
|
||||
mkdir -m777 ${LIBATION_CONFIG_INTERNAL} ${LIBATION_BOOKS_DIR}
|
||||
|
||||
ENV SLEEP_TIME "30m"
|
||||
COPY --from=build /Source/bin/Publish/Linux-chardonnay /libation
|
||||
COPY Docker/* /libation
|
||||
|
||||
# Sets the character set that will be used for folder and filenames when liberating
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
USER ${USER_UID}:${USER_GID}
|
||||
|
||||
RUN mkdir /db /config /data
|
||||
|
||||
COPY --from=build-env /Source/bin/Publish/Linux-chardonnay /libation
|
||||
|
||||
|
||||
CMD ["./libation/liberate.sh"]
|
||||
CMD ["/libation/liberate.sh"]
|
||||
|
||||
@@ -3,38 +3,30 @@
|
||||
### 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**.
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> ## Breaking Changes
|
||||
> * The docker image now runs as user 1001 and group 1001. Make sure that the permissions on your volumes allow user 1001 to read and write to them or see the User section below for other options, or if you're not sure.
|
||||
> * `SLEEP_TIME` is now set to `-1` by default. This means the image will run once and exit. If you were relying on the previous default, you'll need to explicitly set the `SLEEP_TIME` environment variable to `30m` to replicate the previous behavior.
|
||||
> * The docker image now ignores the values in `Settings.json` for `Books` and `InProgress`. You can now change the folder that books are saved to by using the `LIBATION_BOOKS_DIR` environment variable.
|
||||
|
||||
# Disclaimer
|
||||
The docker image is provided as-is. We hope it can be useful to you but it is not officially supported.
|
||||
|
||||
### Setup
|
||||
In order to use the docker image, you'll need to provide it with a copy of the `AccountsSettings.json`, `Settings.json`, and `LibationContext.db` files. These files can usually be found in the Libation folder in your user's home directory. If you haven't run Libation yet, you'll need to launch it to generate these files and setup your accounts. Once you have them, copy these files to a new location, such as `/opt/libation/config`. Before using them we'll need to make a couple edits so that the filepaths referenced are correct when running from the docker image.
|
||||
|
||||
In Settings.json, make the following changes:
|
||||
* Change `Books` to `/data`
|
||||
* 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"
|
||||
```
|
||||

|
||||
|
||||
### Configuration
|
||||
Configuration in Libation is handled by two files, `AccountsSettings.json` and `Settings.json`. These files can usually be found in the Libation folder in your user's home directory. The easiest way to configure these is to run the desktop version of Libation and then copy them into a folder, such as `/opt/libation/config`, that you'll volume mount into the image. `Settings.json` is technically optional, and, if not provided, Libation will run using the default settings. Additionally, the `Books` and `InProgress` settings in `Settings.json` will be ignored and the image will instead substitute it's own values.
|
||||
|
||||
### 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, the docker image can be run with the following command.
|
||||
```
|
||||
sudo docker run -d \
|
||||
-v /opt/libation/config:/config \
|
||||
-v /opt/libation/books:/data \
|
||||
--name libation \
|
||||
--restart=always \
|
||||
rmcrackan/libation
|
||||
rmcrackan/libation:latest
|
||||
```
|
||||
|
||||
By default the container will scan for new books every 30 minutes and download any new ones. This is configurable by passing in a value for the `SLEEP_TIME` environment variable. Additionally, if you pass in `-1` it will scan and download books once and then exit.
|
||||
By default the container will scan for new books once and download any new ones. This is configurable by passing in a value for the `SLEEP_TIME` environment variable. For example, if you pass in `10m` it will keep running, scan for new books, and download them every 10 minutes.
|
||||
|
||||
```
|
||||
sudo docker run -d \
|
||||
@@ -43,6 +35,37 @@ sudo docker run -d \
|
||||
-e SLEEP_TIME='10m' \
|
||||
--name libation \
|
||||
--restart=always \
|
||||
rmcrackan/libation
|
||||
rmcrackan/libation:latest
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
| Env Var | Default | Description |
|
||||
| -------- | ------- | ----------- |
|
||||
| SLEEP_TIME | -1 | Length of time to sleep before doing another scan/download. Set to -1 to run one. |
|
||||
| LIBATION_BOOKS_DIR | /data | Folder where books will be saved |
|
||||
| LIBATION_CONFIG_DIR | /config | Folder to read configuration from. |
|
||||
| LIBATION_DB_DIR | /db | Optional folder to load database from. If not mounted, will load database from `LIBATION_CONFIG_DIR`. |
|
||||
| LIBATION_DB_FILE | | Name of database file to load. By default it will look for all `.db` files and load one if there is only one present. |
|
||||
| LIBATION_CREATE_DB | true | Whether or not the image should create a database file if none are found. |
|
||||
|
||||
### User
|
||||
This docker image runs as user `1001`. In order for the image to function properly, user `1001` must be able to read and write the volumes that are mounted in. If they are not, you will see errors, including [sqlite error](#1060), [Microsoft.Data.Sqlite.SqliteException](#1110), [unable to open database file](#1113), [Microsoft.EntityFrameworkCore.DbUpdateException](#1049)
|
||||
|
||||
If you're not sure what your user number is, check the output of the `id` command. Docker should normally run with the number of the user who configured and ran it.
|
||||
|
||||
If you want to change the user the image runs as, you can specify `-u <uid>:<gid>`. For example, to run it as user `2000` and group `3000`, you could do the following:
|
||||
|
||||
```
|
||||
sudo docker run -d \
|
||||
-u 2000:3000 \
|
||||
-v /opt/libation/config:/config \
|
||||
-v /opt/libation/books:/data \
|
||||
--name libation \
|
||||
--restart=always \
|
||||
rmcrackan/libation:latest
|
||||
```
|
||||
|
||||
If the user it's running as is correct, and it still cannot write, be sure to check whether the files and/or folders might be owned by the wrong user. You can use the `chown` command to change the owner of the file to the correct user and group number, for example: `chown -R 1001:1001 /mnt/audiobooks /mnt/libation-config`
|
||||
|
||||
### Advanced Database Options
|
||||
The docker image supports an optional database mount location defined by `LIBATION_DB_DIR`. This allows the database to be mounted as read/write, while allowing the rest of the configuration files to be mounted as read only. This is specifically useful if running in Kubernetes where you can use Configmaps and Secrets to define the configuration. If the `LIBATION_DB_DIR` is mounted, it will be used, otherwise it will look for the database in `LIBATION_CONFIG_DIR`. If it does not find the database in the expected location, it will attempt to make an empty database there.
|
||||
|
||||
@@ -22,7 +22,18 @@
|
||||
* 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)
|
||||
* Android: [Listen](https://play.google.com/store/apps/details?id=ru.litres.android.audio&hl=en_US&gl=US)
|
||||
* Desktop: [VLC](https://www.videolan.org/)
|
||||
* Windows Desktop: [Audibly](https://github.com/rstewa/Audibly) -- a desktop player build specifically for audiobooks
|
||||
|
||||
Self-hosting online:
|
||||
|
||||
* [audiobookshelf](https://www.audiobookshelf.org). On [reddit](https://www.reddit.com/r/audiobookshelf/)
|
||||
* [plex](https://www.plex.tv/). Listen with [Prologue](https://prologue.audio/) (iOS)
|
||||
|
||||
## Q: I'm having trouble loggin into my Brazil account.
|
||||
|
||||
For reasons known only to Jeff Bezos and God, amazon and audible brazil handle logins slightly differently. [See this ticket for more details.](https://github.com/rmcrackan/Libation/issues/1103)
|
||||
|
||||
## Q: How do I use Libation with a South Africa account?
|
||||
|
||||
|
||||
@@ -4,25 +4,56 @@
|
||||
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
|
||||
|
||||
|
||||
## Packaging status
|
||||
|
||||
# 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.
|
||||
[](https://repology.org/project/libation/versions)
|
||||
|
||||
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 download and install Libation, replacing the url with the latest Libation package url:
|
||||
|
||||
- Debian
|
||||
### Debian
|
||||
```Console
|
||||
wget -O libation.deb https://github.com/rmcrackan/Libation/releases/download/vX.X.X/Libation.X.X.X-linux-chardonnay.deb &&
|
||||
sudo apt install ./libation.deb
|
||||
```
|
||||
- Redhat and CentOS
|
||||
### Redhat and CentOS
|
||||
```Console
|
||||
wget -O libation.rpm https://github.com/rmcrackan/Libation/releases/download/vX.X.X/Libation.X.X.X-linux-chardonnay.rpm &&
|
||||
sudo yum install ./libation.rpm
|
||||
```
|
||||
---
|
||||
### Arch Linux
|
||||
```Console
|
||||
yay -S libation
|
||||
```
|
||||
This package is available on [Arch User Repository](https://aur.archlinux.org/packages/libation), install via your choice of [AUR helpers](https://wiki.archlinux.org/title/AUR_helpers).
|
||||
|
||||
Thanks to [mhdi](https://aur.archlinux.org/account/mhdi) for taking care of AUR package maintenance.
|
||||
### NixOS
|
||||
- Install via `nix-shell`
|
||||
```Console
|
||||
nix-shell -p libation
|
||||
```
|
||||
A `nix-shell` will temporarily modify your $PATH environment variable. This can be used to try a piece of software before deciding to permanently install it.
|
||||
- Install via NixOS configuration
|
||||
```Console
|
||||
environment.systemPackages = [
|
||||
pkgs.libation
|
||||
];
|
||||
```
|
||||
Add the following Nix code to your NixOS Configuration, usually located in `/etc/nixos/configuration.nix`
|
||||
- On NixOS via via `nix-env`
|
||||
```Console
|
||||
nix-env -iA nixos.libation
|
||||
```
|
||||
- On Non NixOS via `nix-env`
|
||||
```Console
|
||||
nix-env -iA nixpkgs.libation
|
||||
```
|
||||
Warning: Using `nix-env` permanently modifies a local profile of installed packages. This must be updated and maintained by the user in the same way as with a traditional package manager.
|
||||
|
||||
Thanks to [TomaSajt](https://github.com/tomasajt) for taking care of Nix package maintenance.
|
||||
|
||||
If your desktop uses gtk, you should now see Libation among your applications.
|
||||
|
||||
|
||||
@@ -16,13 +16,29 @@ This walkthrough should get you up and running with Libation on your Mac.
|
||||
- Apple Silicon (M1, M2, ...): `Libation.9.4.2-macOS-chardonnay-`**arm64**`.tgz`
|
||||
- Intel: `Libation.x.x.x-macOS-chardonnay-`**x64**`.tgz`
|
||||
- Move the extracted Libation app bundle to your applications folder.
|
||||
- Open a terminal (Go > Utilities > Terminal)
|
||||
- Copy/paste/run the following command (you'll be prompted to enter your password)
|
||||
|
||||
- Right-click on Libation and then click on open
|
||||
- The first time, it will not immediately show you an option to open it. Just dismiss the dialog and do the same thing again (right-click -> open) then you will get an option to run the unsigned application. This takes about 10 seconds.
|
||||
|
||||
## If this doesn't work
|
||||
|
||||
You can add Libation as a safe app without touching Gatekeeper.
|
||||
|
||||
- Copy/paste/run the following command. Adjust the file path to the Libation.app on your computer if necessary.
|
||||
|
||||
```Console
|
||||
xattr -r -d com.apple.quarantine ~/Downloads/Libation.app
|
||||
```
|
||||
- Close the terminal and use Libation!
|
||||
|
||||
## If this still doesn't work
|
||||
|
||||
- Copy/paste/run the following command (you'll be prompted to enter your Mac password)
|
||||
|
||||
```Console
|
||||
sudo spctl --master-disable && sudo spctl --add --label "Libation" /Applications/Libation.app && open /Applications/Libation.app && sudo spctl --master-enable
|
||||
```
|
||||
- Close the terminal and use Libation!
|
||||
```
|
||||
|
||||
* Close the terminal and use Libation!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -42,7 +58,7 @@ Libation comes with a recovery app called Hangover. You can start it by running
|
||||
open /Applications/Libation.app --args hangover
|
||||
```
|
||||
|
||||
## Runnign LibationCli
|
||||
## Running LibationCli
|
||||
|
||||
Libation comes with a command-line interface. Unfortunately, due to the way apps are sandboxed on mac, its use is somewhat limited. To open a new sandboxed terminal in LibationCli's directory, run the following command:
|
||||
```Console
|
||||
|
||||
@@ -86,8 +86,12 @@ delfiles=( 'libmp3lame.arm64.so' 'libmp3lame.x64.so' 'libmp3lame.x64.dll' 'libmp
|
||||
if [[ "$ARCH" == "arm64" ]]
|
||||
then
|
||||
delfiles+=('libmp3lame.x64.dylib' 'ffmpegaac.x64.dylib')
|
||||
mv $BUNDLE_MACOS/ffmpegaac.arm64.dylib $BUNDLE_MACOS/ffmpegaac.dylib
|
||||
mv $BUNDLE_MACOS/libmp3lame.arm64.dylib $BUNDLE_MACOS/libmp3lame.dylib
|
||||
else
|
||||
delfiles+=('libmp3lame.arm64.dylib' 'ffmpegaac.arm64.dylib')
|
||||
mv $BUNDLE_MACOS/ffmpegaac.x64.dylib $BUNDLE_MACOS/ffmpegaac.dylib
|
||||
mv $BUNDLE_MACOS/libmp3lame.x64.dylib $BUNDLE_MACOS/libmp3lame.dylib
|
||||
fi
|
||||
|
||||
|
||||
@@ -111,4 +115,4 @@ mv $APP_FILE ./bundle/$APP_FILE
|
||||
|
||||
rm -r $BUNDLE
|
||||
|
||||
echo "Done!"
|
||||
echo "Done!"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@@ -13,7 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AAXClean.Codecs" Version="1.1.3" />
|
||||
<PackageReference Include="AAXClean.Codecs" Version="1.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>11.3.13.1</Version>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>11.6.5.1</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit" Version="11.0.1" />
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
<!-- Do not remove unused Serilog.Sinks -->
|
||||
<!-- Only ZipFile sink is currently used. By user request (June 2024) others packages are included for experimental use. -->
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.ZipFile" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ApplicationServices;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.IO;
|
||||
using Dinah.Core.Logging;
|
||||
using LibationFileManager;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AppScaffolding
|
||||
{
|
||||
public enum ReleaseIdentifier
|
||||
public enum ReleaseIdentifier
|
||||
{
|
||||
None,
|
||||
WindowsClassic = OS.Windows | Variety.Classic | Architecture.X64,
|
||||
@@ -87,6 +88,8 @@ namespace AppScaffolding
|
||||
//
|
||||
|
||||
Migrations.migrate_to_v6_6_9(config);
|
||||
Migrations.migrate_to_v11_5_0(config);
|
||||
Migrations.migrate_to_v11_6_5(config);
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Wire-up events. Run after migration</summary>
|
||||
@@ -124,6 +127,9 @@ namespace AppScaffolding
|
||||
{ "MinimumLevel", "Information" },
|
||||
{ "WriteTo", new JArray
|
||||
{
|
||||
// ABOUT SINKS
|
||||
// Only ZipFile sink is currently used. By user request (June 2024) others packages are included for experimental use.
|
||||
|
||||
// new JObject { {"Name", "Console" } }, // this has caused more problems than it's solved
|
||||
new JObject
|
||||
{
|
||||
@@ -227,7 +233,14 @@ namespace AppScaffolding
|
||||
|
||||
// begin logging session with a form feed
|
||||
Log.Logger.Information("\r\n\f");
|
||||
Log.Logger.Information("Begin. {@DebugInfo}", new
|
||||
|
||||
static int fileCount(FileManager.LongPath longPath)
|
||||
{
|
||||
try { return FileManager.FileUtility.SaferEnumerateFiles(longPath).Count(); }
|
||||
catch { return -1; }
|
||||
}
|
||||
|
||||
Log.Logger.Information("Begin. {@DebugInfo}", new
|
||||
{
|
||||
AppName = EntryAssembly.GetName().Name,
|
||||
Version = BuildVersion.ToString(),
|
||||
@@ -252,10 +265,10 @@ namespace AppScaffolding
|
||||
config.InProgress,
|
||||
|
||||
AudibleFileStorage.DownloadsInProgressDirectory,
|
||||
DownloadsInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DownloadsInProgressDirectory).Count(),
|
||||
DownloadsInProgressFiles = fileCount(AudibleFileStorage.DownloadsInProgressDirectory),
|
||||
|
||||
AudibleFileStorage.DecryptInProgressDirectory,
|
||||
DecryptInProgressFiles = FileManager.FileUtility.SaferEnumerateFiles(AudibleFileStorage.DecryptInProgressDirectory).Count(),
|
||||
DecryptInProgressFiles = fileCount(AudibleFileStorage.DecryptInProgressDirectory),
|
||||
|
||||
disableIPv6 = AppContext.TryGetSwitch("System.Net.DisableIPv6", out bool disableIPv6Value),
|
||||
});
|
||||
@@ -394,5 +407,64 @@ namespace AppScaffolding
|
||||
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FilterState_6_6_9
|
||||
{
|
||||
public bool UseDefault { get; set; }
|
||||
public List<string> Filters { get; set; } = new();
|
||||
}
|
||||
|
||||
public static void migrate_to_v11_6_5(Configuration config)
|
||||
{
|
||||
//Settings migration for unsupported sample rates (#1116)
|
||||
if (config.MaxSampleRate < AAXClean.SampleRate.Hz_8000)
|
||||
config.MaxSampleRate = AAXClean.SampleRate.Hz_8000;
|
||||
else if (config.MaxSampleRate > AAXClean.SampleRate.Hz_48000)
|
||||
config.MaxSampleRate = AAXClean.SampleRate.Hz_48000;
|
||||
}
|
||||
|
||||
public static void migrate_to_v11_5_0(Configuration config)
|
||||
{
|
||||
// Read file, but convert old format to new (with Name field) as necessary.
|
||||
if (!File.Exists(QuickFilters.JsonFile))
|
||||
{
|
||||
QuickFilters.InMemoryState = new();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (JsonConvert.DeserializeObject<QuickFilters.FilterState>(File.ReadAllText(QuickFilters.JsonFile))
|
||||
is QuickFilters.FilterState inMemState)
|
||||
{
|
||||
QuickFilters.InMemoryState = inMemState;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Eat
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (JsonConvert.DeserializeObject<FilterState_6_6_9>(File.ReadAllText(QuickFilters.JsonFile))
|
||||
is FilterState_6_6_9 inMemState)
|
||||
{
|
||||
// Copy old structure to new.
|
||||
QuickFilters.InMemoryState = new();
|
||||
QuickFilters.InMemoryState.UseDefault = inMemState.UseDefault;
|
||||
foreach (var oldFilter in inMemState.Filters)
|
||||
QuickFilters.InMemoryState.Filters.Add(new QuickFilters.NamedFilter(oldFilter, null));
|
||||
|
||||
return;
|
||||
}
|
||||
Debug.Assert(false, "Should not get here, QuickFilters.json deserialization issue");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Eat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||
<PackageReference Include="NPOI" Version="2.7.0" />
|
||||
<PackageReference Include="CsvHelper" Version="33.0.1" />
|
||||
<PackageReference Include="NPOI" Version="2.7.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -126,7 +126,8 @@ namespace ApplicationServices
|
||||
| LibraryOptions.ResponseGroupOptions.Contributors | LibraryOptions.ResponseGroupOptions.ProvidedReview
|
||||
| LibraryOptions.ResponseGroupOptions.ProductPlans | LibraryOptions.ResponseGroupOptions.Series
|
||||
| LibraryOptions.ResponseGroupOptions.CategoryLadders | LibraryOptions.ResponseGroupOptions.ProductExtendedAttrs
|
||||
| LibraryOptions.ResponseGroupOptions.PdfUrl | LibraryOptions.ResponseGroupOptions.OriginAsin,
|
||||
| LibraryOptions.ResponseGroupOptions.PdfUrl | LibraryOptions.ResponseGroupOptions.OriginAsin
|
||||
| LibraryOptions.ResponseGroupOptions.IsFinished,
|
||||
ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215
|
||||
};
|
||||
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
|
||||
|
||||
@@ -115,6 +115,9 @@ namespace ApplicationServices
|
||||
|
||||
[Name("LastDownloadedVersion")]
|
||||
public string LastDownloadedVersion { get; set; }
|
||||
|
||||
[Name("IsFinished")]
|
||||
public bool IsFinished { get; set; }
|
||||
}
|
||||
public static class LibToDtos
|
||||
{
|
||||
@@ -153,8 +156,8 @@ namespace ApplicationServices
|
||||
Language = a.Book.Language,
|
||||
LastDownloaded = a.Book.UserDefinedItem.LastDownloaded,
|
||||
LastDownloadedVersion = a.Book.UserDefinedItem.LastDownloadedVersion?.ToString() ?? "",
|
||||
|
||||
}).ToList();
|
||||
IsFinished = a.Book.UserDefinedItem.IsFinished
|
||||
}).ToList();
|
||||
}
|
||||
public static class LibraryExporter
|
||||
{
|
||||
@@ -229,6 +232,7 @@ namespace ApplicationServices
|
||||
nameof(ExportDto.Language),
|
||||
nameof(ExportDto.LastDownloaded),
|
||||
nameof(ExportDto.LastDownloadedVersion),
|
||||
nameof(ExportDto.IsFinished)
|
||||
};
|
||||
var col = 0;
|
||||
foreach (var c in columns)
|
||||
@@ -306,6 +310,7 @@ namespace ApplicationServices
|
||||
}
|
||||
|
||||
row.CreateCell(++col).SetCellValue(dto.LastDownloadedVersion);
|
||||
row.CreateCell(++col).SetCellValue(dto.IsFinished);
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="9.1.2.1" />
|
||||
<PackageReference Include="AudibleApi" Version="9.3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -10,14 +10,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="8.0.0.1" />
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="8.0.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
|
||||
<PackageReference Include="Dinah.Core" Version="9.0.0.1" />
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="9.0.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace DataLayer
|
||||
ArgumentValidator.EnsureNotNull(ladders, nameof(ladders));
|
||||
|
||||
//Replace all existing category ladders.
|
||||
//Some books make have duplocate ladders
|
||||
//Some books make have duplicate ladders
|
||||
CategoriesLink.Clear();
|
||||
CategoriesLink.UnionWith(ladders.Distinct().Select(l => new BookCategory(this, l)));
|
||||
}
|
||||
|
||||
@@ -195,7 +195,23 @@ namespace DataLayer
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region IsFinished
|
||||
private bool _isFinished;
|
||||
public bool IsFinished
|
||||
{
|
||||
get => _isFinished;
|
||||
set
|
||||
{
|
||||
if (value != _isFinished)
|
||||
{
|
||||
_isFinished = value;
|
||||
OnItemChanged(nameof(IsFinished));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override string ToString() => $"{Book} {Rating} {Tags}";
|
||||
}
|
||||
|
||||
468
Source/DataLayer/Migrations/20240911114741_MyComment.Designer.cs
generated
Normal file
@@ -0,0 +1,468 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
[Migration("20240911114741_MyComment")]
|
||||
partial class MyComment
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
|
||||
|
||||
modelBuilder.Entity("CategoryCategoryLadder", b =>
|
||||
{
|
||||
b.Property<int>("_categoriesCategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("_categoryLaddersCategoryLadderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("_categoriesCategoryId", "_categoryLaddersCategoryLadderId");
|
||||
|
||||
b.HasIndex("_categoryLaddersCategoryLadderId");
|
||||
|
||||
b.ToTable("CategoryCategoryLadder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ContentType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAbridged")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureLarge")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Subtitle")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("_audioFormat")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
b.ToTable("Books");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookCategory", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CategoryLadderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "CategoryLadderId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("CategoryLadderId");
|
||||
|
||||
b.ToTable("BookCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContributorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "ContributorId", "Role");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("ContributorId");
|
||||
|
||||
b.ToTable("BookContributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleCategoryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("AudibleCategoryId");
|
||||
|
||||
b.ToTable("Categories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.CategoryLadder", b =>
|
||||
{
|
||||
b.Property<int>("CategoryLadderId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CategoryLadderId");
|
||||
|
||||
b.ToTable("CategoryLadders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Property<int>("ContributorId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleContributorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ContributorId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("Contributors");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ContributorId = -1,
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AbsentFromLastScan")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("LibraryBooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleSeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId");
|
||||
|
||||
b.HasIndex("AudibleSeriesId");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId", "BookId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("SeriesBook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CategoryCategoryLadder", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("_categoriesCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.CategoryLadder", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("_categoryLaddersCategoryLadderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("Books");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||
{
|
||||
b1.Property<int>("SupplementId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("SupplementId");
|
||||
|
||||
b1.HasIndex("BookId");
|
||||
|
||||
b1.ToTable("Supplement");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.Navigation("Book");
|
||||
});
|
||||
|
||||
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<DateTime?>("LastDownloaded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<string>("LastDownloadedVersion")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<int?>("PdfStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Tags")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem", (string)null);
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||
{
|
||||
b2.Property<int>("UserDefinedItemBookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b2.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.HasKey("UserDefinedItemBookId");
|
||||
|
||||
b2.ToTable("UserDefinedItem");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("UserDefinedItemBookId");
|
||||
});
|
||||
|
||||
b1.Navigation("Book");
|
||||
|
||||
b1.Navigation("Rating");
|
||||
});
|
||||
|
||||
b.Navigation("Rating");
|
||||
|
||||
b.Navigation("Supplements");
|
||||
|
||||
b.Navigation("UserDefinedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookCategory", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("CategoriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.CategoryLadder", "CategoryLadder")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("CategoryLadderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("CategoryLadder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("ContributorsLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("ContributorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Contributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithOne()
|
||||
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("SeriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Series", "Series")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Navigation("CategoriesLink");
|
||||
|
||||
b.Navigation("ContributorsLink");
|
||||
|
||||
b.Navigation("SeriesLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.CategoryLadder", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Source/DataLayer/Migrations/20240911114741_MyComment.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MyComment : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsFinished",
|
||||
table: "UserDefinedItem",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsFinished",
|
||||
table: "UserDefinedItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace DataLayer.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
|
||||
|
||||
modelBuilder.Entity("CategoryCategoryLadder", b =>
|
||||
{
|
||||
@@ -312,6 +312,9 @@ namespace DataLayer.Migrations
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<bool>("IsFinished")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<DateTime?>("LastDownloaded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -164,6 +164,9 @@ namespace DtoImporterService
|
||||
if (item.PictureLarge is not null)
|
||||
book.PictureLarge = item.PictureLarge;
|
||||
|
||||
if (item.IsFinished is not null)
|
||||
book.UserDefinedItem.IsFinished = item.IsFinished.Value;
|
||||
|
||||
// 2023-02-01
|
||||
// updateBook must update language on books which were imported before the migration which added language.
|
||||
// Can eventually delete this
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace FileManager
|
||||
lock (fsCacheLocker)
|
||||
{
|
||||
fsCache.Clear();
|
||||
fsCache.AddRange(FileUtility.SaferEnumerateFiles(RootDirectory, SearchPattern, SearchOption));
|
||||
fsCache.AddRange(SafestEnumerateFiles(RootDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace FileManager
|
||||
Stop();
|
||||
|
||||
lock (fsCacheLocker)
|
||||
fsCache.AddRange(FileUtility.SaferEnumerateFiles(RootDirectory, SearchPattern, SearchOption));
|
||||
fsCache.AddRange(SafestEnumerateFiles(RootDirectory));
|
||||
|
||||
directoryChangesEvents = new BlockingCollection<FileSystemEventArgs>();
|
||||
fileSystemWatcher = new FileSystemWatcher(RootDirectory)
|
||||
@@ -152,11 +152,23 @@ namespace FileManager
|
||||
if (Path.GetFileName(path).Contains("LibationContext.db") || !File.Exists(path) && !Directory.Exists(path))
|
||||
return;
|
||||
if (File.GetAttributes(path).HasFlag(FileAttributes.Directory))
|
||||
AddUniqueFiles(FileUtility.SaferEnumerateFiles(path, SearchPattern, SearchOption));
|
||||
AddUniqueFiles(SafestEnumerateFiles(path));
|
||||
else
|
||||
AddUniqueFile(path);
|
||||
}
|
||||
|
||||
private IEnumerable<LongPath> SafestEnumerateFiles(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FileUtility.SaferEnumerateFiles(path, SearchPattern, SearchOption);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUniqueFiles(IEnumerable<LongPath> newFiles)
|
||||
{
|
||||
foreach (var file in newFiles)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="8.0.0.1" />
|
||||
<PackageReference Include="Polly" Version="8.4.0" />
|
||||
<PackageReference Include="Dinah.Core" Version="9.0.0.1" />
|
||||
<PackageReference Include="Polly" Version="8.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
|
||||
<TrimMode>copyused</TrimMode>
|
||||
@@ -71,13 +71,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Avalonia" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.4" />
|
||||
<!--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.5" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<AssemblyName>Hangover</AssemblyName>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\classic</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -77,6 +77,10 @@
|
||||
<Style Selector="Button">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="ScrollBar">
|
||||
<!-- It's called AutoHide, but this is really the mouseover shrink/expand. -->
|
||||
<Setter Property="AllowAutoHide" Value="false"/>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
|
||||
<NativeMenu.Menu>
|
||||
|
||||
@@ -90,14 +90,10 @@ namespace LibationAvalonia
|
||||
|
||||
if (setupDialog.Config.LibationSettingsAreValid)
|
||||
{
|
||||
var theme
|
||||
= setupDialog.SelectedTheme.Content is nameof(ThemeVariant.Dark)
|
||||
? nameof(ThemeVariant.Dark)
|
||||
: nameof(ThemeVariant.Light);
|
||||
|
||||
string theme = setupDialog.SelectedTheme.Content as string;
|
||||
|
||||
setupDialog.Config.SetString(theme, nameof(ThemeVariant));
|
||||
|
||||
|
||||
await RunMigrationsAsync(setupDialog.Config);
|
||||
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||
@@ -208,9 +204,15 @@ namespace LibationAvalonia
|
||||
|
||||
private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is "Dark" ? ThemeVariant.Dark : ThemeVariant.Light;
|
||||
Current.RequestedThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) switch
|
||||
{
|
||||
nameof(ThemeVariant.Dark) => ThemeVariant.Dark,
|
||||
nameof(ThemeVariant.Light) => ThemeVariant.Light,
|
||||
// "System"
|
||||
_ => ThemeVariant.Default
|
||||
};
|
||||
|
||||
//Reload colors for current theme
|
||||
//Reload colors for current theme
|
||||
LoadStyles();
|
||||
var mainWindow = new MainWindow();
|
||||
desktop.MainWindow = MainWindow = mainWindow;
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
Width="800" Height="450"
|
||||
x:Class="LibationAvalonia.Dialogs.EditQuickFilters"
|
||||
Title="Audible Accounts"
|
||||
Icon="/Assets/libation.ico">
|
||||
Icon="/Assets/libation.ico"
|
||||
x:DataType="dialogs:EditQuickFilters">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<Grid.Styles>
|
||||
@@ -43,7 +45,14 @@
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
IsReadOnly="False"
|
||||
Binding="{Binding Name, Mode=TwoWay}"
|
||||
Header="Name"/>
|
||||
|
||||
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
IsReadOnly="False"
|
||||
|
||||
@@ -12,8 +12,18 @@ namespace LibationAvalonia.Dialogs
|
||||
public ObservableCollection<Filter> Filters { get; } = new();
|
||||
|
||||
public class Filter : ViewModels.ViewModelBase
|
||||
{
|
||||
private string _filterString;
|
||||
{
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private string _filterString;
|
||||
public string FilterString
|
||||
{
|
||||
get => _filterString;
|
||||
@@ -25,6 +35,9 @@ namespace LibationAvalonia.Dialogs
|
||||
}
|
||||
}
|
||||
public bool IsDefault { get; private set; } = true;
|
||||
|
||||
public QuickFilters.NamedFilter AsNamedFilter() => new(FilterString, Name);
|
||||
|
||||
}
|
||||
public EditQuickFilters()
|
||||
{
|
||||
@@ -40,7 +53,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
ControlToFocusOnShow = this.FindControl<Button>(nameof(saveBtn));
|
||||
|
||||
var allFilters = QuickFilters.Filters.Select(f => new Filter { FilterString = f }).ToList();
|
||||
var allFilters = QuickFilters.Filters.Select(f => new Filter { FilterString = f.Filter, Name = f.Name }).ToList();
|
||||
allFilters.Add(new Filter());
|
||||
|
||||
foreach (var f in allFilters)
|
||||
@@ -61,7 +74,7 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
protected override void SaveAndClose()
|
||||
{
|
||||
QuickFilters.ReplaceAll(Filters.Where(f => !f.IsDefault).Select(f => f.FilterString));
|
||||
QuickFilters.ReplaceAll(Filters.Where(f => !f.IsDefault).Select(x => x.AsNamedFilter()));
|
||||
base.SaveAndClose();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="350"
|
||||
x:Class="LibationAvalonia.Dialogs.SetupDialog"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Width="500" Height="350"
|
||||
Icon="/Assets/libation.ico"
|
||||
Title="Welcome to Libation">
|
||||
Title="Welcome to Libation"
|
||||
x:DataType="dialogs:SetupDialog">
|
||||
|
||||
<Grid
|
||||
Margin="10"
|
||||
@@ -58,6 +60,7 @@
|
||||
SelectedIndex="0"
|
||||
SelectedItem="{Binding SelectedTheme, Mode=OneWayToSource}">
|
||||
<ComboBox.Items>
|
||||
<ComboBoxItem Content="System" />
|
||||
<ComboBoxItem Content="Light" />
|
||||
<ComboBoxItem Content="Dark" />
|
||||
</ComboBox.Items>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationIcon>Assets/libation.ico</ApplicationIcon>
|
||||
@@ -74,13 +74,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.5" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.4" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.4" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -46,11 +46,6 @@ namespace LibationAvalonia
|
||||
try
|
||||
{
|
||||
var config = LibationScaffolding.RunPreConfigMigrations();
|
||||
|
||||
//Start as much work in parallel as possible.
|
||||
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
|
||||
var appBuilderTask = Task.Run(BuildAvaloniaApp);
|
||||
|
||||
if (config.LibationSettingsAreValid)
|
||||
{
|
||||
// most migrations go in here
|
||||
@@ -62,9 +57,7 @@ namespace LibationAvalonia
|
||||
App.LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
}
|
||||
|
||||
appBuilderTask.GetAwaiter().GetResult().SetupWithLifetime(classicLifetimeTask.GetAwaiter().GetResult());
|
||||
|
||||
classicLifetimeTask.Result.Start(null);
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private string lastGoodFilter = "";
|
||||
private string _filterString;
|
||||
private QuickFilters.NamedFilter lastGoodFilter = new(string.Empty, null);
|
||||
private QuickFilters.NamedFilter _selectedNamedFilter = new(string.Empty, null);
|
||||
private bool _firstFilterIsDefault = true;
|
||||
|
||||
/// <summary> Library filterting query </summary>
|
||||
public string FilterString { get => _filterString; set => this.RaiseAndSetIfChanged(ref _filterString, value); }
|
||||
public QuickFilters.NamedFilter SelectedNamedFilter { get => _selectedNamedFilter; set => this.RaiseAndSetIfChanged(ref _selectedNamedFilter, value); }
|
||||
public AvaloniaList<Control> QuickFilterMenuItems { get; } = new();
|
||||
/// <summary> Indicates if the first quick filter is the default filter </summary>
|
||||
public bool FirstFilterIsDefault { get => _firstFilterIsDefault; set => QuickFilters.UseDefault = this.RaiseAndSetIfChanged(ref _firstFilterIsDefault, value); }
|
||||
@@ -50,19 +50,19 @@ namespace LibationAvalonia.ViewModels
|
||||
QuickFilterMenuItems.Add(new Separator());
|
||||
}
|
||||
|
||||
public void AddQuickFilterBtn() => QuickFilters.Add(FilterString);
|
||||
public async Task FilterBtn() => await PerformFilter(FilterString);
|
||||
public void AddQuickFilterBtn() => QuickFilters.Add(SelectedNamedFilter);
|
||||
public async Task FilterBtn() => await PerformFilter(SelectedNamedFilter);
|
||||
public async Task FilterHelpBtn() => await new LibationAvalonia.Dialogs.SearchSyntaxDialog().ShowDialog(MainWindow);
|
||||
public void ToggleFirstFilterIsDefault() => FirstFilterIsDefault = !FirstFilterIsDefault;
|
||||
public async Task EditQuickFiltersAsync() => await new LibationAvalonia.Dialogs.EditQuickFilters().ShowDialog(MainWindow);
|
||||
public async Task PerformFilter(string filterString)
|
||||
public async Task PerformFilter(QuickFilters.NamedFilter namedFilter)
|
||||
{
|
||||
FilterString = filterString;
|
||||
SelectedNamedFilter = namedFilter;
|
||||
|
||||
try
|
||||
{
|
||||
await ProductsDisplay.Filter(filterString);
|
||||
lastGoodFilter = filterString;
|
||||
await ProductsDisplay.Filter(namedFilter.Filter);
|
||||
lastGoodFilter = namedFilter;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -99,8 +99,8 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
var command = ReactiveCommand.Create(async () => await PerformFilter(filter));
|
||||
|
||||
var menuItem = new MenuItem { Header = $"{++index}: {filter}", Command = command };
|
||||
var nativeMenuItem = new NativeMenuItem { Header = $"{index}: {filter}", Command = command };
|
||||
var menuItem = new MenuItem { Header = $"{++index}: {(string.IsNullOrWhiteSpace(filter.Name) ? filter.Filter : filter.Name)}", Command = command };
|
||||
var nativeMenuItem = new NativeMenuItem { Header = $"{index}: {(string.IsNullOrWhiteSpace(filter.Name) ? filter.Filter : filter.Name)}", Command = command };
|
||||
|
||||
if (Configuration.IsMacOs && index <= 10)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
public NAudio.Lame.EncoderQuality SelectedEncoderQuality { get; set; }
|
||||
|
||||
public AvaloniaList<EnumDiaplay<SampleRate>> SampleRates { get; }
|
||||
= new(Enum.GetValues<SampleRate>().Select(v => new EnumDiaplay<SampleRate>(v, $"{(int)v} Hz")));
|
||||
= new(Enum.GetValues<SampleRate>()
|
||||
.Where(r => r >= SampleRate.Hz_8000 && r <= SampleRate.Hz_48000)
|
||||
.Select(v => new EnumDiaplay<SampleRate>(v, $"{(int)v} Hz")));
|
||||
|
||||
public AvaloniaList<NAudio.Lame.EncoderQuality> EncoderQualities { get; }
|
||||
= new(
|
||||
|
||||
@@ -31,10 +31,10 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
LoggingLevel = config.LogLevel;
|
||||
GridScaleFactor = scaleFactorToLinearRange(config.GridScaleFactor);
|
||||
GridFontScaleFactor = scaleFactorToLinearRange(config.GridFontScaleFactor);
|
||||
ThemeVariant = initialThemeVariant
|
||||
= Configuration.Instance.GetString(propertyName: nameof(ThemeVariant)) is nameof(Avalonia.Styling.ThemeVariant.Dark)
|
||||
? nameof(Avalonia.Styling.ThemeVariant.Dark)
|
||||
: nameof(Avalonia.Styling.ThemeVariant.Light);
|
||||
|
||||
ThemeVariant = initialThemeVariant = Configuration.Instance.GetString(propertyName: nameof(ThemeVariant));
|
||||
if (string.IsNullOrWhiteSpace(initialThemeVariant))
|
||||
ThemeVariant = initialThemeVariant = "System";
|
||||
}
|
||||
|
||||
public void SaveSettings(Configuration config)
|
||||
@@ -83,7 +83,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
public string GridScaleFactorText { get; } = Configuration.GetDescription(nameof(Configuration.GridScaleFactor));
|
||||
public string GridFontScaleFactorText { get; } = Configuration.GetDescription(nameof(Configuration.GridFontScaleFactor));
|
||||
public string BetaOptInText { get; } = Configuration.GetDescription(nameof(Configuration.BetaOptIn));
|
||||
public string[] Themes { get; } = { nameof(Avalonia.Styling.ThemeVariant.Light), nameof(Avalonia.Styling.ThemeVariant.Dark) };
|
||||
public string[] Themes { get; } = { "System", nameof(Avalonia.Styling.ThemeVariant.Light), nameof(Avalonia.Styling.ThemeVariant.Dark) };
|
||||
|
||||
public string BooksDirectory { get; set; }
|
||||
public bool SavePodcastsToParentFolder { get; set; }
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
<Button IsVisible="{CompiledBinding RemoveButtonsVisible}" Command="{CompiledBinding DoneRemovingBtn}" Content="Done Removing Books"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{CompiledBinding !RemoveButtonsVisible}" Text="{CompiledBinding FilterString, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
<TextBox Grid.Column="1" Margin="10,0,0,0" Name="filterSearchTb" IsVisible="{CompiledBinding !RemoveButtonsVisible}" Text="{CompiledBinding SelectedNamedFilter.Filter, Mode=TwoWay}" KeyDown="filterSearchTb_KeyPress" />
|
||||
|
||||
<StackPanel Grid.Column="2" Height="30" Orientation="Horizontal">
|
||||
<Button Name="filterBtn" Command="{CompiledBinding FilterBtn}" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="Filter"/>
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
{
|
||||
await ViewModel.PerformFilter(ViewModel.FilterString);
|
||||
await ViewModel.PerformFilter(ViewModel.SelectedNamedFilter);
|
||||
|
||||
// silence the 'ding'
|
||||
e.Handled = true;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Linux-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\MacOS-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -25,9 +25,18 @@ namespace LibationFileManager
|
||||
static AudibleFileStorage()
|
||||
{
|
||||
//Clean up any partially-decrypted files from previous Libation instances.
|
||||
//Do no clean DownloadsInProgressDirectory because those files are resumable
|
||||
foreach (var tempFile in Directory.EnumerateFiles(DecryptInProgressDirectory))
|
||||
FileUtility.SaferDelete(tempFile);
|
||||
//Do not clean DownloadsInProgressDirectory. Those files are resumable.
|
||||
try
|
||||
{
|
||||
foreach (var tempFile in FileUtility.SaferEnumerateFiles(DecryptInProgressDirectory))
|
||||
FileUtility.SaferDelete(tempFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// since this is a static constructor which may be called very early, do not assume Serilog is initialized
|
||||
try { Serilog.Log.Error(ex, "Error cleaning up partially-decrypted files"); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ namespace LibationFileManager
|
||||
set => setTemplate<Templates.ChapterFileTemplate>(value);
|
||||
}
|
||||
|
||||
[Description("How to format the file's Tile stored in metadata")]
|
||||
[Description("How to format the file's Title stored in metadata")]
|
||||
public string ChapterTitleTemplate
|
||||
{
|
||||
get => getTemplate<Templates.ChapterTitleTemplate>();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
|
||||
<PackageReference Include="NameParserSharp" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using Dinah.Core.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dinah.Core.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationFileManager
|
||||
@@ -14,24 +14,21 @@ namespace LibationFileManager
|
||||
|
||||
public static event EventHandler? UseDefaultChanged;
|
||||
|
||||
internal class FilterState
|
||||
public class FilterState
|
||||
{
|
||||
public bool UseDefault { get; set; }
|
||||
public List<string> Filters { get; set; } = new List<string>();
|
||||
public List<NamedFilter> Filters { get; set; } = new();
|
||||
}
|
||||
|
||||
public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "QuickFilters.json");
|
||||
|
||||
|
||||
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
||||
static FilterState inMemoryState { get; }
|
||||
= File.Exists(JsonFile) && JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile)) is FilterState inMemState
|
||||
? inMemState
|
||||
: new FilterState();
|
||||
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
||||
public static FilterState InMemoryState { get; set; } = null!;
|
||||
|
||||
public static bool UseDefault
|
||||
{
|
||||
get => inMemoryState.UseDefault;
|
||||
get => InMemoryState.UseDefault;
|
||||
set
|
||||
{
|
||||
if (UseDefault == value)
|
||||
@@ -39,7 +36,7 @@ namespace LibationFileManager
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.UseDefault = value;
|
||||
InMemoryState.UseDefault = value;
|
||||
save(false);
|
||||
}
|
||||
|
||||
@@ -47,68 +44,81 @@ namespace LibationFileManager
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Filters => inMemoryState.Filters.AsReadOnly();
|
||||
|
||||
public static void Add(string filter)
|
||||
// Note that records overload equality automagically, so should be able to
|
||||
// compare these the same way as comparing simple strings.
|
||||
public record NamedFilter(string Filter, string Name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
public string Filter { get; set; } = Filter;
|
||||
public string? Name { get; set; } = Name;
|
||||
}
|
||||
|
||||
public static IEnumerable<NamedFilter> Filters => InMemoryState.Filters.AsReadOnly();
|
||||
|
||||
public static void Add(NamedFilter namedFilter)
|
||||
{
|
||||
if (namedFilter == null)
|
||||
throw new ArgumentNullException(nameof(namedFilter));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(namedFilter.Filter))
|
||||
return;
|
||||
filter = filter.Trim();
|
||||
namedFilter.Filter = namedFilter.Filter?.Trim() ?? string.Empty;
|
||||
namedFilter.Name = namedFilter.Name?.Trim() ?? null;
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
// check for duplicate
|
||||
if (inMemoryState.Filters.ContainsInsensative(filter))
|
||||
// check for duplicates
|
||||
if (InMemoryState.Filters.Select(x => x.Filter).ContainsInsensative(namedFilter.Filter))
|
||||
return;
|
||||
|
||||
inMemoryState.Filters.Add(filter);
|
||||
InMemoryState.Filters.Add(namedFilter);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(string filter)
|
||||
public static void Remove(NamedFilter filter)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.Filters.Remove(filter);
|
||||
InMemoryState.Filters.Remove(filter);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Edit(string oldFilter, string newFilter)
|
||||
public static void Edit(NamedFilter oldFilter, NamedFilter newFilter)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
var index = inMemoryState.Filters.IndexOf(oldFilter);
|
||||
var index = InMemoryState.Filters.IndexOf(oldFilter);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
inMemoryState.Filters = inMemoryState.Filters.Select(f => f == oldFilter ? newFilter : f).ToList();
|
||||
InMemoryState.Filters = InMemoryState.Filters.Select(f => f == oldFilter ? newFilter : f).ToList();
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReplaceAll(IEnumerable<string> filters)
|
||||
public static void ReplaceAll(IEnumerable<NamedFilter> filters)
|
||||
{
|
||||
filters = filters
|
||||
.Where(f => !string.IsNullOrWhiteSpace(f))
|
||||
.Distinct()
|
||||
.Select(f => f.Trim());
|
||||
.Where(f => !string.IsNullOrWhiteSpace(f.Filter))
|
||||
.Distinct();
|
||||
foreach (var filter in filters)
|
||||
filter.Filter = filter.Filter.Trim();
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.Filters = new List<string>(filters);
|
||||
InMemoryState.Filters = new List<NamedFilter>(filters);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private static object locker { get; } = new object();
|
||||
private static object locker { get; } = new();
|
||||
|
||||
// ONLY call this within lock()
|
||||
private static void save(bool invokeUpdatedEvent = true)
|
||||
{
|
||||
// create json if not exists
|
||||
void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(inMemoryState, Formatting.Indented));
|
||||
void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(InMemoryState, Formatting.Indented));
|
||||
try { resave(); }
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace LibationFileManager
|
||||
try
|
||||
{
|
||||
//Currently only works for Windows and macOS
|
||||
if (!Configuration.Instance.UseCoverAsFolderIcon || Configuration.IsLinux)
|
||||
if (!Configuration.Instance.UseCoverAsFolderIcon)
|
||||
return;
|
||||
|
||||
// get path of cover art in Images dir. Download first if not exists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -9,7 +9,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LuceneNet303r2" Version="3.0.3.8" />
|
||||
<PackageReference Include="LuceneNet303r2" Version="3.0.3.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace LibationSearchEngine
|
||||
{ FieldType.Bool, lb => (lb.Book.UserDefinedItem.BookStatus == LiberatedStatus.Error).ToString(), "LiberatedError" },
|
||||
{ FieldType.Bool, lb => lb.Book.IsEpisodeChild().ToString(), "Podcast", "Podcasts", "IsPodcast", "Episode", "Episodes", "IsEpisode" },
|
||||
{ FieldType.Bool, lb => lb.AbsentFromLastScan.ToString(), "AbsentFromLastScan", "Absent" },
|
||||
{ FieldType.Bool, lb => (!string.IsNullOrWhiteSpace(lb.Book.SeriesNames())).ToString(), "IsInSeries", "InSeries" },
|
||||
{ FieldType.Bool, lb => lb.Book.UserDefinedItem.IsFinished.ToString(), nameof(UserDefinedItem.IsFinished), "Finished", "IsFinished" },
|
||||
// all numbers are padded to 8 char.s
|
||||
// This will allow a single method to auto-pad numbers. The method will match these as well as date: yyyymmdd
|
||||
{ FieldType.Number, lb => lb.Book.LengthInMinutes.ToLuceneString(), nameof(Book.LengthInMinutes), "Length", "Minutes" },
|
||||
|
||||
@@ -53,7 +53,13 @@ namespace LibationUiBase.GridView
|
||||
public bool IsSeries { get; }
|
||||
public bool IsEpisode { get; }
|
||||
public bool IsBook => !IsSeries && !IsEpisode;
|
||||
public bool IsUnavailable => !IsSeries & isAbsent & (BookStatus is not LiberatedStatus.Liberated || PdfStatus is not null and not LiberatedStatus.Liberated);
|
||||
public bool IsUnavailable
|
||||
=> !IsSeries
|
||||
& isAbsent
|
||||
& (
|
||||
BookStatus is not LiberatedStatus.Liberated
|
||||
|| PdfStatus is not null and not LiberatedStatus.Liberated
|
||||
);
|
||||
public double Opacity => !IsSeries && Book.UserDefinedItem.Tags.ContainsInsensitive("hidden") ? 0.4 : 1;
|
||||
public abstract object BackgroundBrush { get; }
|
||||
public object ButtonImage => GetLiberateIcon();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -28,116 +28,127 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
this.Original = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.Delete = new DisableButtonColumn();
|
||||
this.Filter = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.MoveUp = new DisableButtonColumn();
|
||||
this.MoveDown = new DisableButtonColumn();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
cancelBtn = new System.Windows.Forms.Button();
|
||||
saveBtn = new System.Windows.Forms.Button();
|
||||
dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
Original = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
Delete = new DisableButtonColumn();
|
||||
FilterName = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
Filter = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
MoveUp = new DisableButtonColumn();
|
||||
MoveDown = new DisableButtonColumn();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 415);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 2;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
cancelBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
cancelBtn.Location = new System.Drawing.Point(1248, 726);
|
||||
cancelBtn.Margin = new System.Windows.Forms.Padding(5);
|
||||
cancelBtn.Name = "cancelBtn";
|
||||
cancelBtn.Size = new System.Drawing.Size(131, 40);
|
||||
cancelBtn.TabIndex = 2;
|
||||
cancelBtn.Text = "Cancel";
|
||||
cancelBtn.UseVisualStyleBackColor = true;
|
||||
cancelBtn.Click += cancelBtn_Click;
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 415);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.saveBtn.TabIndex = 1;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
saveBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
saveBtn.Location = new System.Drawing.Point(1071, 726);
|
||||
saveBtn.Margin = new System.Windows.Forms.Padding(5);
|
||||
saveBtn.Name = "saveBtn";
|
||||
saveBtn.Size = new System.Drawing.Size(131, 40);
|
||||
saveBtn.TabIndex = 1;
|
||||
saveBtn.Text = "Save";
|
||||
saveBtn.UseVisualStyleBackColor = true;
|
||||
saveBtn.Click += saveBtn_Click;
|
||||
//
|
||||
// dataGridView1
|
||||
//
|
||||
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.Original,
|
||||
this.Delete,
|
||||
this.Filter,
|
||||
this.MoveUp,
|
||||
this.MoveDown});
|
||||
this.dataGridView1.Location = new System.Drawing.Point(12, 12);
|
||||
this.dataGridView1.MultiSelect = false;
|
||||
this.dataGridView1.Name = "dataGridView1";
|
||||
this.dataGridView1.Size = new System.Drawing.Size(776, 397);
|
||||
this.dataGridView1.TabIndex = 0;
|
||||
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
|
||||
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
|
||||
dataGridView1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { Original, Delete, FilterName, Filter, MoveUp, MoveDown });
|
||||
dataGridView1.Location = new System.Drawing.Point(21, 21);
|
||||
dataGridView1.Margin = new System.Windows.Forms.Padding(5);
|
||||
dataGridView1.MultiSelect = false;
|
||||
dataGridView1.Name = "dataGridView1";
|
||||
dataGridView1.RowHeadersWidth = 72;
|
||||
dataGridView1.Size = new System.Drawing.Size(1358, 695);
|
||||
dataGridView1.TabIndex = 0;
|
||||
dataGridView1.CellContentClick += DataGridView1_CellContentClick;
|
||||
dataGridView1.DefaultValuesNeeded += dataGridView1_DefaultValuesNeeded;
|
||||
//
|
||||
// Original
|
||||
//
|
||||
this.Original.HeaderText = "Original";
|
||||
this.Original.Name = "Original";
|
||||
this.Original.ReadOnly = true;
|
||||
this.Original.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
this.Original.Visible = false;
|
||||
this.Original.Width = 48;
|
||||
Original.HeaderText = "Original";
|
||||
Original.MinimumWidth = 9;
|
||||
Original.Name = "Original";
|
||||
Original.ReadOnly = true;
|
||||
Original.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
Original.Visible = false;
|
||||
Original.Width = 150;
|
||||
//
|
||||
// Delete
|
||||
//
|
||||
this.Delete.HeaderText = "Delete";
|
||||
this.Delete.Name = "Delete";
|
||||
this.Delete.ReadOnly = true;
|
||||
this.Delete.Text = "x";
|
||||
this.Delete.Width = 44;
|
||||
Delete.HeaderText = "Delete";
|
||||
Delete.MinimumWidth = 9;
|
||||
Delete.Name = "Delete";
|
||||
Delete.ReadOnly = true;
|
||||
Delete.Text = "x";
|
||||
Delete.Width = 127;
|
||||
//
|
||||
// FilterName
|
||||
//
|
||||
FilterName.HeaderText = "Name";
|
||||
FilterName.MinimumWidth = 300;
|
||||
FilterName.Name = "FilterName";
|
||||
FilterName.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
FilterName.Width = 300;
|
||||
//
|
||||
// Filter
|
||||
//
|
||||
this.Filter.HeaderText = "Filter";
|
||||
this.Filter.Name = "Filter";
|
||||
this.Filter.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
this.Filter.Width = 35;
|
||||
Filter.HeaderText = "Filter";
|
||||
Filter.MinimumWidth = 400;
|
||||
Filter.Name = "Filter";
|
||||
Filter.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
Filter.Width = 400;
|
||||
//
|
||||
// MoveUp
|
||||
//
|
||||
this.MoveUp.HeaderText = "Move Up";
|
||||
this.MoveUp.Name = "MoveUp";
|
||||
this.MoveUp.ReadOnly = true;
|
||||
this.MoveUp.Text = "^";
|
||||
this.MoveUp.Width = 57;
|
||||
MoveUp.HeaderText = "Move Up";
|
||||
MoveUp.MinimumWidth = 9;
|
||||
MoveUp.Name = "MoveUp";
|
||||
MoveUp.ReadOnly = true;
|
||||
MoveUp.Text = "^";
|
||||
MoveUp.Width = 169;
|
||||
//
|
||||
// MoveDown
|
||||
//
|
||||
this.MoveDown.HeaderText = "Move Down";
|
||||
this.MoveDown.Name = "MoveDown";
|
||||
this.MoveDown.ReadOnly = true;
|
||||
this.MoveDown.Text = "v";
|
||||
this.MoveDown.Width = 71;
|
||||
MoveDown.HeaderText = "Move Down";
|
||||
MoveDown.MinimumWidth = 9;
|
||||
MoveDown.Name = "MoveDown";
|
||||
MoveDown.ReadOnly = true;
|
||||
MoveDown.Text = "v";
|
||||
MoveDown.Width = 215;
|
||||
//
|
||||
// EditQuickFilters
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.dataGridView1);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Name = "EditQuickFilters";
|
||||
this.Text = "Edit Quick Filters";
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
AcceptButton = saveBtn;
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(168F, 168F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
CancelButton = cancelBtn;
|
||||
ClientSize = new System.Drawing.Size(1400, 788);
|
||||
Controls.Add(dataGridView1);
|
||||
Controls.Add(cancelBtn);
|
||||
Controls.Add(saveBtn);
|
||||
Margin = new System.Windows.Forms.Padding(5);
|
||||
Name = "EditQuickFilters";
|
||||
Text = "Edit Quick Filters";
|
||||
((System.ComponentModel.ISupportInitialize)dataGridView1).EndInit();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -146,6 +157,7 @@
|
||||
private System.Windows.Forms.DataGridView dataGridView1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn Original;
|
||||
private DisableButtonColumn Delete;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn FilterName;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn Filter;
|
||||
private DisableButtonColumn MoveUp;
|
||||
private DisableButtonColumn MoveDown;
|
||||
|
||||
@@ -6,14 +6,6 @@ using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public class DisableButtonColumn : DataGridViewButtonColumn
|
||||
{
|
||||
public DisableButtonColumn()
|
||||
{
|
||||
CellTemplate = new EditQuickFilters.DisableButtonCell();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EditQuickFilters : Form
|
||||
{
|
||||
private const string BLACK_UP_POINTING_TRIANGLE = "\u25B2";
|
||||
@@ -21,7 +13,8 @@ namespace LibationWinForms.Dialogs
|
||||
private const string COL_Original = nameof(Original);
|
||||
private const string COL_Delete = nameof(Delete);
|
||||
private const string COL_Filter = nameof(Filter);
|
||||
private const string COL_MoveUp = nameof(MoveUp);
|
||||
private const string COL_FilterName = nameof(FilterName);
|
||||
private const string COL_MoveUp = nameof(MoveUp);
|
||||
private const string COL_MoveDown = nameof(MoveDown);
|
||||
|
||||
internal class DisableButtonCell : AccessibleDataGridViewButtonCell
|
||||
@@ -76,10 +69,11 @@ namespace LibationWinForms.Dialogs
|
||||
return;
|
||||
|
||||
foreach (var filter in filters)
|
||||
dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
}
|
||||
dataGridView1.Rows.Add(filter.Filter, "X", filter.Name, filter.Filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
//dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
}
|
||||
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
{
|
||||
e.Row.Cells[COL_Delete].Value = "X";
|
||||
e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE;
|
||||
@@ -90,7 +84,9 @@ namespace LibationWinForms.Dialogs
|
||||
{
|
||||
var list = dataGridView1.Rows
|
||||
.OfType<DataGridViewRow>()
|
||||
.Select(r => r.Cells[COL_Filter].Value?.ToString())
|
||||
.Select(r => new QuickFilters.NamedFilter(
|
||||
r.Cells[COL_Filter].Value?.ToString(),
|
||||
r.Cells[COL_FilterName].Value?.ToString()))
|
||||
.ToList();
|
||||
QuickFilters.ReplaceAll(list);
|
||||
|
||||
@@ -137,4 +133,12 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
}
|
||||
}
|
||||
public class DisableButtonColumn : DataGridViewButtonColumn
|
||||
{
|
||||
public DisableButtonColumn()
|
||||
{
|
||||
CellTemplate = new EditQuickFilters.DisableButtonCell();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, 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="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -61,16 +120,10 @@
|
||||
<metadata name="Original.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Delete.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<metadata name="FilterName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Filter.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="MoveUp.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="MoveDown.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -41,6 +41,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
maxSampleRateCb.Items.AddRange(
|
||||
Enum.GetValues<AAXClean.SampleRate>()
|
||||
.Where(r => r >= AAXClean.SampleRate.Hz_8000 && r <= AAXClean.SampleRate.Hz_48000)
|
||||
.Select(v => new EnumDiaplay<AAXClean.SampleRate>(v, $"{(int)v} Hz"))
|
||||
.ToArray());
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ namespace LibationWinForms
|
||||
var quickFilterMenuItem = new ToolStripMenuItem
|
||||
{
|
||||
Tag = quickFilterTag,
|
||||
Text = $"&{++index}: {filter}"
|
||||
Text = $"&{++index}: {(string.IsNullOrWhiteSpace(filter.Name) ? filter.Filter : filter.Name)}"
|
||||
};
|
||||
quickFilterMenuItem.Click += (_, __) => performFilter(filter);
|
||||
quickFilterMenuItem.Click += (_, __) => performFilter(filter.Filter);
|
||||
quickFiltersToolStripMenuItem.DropDownItems.Add(quickFilterMenuItem);
|
||||
}
|
||||
}
|
||||
@@ -47,14 +47,17 @@ namespace LibationWinForms
|
||||
private void firstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> QuickFilters.UseDefault = !firstFilterIsDefaultToolStripMenuItem.Checked;
|
||||
|
||||
private void addQuickFilterBtn_Click(object sender, EventArgs e) => QuickFilters.Add(this.filterSearchTb.Text);
|
||||
private void addQuickFilterBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
QuickFilters.Add(new QuickFilters.NamedFilter(this.filterSearchTb.Text, null));
|
||||
}
|
||||
|
||||
private void editQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters().ShowDialog();
|
||||
private void editQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters().ShowDialog();
|
||||
|
||||
private void productsDisplay_InitialLoaded(object sender, EventArgs e)
|
||||
{
|
||||
if (QuickFilters.UseDefault)
|
||||
performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
performFilter(QuickFilters.Filters.FirstOrDefault().Filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +89,34 @@ namespace LibationWinForms.GridView
|
||||
displayWindow.Show(this);
|
||||
}
|
||||
|
||||
private void productsGrid_DetailsClicked(ILibraryBookEntry liveGridEntry)
|
||||
private async void productsGrid_DetailsClicked(ILibraryBookEntry liveGridEntry)
|
||||
{
|
||||
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
|
||||
if (bookDetailsForm.ShowDialog() == DialogResult.OK)
|
||||
liveGridEntry.LibraryBook.UpdateUserDefinedItem(bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
||||
// HACK: workaround for a Winforms bug.
|
||||
// This event is fired by the DataGridCell.OnMouseUpInternal
|
||||
// method. If any user changes made in the BookDetailsDialog
|
||||
// result in the entry's row being removed from the DataGridView,
|
||||
// then when this event handler returns, OnMouseUpInternal will
|
||||
// throw a NRE trying to access the DataGridCell.DataGridView
|
||||
// property.
|
||||
|
||||
//Steps to cause the bug:
|
||||
// * book has tag: asdf
|
||||
// * filter is `[asdf]`
|
||||
// * tag asdf is removed from book
|
||||
// * DataGridView throws NRE
|
||||
|
||||
//The workaround is to make this event handler execute
|
||||
//asynchronously so that DataGridCell.OnMouseUpInternal completes
|
||||
//before the user can change the DataGridView state.
|
||||
|
||||
await Task.Run(() => this.Invoke(runAsync));
|
||||
|
||||
void runAsync()
|
||||
{
|
||||
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
|
||||
if (bookDetailsForm.ShowDialog() == DialogResult.OK)
|
||||
liveGridEntry.LibraryBook.UpdateUserDefinedItem(bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>libation.ico</ApplicationIcon>
|
||||
@@ -14,6 +14,7 @@
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<NoWarn>$(NoWarn);WFO1000</NoWarn>
|
||||
<!-- Version is now in AppScaffolding.csproj -->
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -41,7 +42,7 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="8.0.0.1" />
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="9.0.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\classic</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -6,3 +6,5 @@ Comment=Liberate your Audiobooks
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=x-content/unix-software;
|
||||
Categories=Audio;AudioVideo;
|
||||
Keywords=audible;audio;audiobook;book;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\..\bin\Publish\Linux-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2024 rmcrackan <rmcrackan@gmail.com> -->
|
||||
<component type="desktop-application">
|
||||
<id>com.getlibation.Libation</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>GPL-3.0-only</project_license>
|
||||
<content_rating type="oars-1.1" />
|
||||
<name>Libation</name>
|
||||
<summary>Liberate your Audiobooks</summary>
|
||||
<developer id="com.getlibation">
|
||||
<name>rmcrackan</name>
|
||||
</developer>
|
||||
<update_contact>rmcrackan@gmail.com</update_contact>
|
||||
<description>
|
||||
<p>Free and open source app to manage your Audible books.</p>
|
||||
<ul>
|
||||
<li>Import library from audible, including cover art</li>
|
||||
<li>Download and remove DRM from all books</li>
|
||||
<li>Download accompanying PDFs</li>
|
||||
<li>Add tags to books for better organization</li>
|
||||
<li>Powerful advanced search built on the Lucene search engine</li>
|
||||
<li>Customizable saved filters for common searches</li>
|
||||
<li>Open source</li>
|
||||
<li>Supports all regions: US, UK, Canada, Germany, France, Australia, Japan, India, and Spain</li>
|
||||
</ul>
|
||||
</description>
|
||||
<launchable type="desktop-id">com.getlibation.Libation.desktop</launchable>
|
||||
<url type="bugtracker">https://github.com/rmcrackan/Libation/issues</url>
|
||||
<url type="donation">https://paypal.me/mcrackan?locale.x=en_us</url>
|
||||
<url type="homepage">https://getlibation.com</url>
|
||||
<url type="vcs-browser">https://github.com/rmcrackan/Libation</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/main-page-light.png</image>
|
||||
<caption>Listing of audiobooks</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/main-page-dark.png</image>
|
||||
<caption>Listing of audiobooks</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/filter.png</image>
|
||||
<caption>Filtered listing of audiobooks</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/important-settings.png</image>
|
||||
<caption>Settings page with basic settings</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/audio-file-settings.png</image>
|
||||
<caption>Settings for audio file format and conversion</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/download-decrypt-settings.png</image>
|
||||
<caption>Download and decrypt settings</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/rmcrackan/Libation/refs/heads/master/Source/LoadByOS/LinuxConfigApp/screenshots/accounts.png</image>
|
||||
<caption>Account list</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="11.5.2" date="2024-10-25">
|
||||
<description>
|
||||
<p>Update Bundle_MacOS.sh to address #854, #1020</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="11.1" date="2023-10-18">
|
||||
<description>
|
||||
<p>New locale: Brazil</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
BIN
Source/LoadByOS/LinuxConfigApp/screenshots/accounts.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 118 KiB |
BIN
Source/LoadByOS/LinuxConfigApp/screenshots/filter.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 87 KiB |
BIN
Source/LoadByOS/LinuxConfigApp/screenshots/main-page-dark.png
Normal file
|
After Width: | Height: | Size: 371 KiB |
BIN
Source/LoadByOS/LinuxConfigApp/screenshots/main-page-light.png
Normal file
|
After Width: | Height: | Size: 391 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<!--<TargetFramework>net8.0-macos</TargetFramework>-->
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\..\bin\Publish\MacOS-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\..\bin\Publish\classic</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
@@ -26,7 +26,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2478.35" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3065.39" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
@@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="8.0.0.1" />
|
||||
<PackageReference Include="Dinah.Core" Version="9.0.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||