Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
003f80098a
|
||
|
|
ad8a6743ab
|
||
|
|
c61404dc71
|
||
|
|
a5b95c165a
|
||
|
|
8ec4ec018b
|
||
|
|
642a5c0a87
|
||
|
|
9128e68d0a
|
||
|
|
ddc18ae21a
|
||
|
|
4b8b9cde57
|
||
|
|
238348f6ec
|
||
|
|
37f697d1b4
|
||
|
|
a70019b88f
|
||
|
|
fa02b70bf0 | ||
|
|
76f9f4da9a | ||
|
|
49be500b8c | ||
|
|
78918b6d88 | ||
|
|
9b98e53b55 | ||
|
|
b799fe3865 | ||
|
|
0e869e1e98 | ||
|
|
196bc55f35 | ||
|
|
e29bd67a3e | ||
|
|
424ed62229
|
||
|
|
ab7542651b
|
||
|
|
a222f40616
|
||
|
|
699e929ed2 | ||
|
|
5279d37ea2 | ||
|
|
a4b8a23424
|
||
|
|
f2ee1e71f6
|
No files matched your search
@@ -1,11 +0,0 @@
|
|||||||
### AI assistance (if any):
|
|
||||||
- List tools here and files touched by them
|
|
||||||
|
|
||||||
### Authorship & Understanding
|
|
||||||
|
|
||||||
- [ ] I wrote or heavily modified this code myself
|
|
||||||
- [ ] I understand how it works end-to-end
|
|
||||||
- [ ] I can maintain this code in the future
|
|
||||||
- [ ] No undisclosed AI-generated code was used
|
|
||||||
- [ ] If AI assistance was used, it is documented below
|
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
### Description
|
||||||
|
<!-- What problem is this PR solving? -->
|
||||||
|
|
||||||
|
### Alternatives Considered (Optional)
|
||||||
|
<!-- What alternatives did you consider? Why did you choose this approach? -->
|
||||||
|
|
||||||
|
### AI assistance (if any)
|
||||||
|
<!-- Required if AI assistance was used to write or modify code in this PR. -->
|
||||||
|
<!-- - List tools here -->
|
||||||
|
|
||||||
|
### Authorship & Understanding
|
||||||
|
|
||||||
|
- [ ] I wrote or heavily modified this code myself. If I didn't, I gave proper attribution to AI assistance above.
|
||||||
|
- [ ] I understand how it works end-to-end
|
||||||
|
- [ ] I can maintain this code in the future
|
||||||
|
- [ ] No undisclosed AI-generated code was used
|
||||||
|
- [ ] If AI assistance was used, it is documented above
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Rust stable
|
- name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
toolchain: [stable, beta]
|
toolchain: [stable, beta]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Rust stable
|
- name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
components: clippy
|
components: clippy
|
||||||
|
|
||||||
- name: Run clippy action
|
- name: Run clippy action
|
||||||
uses: clechasseur/rs-clippy-check@v3
|
uses: clechasseur/rs-clippy-check@v5
|
||||||
|
|
||||||
- name: Cache Cargo dependencies
|
- name: Cache Cargo dependencies
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
name: nightly / doc
|
name: nightly / doc
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Rust nightly
|
- name: Install Rust nightly
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
name: 1.95.0 / check
|
name: 1.95.0 / check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install 1.95.0
|
- name: Install 1.95.0
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
if: env.ACT != 'true'
|
if: env.ACT != 'true'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }}
|
ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
if: env.ACT == 'true'
|
if: env.ACT == 'true'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
|
||||||
@@ -219,13 +219,13 @@ jobs:
|
|||||||
git push origin --follow-tags
|
git push origin --follow-tags
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Upload the changed Cargo files (Act)
|
- name: Upload the changed Cargo files (Act)
|
||||||
if: env.ACT == 'true'
|
if: env.ACT == 'true'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: bumped-cargo-files
|
name: bumped-cargo-files
|
||||||
path: |
|
path: |
|
||||||
@@ -267,7 +267,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
@@ -279,18 +279,18 @@ jobs:
|
|||||||
|
|
||||||
- name: Get bumped Cargo files (Act)
|
- name: Get bumped Cargo files (Act)
|
||||||
if: env.ACT == 'true'
|
if: env.ACT == 'true'
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: bumped-cargo-files
|
name: bumped-cargo-files
|
||||||
path: ${{ github.workspace }}
|
path: ${{ github.workspace }}
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v5
|
||||||
name: Cache Cargo registry
|
name: Cache Cargo registry
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v5
|
||||||
if: startsWith(matrix.job.name, 'linux-')
|
if: startsWith(matrix.job.name, 'linux-')
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin
|
path: ~/.cargo/bin
|
||||||
@@ -375,7 +375,7 @@ jobs:
|
|||||||
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/
|
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: artifacts-${{ env.RELEASE_NAME }}
|
name: artifacts-${{ env.RELEASE_NAME }}
|
||||||
path: artifacts
|
path: artifacts
|
||||||
@@ -393,12 +393,12 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -421,7 +421,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create a GitHub Release
|
- name: Create a GitHub Release
|
||||||
if: env.ACT != 'true'
|
if: env.ACT != 'true'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v3
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@@ -457,7 +457,7 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -474,12 +474,12 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Get release artifacts
|
- name: Get release artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -528,12 +528,12 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Get release artifacts
|
- name: Get release artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -592,12 +592,12 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Get release artifacts
|
- name: Get release artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -618,20 +618,20 @@ jobs:
|
|||||||
echo "Release version: ${{ env.version }}"
|
echo "Release version: ${{ env.version }}"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: env.ACT != 'true'
|
if: env.ACT != 'true'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Push to Docker Hub
|
- name: Push to Docker Hub
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
@@ -651,13 +651,13 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Get bumped Cargo files (Act)
|
- name: Get bumped Cargo files (Act)
|
||||||
if: env.ACT == 'true'
|
if: env.ACT == 'true'
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: bumped-cargo-files
|
name: bumped-cargo-files
|
||||||
path: ${{ github.workspace }}
|
path: ${{ github.workspace }}
|
||||||
@@ -668,13 +668,13 @@ jobs:
|
|||||||
git fetch --all
|
git fetch --all
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v5
|
||||||
name: Cache Cargo registry
|
name: Cache Cargo registry
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin
|
path: ~/.cargo/bin
|
||||||
key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }}
|
key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
# toolchain
|
# toolchain
|
||||||
toolchain: [stable, beta]
|
toolchain: [stable, beta]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install ${{ matrix.toolchain }}
|
- name: Install ${{ matrix.toolchain }}
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# name: ubuntu / stable / minimal-versions
|
# name: ubuntu / stable / minimal-versions
|
||||||
# steps:
|
# steps:
|
||||||
# - uses: actions/checkout@v4
|
# - uses: actions/checkout@v5
|
||||||
|
|
||||||
# - name: Install Rust stable
|
# - name: Install Rust stable
|
||||||
# uses: dtolnay/rust-toolchain@stable
|
# uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
# - run: vcpkg install openssl:x64-windows-static-md
|
# - run: vcpkg install openssl:x64-windows-static-md
|
||||||
# if: runner.os == 'Windows'
|
# if: runner.os == 'Windows'
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Rust stable
|
- name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -151,7 +151,7 @@ jobs:
|
|||||||
name: ubuntu / stable / coverage
|
name: ubuntu / stable / coverage
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Rust stable
|
- name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -176,7 +176,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
if: env.ACT != 'true'
|
if: env.ACT != 'true'
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v7
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ act -W .github/workflows/release.yml --input_type bump=minor
|
|||||||
|
|
||||||
## Authorship Policy
|
## Authorship Policy
|
||||||
|
|
||||||
All code in this repository is written and reviewed by humans. AI-generated code (e.g., Copilot, ChatGPT,
|
All code in this repository is written and reviewed by humans, or AI-generated where explicitly disclosed. AI-generated code (e.g., Copilot, ChatGPT,
|
||||||
Claude, etc.) is not permitted unless explicitly disclosed and approved.
|
Claude, etc.) is permitted only when explicitly disclosed and approved. For example: disclosing the use of AI-generated code in a PR description.
|
||||||
|
|
||||||
Submissions must certify that the contributor understands and can maintain the code they submit.
|
Submissions must certify that the contributor understands and can maintain the code they submit.
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built
|
|||||||
- [ ]  [Bazarr](https://www.bazarr.media/)
|
- [ ]  [Bazarr](https://www.bazarr.media/)
|
||||||
- [ ]  [Tautulli](https://tautulli.com/)
|
- [ ]  [Tautulli](https://tautulli.com/)
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The official Readarr repository has been archived, but Managarr's Readarr support is compatible with any Readarr forks
|
||||||
|
> that maintain compatibility with the official Readarr v1 API. See [Tested Readarr Forks](#tested-readarr-forks) below
|
||||||
|
> for forks that have been explicitly tested with Managarr, and any oddities in configuring them to work with Managarr.
|
||||||
|
|
||||||
## Try Out the Demo
|
## Try Out the Demo
|
||||||
To try out Managarr before linking it to your HTPC, you can use the purpose built [managarr-demo](https://github.com/Dark-Alex-17/managarr-demo) repository.
|
To try out Managarr before linking it to your HTPC, you can use the purpose built [managarr-demo](https://github.com/Dark-Alex-17/managarr-demo) repository.
|
||||||
Simply run the following command to start a demo:
|
Simply run the following command to start a demo:
|
||||||
@@ -134,8 +139,8 @@ Key:
|
|||||||
|
|
||||||
| Symbol | Status |
|
| Symbol | Status |
|
||||||
|--------|-----------|
|
|--------|-----------|
|
||||||
| ✅ | Supported |
|
| ✅ | Supported |
|
||||||
| ❌ | Missing |
|
| ❌ | Missing |
|
||||||
| 🕒 | Planned |
|
| 🕒 | Planned |
|
||||||
| 🚫 | Won't Add |
|
| 🚫 | Won't Add |
|
||||||
|
|
||||||
@@ -143,66 +148,82 @@ Key:
|
|||||||
|
|
||||||
| TUI | CLI | Feature |
|
| TUI | CLI | Feature |
|
||||||
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
||||||
| ✅ | ✅ | View your library, downloads, collections, and blocklist |
|
| ✅ | ✅ | View your library, downloads, collections, and blocklist |
|
||||||
| ✅ | ✅ | View details of a specific movie including description, history, downloaded file info, or the credits |
|
| ✅ | ✅ | View details of a specific movie including description, history, downloaded file info, or the credits |
|
||||||
| ✅ | ✅ | View details of any collection and the movies in them |
|
| ✅ | ✅ | View details of any collection and the movies in them |
|
||||||
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
| ✅ | ✅ | Search your library or collections |
|
| ✅ | ✅ | Search your library or collections |
|
||||||
| ✅ | ✅ | Add movies to your library |
|
| ✅ | ✅ | Add movies to your library |
|
||||||
| ✅ | ✅ | Delete movies, downloads, and indexers |
|
| ✅ | ✅ | Delete movies, downloads, and indexers |
|
||||||
| ✅ | ✅ | Trigger automatic searches for movies |
|
| ✅ | ✅ | Trigger automatic searches for movies |
|
||||||
| ✅ | ✅ | Trigger refresh and disk scan for movies, downloads, and collections |
|
| ✅ | ✅ | Trigger refresh and disk scan for movies, downloads, and collections |
|
||||||
| ✅ | ✅ | Manually search for movies |
|
| ✅ | ✅ | Manually search for movies |
|
||||||
| ✅ | ✅ | Edit your movies, collections, and indexers |
|
| ✅ | ✅ | Edit your movies, collections, and indexers |
|
||||||
| ✅ | ✅ | Manage your tags |
|
| ✅ | ✅ | Manage your tags |
|
||||||
| ✅ | ✅ | Manage your root folders |
|
| ✅ | ✅ | Manage your root folders |
|
||||||
| ✅ | ✅ | Manage your blocklist |
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
### Sonarr
|
### Sonarr
|
||||||
|
|
||||||
| TUI | CLI | Feature |
|
| TUI | CLI | Feature |
|
||||||
|-----|-----|--------------------------------------------------------------------------------------------------------------------|
|
|-----|-----|--------------------------------------------------------------------------------------------------------------------|
|
||||||
| ✅ | ✅ | View your library, downloads, blocklist, episodes |
|
| ✅ | ✅ | View your library, downloads, blocklist, episodes |
|
||||||
| ✅ | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
|
| ✅ | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
|
||||||
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
| ✅ | ✅ | Search your library |
|
| ✅ | ✅ | Search your library |
|
||||||
| ✅ | ✅ | Add series to your library |
|
| ✅ | ✅ | Add series to your library |
|
||||||
| ✅ | ✅ | Delete series, downloads, indexers, root folders, and episode files |
|
| ✅ | ✅ | Delete series, downloads, indexers, root folders, and episode files |
|
||||||
| ✅ | ✅ | Trigger automatic searches for series, seasons, or episodes |
|
| ✅ | ✅ | Trigger automatic searches for series, seasons, or episodes |
|
||||||
| ✅ | ✅ | Trigger refresh and disk scan for series and downloads |
|
| ✅ | ✅ | Trigger refresh and disk scan for series and downloads |
|
||||||
| ✅ | ✅ | Manually search for series, seasons, or episodes |
|
| ✅ | ✅ | Manually search for series, seasons, or episodes |
|
||||||
| ✅ | ✅ | Edit your series and indexers |
|
| ✅ | ✅ | Edit your series and indexers |
|
||||||
| ✅ | ✅ | Manage your tags |
|
| ✅ | ✅ | Manage your tags |
|
||||||
| ✅ | ✅ | Manage your root folders |
|
| ✅ | ✅ | Manage your root folders |
|
||||||
| ✅ | ✅ | Manage your blocklist |
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
### Lidarr
|
### Lidarr
|
||||||
|
|
||||||
| TUI | CLI | Feature |
|
| TUI | CLI | Feature |
|
||||||
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
||||||
| ✅ | ✅ | View your library, downloads, blocklist, tracks |
|
| ✅ | ✅ | View your library, downloads, blocklist, tracks |
|
||||||
| ✅ | ✅ | View details of a specific artists, albums, or tracks including description, history, downloaded file info |
|
| ✅ | ✅ | View details of a specific artists, albums, or tracks including description, history, downloaded file info |
|
||||||
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
| ✅ | ✅ | Search your library |
|
| ✅ | ✅ | Search your library |
|
||||||
| ✅ | ✅ | Add artists to your library |
|
| ✅ | ✅ | Add artists to your library |
|
||||||
| ✅ | ✅ | Delete artists, downloads, indexers, root folders, and track files |
|
| ✅ | ✅ | Delete artists, downloads, indexers, root folders, and track files |
|
||||||
| ✅ | ✅ | Trigger automatic searches for artists or albums |
|
| ✅ | ✅ | Trigger automatic searches for artists or albums |
|
||||||
| ✅ | ✅ | Trigger refresh and disk scan for artists and downloads |
|
| ✅ | ✅ | Trigger refresh and disk scan for artists and downloads |
|
||||||
| ✅ | ✅ | Manually search for full artist discographies or albums |
|
| ✅ | ✅ | Manually search for full artist discographies or albums |
|
||||||
| ✅ | ✅ | Edit your artists and indexers |
|
| ✅ | ✅ | Edit your artists and indexers |
|
||||||
| ✅ | ✅ | Manage your tags |
|
| ✅ | ✅ | Manage your tags |
|
||||||
| ✅ | ✅ | Manage your root folders |
|
| ✅ | ✅ | Manage your root folders |
|
||||||
| ✅ | ✅ | Manage your blocklist |
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
### Readarr
|
### Readarr
|
||||||
|
|
||||||
- [ ] Support for Readarr
|
| TUI | CLI | Feature |
|
||||||
|
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ✅ | ✅ | View your library, downloads, blocklist, editions |
|
||||||
|
| ✅ | ✅ | View details of a specific author, book, or edition including description, history, downloaded file info |
|
||||||
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
|
| ✅ | ✅ | Search your library |
|
||||||
|
| ✅ | ✅ | Add authors to your library |
|
||||||
|
| ✅ | ✅ | Delete authors, books, downloads, indexers, root folders, and book files |
|
||||||
|
| ✅ | ✅ | Trigger automatic searches for authors or books |
|
||||||
|
| ✅ | ✅ | Trigger refresh and disk scan for authors and downloads |
|
||||||
|
| ✅ | ✅ | Manually search for author releases or books |
|
||||||
|
| ✅ | ✅ | Edit your authors and indexers |
|
||||||
|
| ✅ | ✅ | Manage your tags |
|
||||||
|
| ✅ | ✅ | Manage your root folders |
|
||||||
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
### Whisparr
|
### Whisparr
|
||||||
|
|
||||||
@@ -245,7 +266,7 @@ To see all available commands, simply run `managarr --help`:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ managarr --help
|
$ managarr --help
|
||||||
managarr 0.7.0
|
managarr 0.8.0
|
||||||
Alex Clarke <alex.j.tusa@gmail.com>
|
Alex Clarke <alex.j.tusa@gmail.com>
|
||||||
|
|
||||||
A TUI and CLI to manage your Servarrs
|
A TUI and CLI to manage your Servarrs
|
||||||
@@ -443,6 +464,11 @@ lidarr:
|
|||||||
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
|
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
|
||||||
traefik-auth-bypass-key: someBypassKey1234567890
|
traefik-auth-bypass-key: someBypassKey1234567890
|
||||||
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
|
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
|
||||||
|
|
||||||
|
readarr:
|
||||||
|
- name: Chaptarr (Ebooks) # Example of how to use the Readarr support with a Readarr fork like Chaptarr
|
||||||
|
uri: http://192.168.0.87:8789/readarr/gr/ebook
|
||||||
|
api_token: someApiToken1234567890
|
||||||
```
|
```
|
||||||
|
|
||||||
### SSL Configuration
|
### SSL Configuration
|
||||||
@@ -552,6 +578,46 @@ Managarr supports using environment variables on startup so you don't have to al
|
|||||||
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
||||||
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|
||||||
|
|
||||||
|
## Tested Readarr Forks
|
||||||
|
|
||||||
|
The following Readarr forks have been explicitly tested with Managarr:
|
||||||
|
|
||||||
|
* [Bookshelf](https://github.com/pennydreadful/bookshelf)
|
||||||
|
* [Readarr-Resurrected](https://github.com/ricetim/readarr-rresurrected)
|
||||||
|
* [Librarr](https://github.com/Rorqualx/Librarr)
|
||||||
|
* [Chaptarr](https://github.com/Chaptarr/Chaptarr)
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Chaptarr houses a dedicated Readarr facade middleware that activates on a path prefix, meaning a bare config doesn't
|
||||||
|
> work. Instead, users need to configure both Audiobooks and Ebooks separately, and based on their desired metadata
|
||||||
|
> fetcher (i.e. Goodreads and Hardcover IDs). Because of this, Chaptarr-based configurations must use the `uri` key
|
||||||
|
> rather than the `host` and `port` keys. The below configuration file demonstrates all the supported variations for
|
||||||
|
> connecting Managarr to Chaptarr.
|
||||||
|
|
||||||
|
### Chaptarr Example Configuration
|
||||||
|
```yaml
|
||||||
|
readarr:
|
||||||
|
# Because Chaptarr supports both eBooks and audiobooks, the same instance must be provisioned twice if you're using
|
||||||
|
# both so Managarr can differentiate between the two. Thus, for Chaptarr, you need an eBook server configured and an
|
||||||
|
# audiobook server configured like below.
|
||||||
|
|
||||||
|
- name: eBooks (Goodreads) # Readarr originally used Goodreads as its metadata provider
|
||||||
|
uri: http://192.168.0.87:8789/readarr/gr/ebook # Note the `gr` in the path prefix indicating "GoodReads"
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
|
- name: Chaptarr Audiobooks (Goodreads)
|
||||||
|
uri: http://192.168.0.105:8789/readarr/gr/audiobook
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
|
- name: Chaptarr eBooks (Hardcover)
|
||||||
|
uri: http://192.168.0.105:8789/readarr/hc/ebook # Note the `hc` in the path prefix indicating "HardCover"
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
|
- name: Chaptarr Audiobooks (Hardcover)
|
||||||
|
uri: http://192.168.0.105:8789/readarr/hc/audiobook
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
```
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### Radarr
|
### Radarr
|
||||||
@@ -574,6 +640,13 @@ Managarr supports using environment variables on startup so you don't have to al
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
### Readarr
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
### General
|
### General
|
||||||

|

|
||||||

|

|
||||||
|
|||||||
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 186 KiB |
@@ -16,6 +16,7 @@ mod tests {
|
|||||||
use crate::models::{HorizontallyScrollableText, TabRoute};
|
use crate::models::{HorizontallyScrollableText, TabRoute};
|
||||||
use crate::network::NetworkEvent;
|
use crate::network::NetworkEvent;
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
|
use crate::network::sonarr_network::SonarrEvent;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -165,6 +166,36 @@ mod tests {
|
|||||||
assert!(app.is_routing);
|
assert!(app.is_routing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_named_tab_uses_selected_servarr_route_and_network_events() {
|
||||||
|
let (network_tx, mut network_rx) = mpsc::channel(500);
|
||||||
|
let mut app = App::new(
|
||||||
|
network_tx,
|
||||||
|
AppConfig {
|
||||||
|
radarr: Some(vec![ServarrConfig {
|
||||||
|
name: Some("Movies".to_owned()),
|
||||||
|
..ServarrConfig::default()
|
||||||
|
}]),
|
||||||
|
sonarr: Some(vec![ServarrConfig {
|
||||||
|
name: Some("Shows".to_owned()),
|
||||||
|
..ServarrConfig::default()
|
||||||
|
}]),
|
||||||
|
..AppConfig::default()
|
||||||
|
},
|
||||||
|
CancellationToken::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(app.server_tabs.select_tab_by_title("Shows"));
|
||||||
|
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
|
||||||
|
|
||||||
|
app.on_tick().await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
network_rx.recv().await.unwrap(),
|
||||||
|
SonarrEvent::GetQualityProfiles.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reset_cancellation_token() {
|
fn test_reset_cancellation_token() {
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
|
|||||||
@@ -263,14 +263,11 @@ impl App<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_route(&self) -> Route {
|
pub fn get_current_route(&self) -> Route {
|
||||||
*self.navigation_stack.last().unwrap_or(
|
self
|
||||||
&self
|
.navigation_stack
|
||||||
.server_tabs
|
.last()
|
||||||
.tabs
|
.copied()
|
||||||
.first()
|
.unwrap_or_else(|| self.server_tabs.get_active_route())
|
||||||
.expect("At least one server tab must exist")
|
|
||||||
.route,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ mod tests {
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::cli::lidarr::LidarrCommand;
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
|
use crate::cli::readarr::ReadarrCommand;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::readarr_network::ReadarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
Cli,
|
Cli,
|
||||||
app::App,
|
app::App,
|
||||||
@@ -24,6 +26,10 @@ mod tests {
|
|||||||
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
||||||
RadarrSerdeable,
|
RadarrSerdeable,
|
||||||
},
|
},
|
||||||
|
readarr_models::{
|
||||||
|
BlocklistItem as ReadarrBlocklistItem, BlocklistResponse as ReadarrBlocklistResponse,
|
||||||
|
ReadarrSerdeable,
|
||||||
|
},
|
||||||
sonarr_models::{
|
sonarr_models::{
|
||||||
BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
|
BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
|
||||||
SonarrSerdeable,
|
SonarrSerdeable,
|
||||||
@@ -68,6 +74,13 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_readarr_subcommand_delegates_to_readarr() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "readarr", "list", "authors"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_completions_requires_argument() {
|
fn test_completions_requires_argument() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "completions"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "completions"]);
|
||||||
@@ -218,4 +231,35 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cli_handler_delegates_readarr_commands_to_the_readarr_cli_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(ReadarrEvent::GetBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::BlocklistResponse(
|
||||||
|
ReadarrBlocklistResponse {
|
||||||
|
records: vec![ReadarrBlocklistItem::default()],
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(ReadarrEvent::ClearBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let clear_blocklist_command = ReadarrCommand::ClearBlocklist.into();
|
||||||
|
|
||||||
|
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -171,6 +171,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrAddCommand> for ReadarrAddCommandH
|
|||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
metadata_profile_id,
|
metadata_profile_id,
|
||||||
tags,
|
tags,
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor,
|
monitor,
|
||||||
|
|||||||
@@ -447,6 +447,7 @@ mod tests {
|
|||||||
quality_profile_id: 1,
|
quality_profile_id: 1,
|
||||||
metadata_profile_id: 1,
|
metadata_profile_id: 1,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor: MonitorType::All,
|
monitor: MonitorType::All,
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
readarr::{ReadarrCommand, list_command_handler::ReadarrListCommand},
|
||||||
|
};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_readarr_command_from() {
|
||||||
|
let command = ReadarrCommand::List(ReadarrListCommand::Authors);
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Readarr(command));
|
||||||
|
}
|
||||||
|
|
||||||
mod cli {
|
mod cli {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use clap::error::ErrorKind;
|
use clap::error::ErrorKind;
|
||||||
@@ -243,20 +258,246 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::readarr::add_command_handler::ReadarrAddCommand;
|
||||||
|
use crate::cli::readarr::edit_command_handler::ReadarrEditCommand;
|
||||||
|
use crate::cli::readarr::get_command_handler::ReadarrGetCommand;
|
||||||
|
use crate::cli::readarr::refresh_command_handler::ReadarrRefreshCommand;
|
||||||
|
use crate::cli::readarr::trigger_automatic_search_command_handler::ReadarrTriggerAutomaticSearchCommand;
|
||||||
use crate::models::readarr_models::{
|
use crate::models::readarr_models::{
|
||||||
BlocklistItem, BlocklistResponse, ReadarrSerdeable, ReadarrTaskName,
|
Author, BlocklistItem, BlocklistResponse, DeleteParams, ReadarrSerdeable, ReadarrTaskName,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::ReleaseDownloadBody;
|
use crate::models::servarr_models::{IndexerSettings, ReleaseDownloadBody};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
readarr::{ReadarrCliHandler, ReadarrCommand},
|
readarr::{
|
||||||
|
ReadarrCliHandler, ReadarrCommand, delete_command_handler::ReadarrDeleteCommand,
|
||||||
|
list_command_handler::ReadarrListCommand,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
models::Serdeable,
|
models::Serdeable,
|
||||||
network::{MockNetworkTrait, NetworkEvent, readarr_network::ReadarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, readarr_network::ReadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_add_commands_to_the_add_command_handler() {
|
||||||
|
let expected_tag_name = "test".to_owned();
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
ReadarrEvent::AddTag(expected_tag_name.clone()).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let add_tag_command = ReadarrCommand::Add(ReadarrAddCommand::Tag {
|
||||||
|
name: expected_tag_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = ReadarrCliHandler::with(&app_arc, add_tag_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_get_commands_to_the_get_command_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(ReadarrEvent::GetStatus.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_system_status_command = ReadarrCommand::Get(ReadarrGetCommand::SystemStatus);
|
||||||
|
|
||||||
|
let result = ReadarrCliHandler::with(&app_arc, get_system_status_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() {
|
||||||
|
let expected_delete_author_params = DeleteParams {
|
||||||
|
id: 7,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: false,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
ReadarrEvent::DeleteAuthor(expected_delete_author_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_author_command = ReadarrCommand::Delete(ReadarrDeleteCommand::Author {
|
||||||
|
author_id: 7,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = ReadarrCliHandler::with(&app_arc, delete_author_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_edit_commands_to_the_edit_command_handler() {
|
||||||
|
let expected_edit_all_indexer_settings = IndexerSettings {
|
||||||
|
id: 1,
|
||||||
|
maximum_size: 26500,
|
||||||
|
minimum_age: 17,
|
||||||
|
retention: 43,
|
||||||
|
rss_sync_interval: 35,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
ReadarrEvent::GetAllIndexerSettings.into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::IndexerSettings(
|
||||||
|
IndexerSettings {
|
||||||
|
id: 9,
|
||||||
|
maximum_size: 31200,
|
||||||
|
minimum_age: 22,
|
||||||
|
retention: 58,
|
||||||
|
rss_sync_interval: 90,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
ReadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_all_indexer_settings_command =
|
||||||
|
ReadarrCommand::Edit(ReadarrEditCommand::AllIndexerSettings {
|
||||||
|
maximum_size: Some(26500),
|
||||||
|
minimum_age: Some(17),
|
||||||
|
retention: Some(43),
|
||||||
|
rss_sync_interval: Some(35),
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = ReadarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
edit_all_indexer_settings_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_list_commands_to_the_list_command_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(ReadarrEvent::ListAuthors.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Authors(vec![
|
||||||
|
Author::default(),
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_authors_command = ReadarrCommand::List(ReadarrListCommand::Authors);
|
||||||
|
|
||||||
|
let result = ReadarrCliHandler::with(&app_arc, list_authors_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_refresh_commands_to_the_refresh_command_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(ReadarrEvent::UpdateAllAuthors.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let refresh_all_authors_command = ReadarrCommand::Refresh(ReadarrRefreshCommand::AllAuthors);
|
||||||
|
|
||||||
|
let result =
|
||||||
|
ReadarrCliHandler::with(&app_arc, refresh_all_authors_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_readarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler()
|
||||||
|
{
|
||||||
|
let expected_author_id = 7;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
ReadarrEvent::TriggerAutomaticAuthorSearch(expected_author_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Readarr(ReadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let trigger_automatic_search_command =
|
||||||
|
ReadarrCommand::TriggerAutomaticSearch(ReadarrTriggerAutomaticSearchCommand::Author {
|
||||||
|
author_id: expected_author_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = ReadarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_mark_history_item_as_failed_command() {
|
async fn test_mark_history_item_as_failed_command() {
|
||||||
let expected_history_item_id = 1234i64;
|
let expected_history_item_id = 1234i64;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ mod tests {
|
|||||||
#[case(Key::Char('q'), "q")]
|
#[case(Key::Char('q'), "q")]
|
||||||
#[case(Key::Ctrl('q'), "ctrl-q")]
|
#[case(Key::Ctrl('q'), "ctrl-q")]
|
||||||
fn test_key_formatter(#[case] key: Key, #[case] expected_str: &str) {
|
fn test_key_formatter(#[case] key: Key, #[case] expected_str: &str) {
|
||||||
assert_str_eq!(format!("{key}"), format!("{expected_str}"));
|
assert_str_eq!(format!("{key}"), expected_str.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -768,7 +768,7 @@ mod tests {
|
|||||||
mod test_handle_submit {
|
mod test_handle_submit {
|
||||||
use crate::assert_navigation_popped;
|
use crate::assert_navigation_popped;
|
||||||
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::{
|
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::{
|
||||||
add_movie_body, add_movie_search_result, collection_movie,
|
add_movie_search_result, collection_movie,
|
||||||
};
|
};
|
||||||
use crate::models::BlockSelectionState;
|
use crate::models::BlockSelectionState;
|
||||||
use crate::models::radarr_models::Movie;
|
use crate::models::radarr_models::Movie;
|
||||||
@@ -1336,7 +1336,7 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
assert_navigation_popped,
|
assert_navigation_popped,
|
||||||
handlers::radarr_handlers::radarr_handler_test_utils::utils::{
|
handlers::radarr_handlers::radarr_handler_test_utils::utils::{
|
||||||
add_movie_body, add_movie_search_result, collection_movie,
|
add_movie_search_result, collection_movie,
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
BlockSelectionState,
|
BlockSelectionState,
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ impl AddAuthorHandler<'_, '_> {
|
|||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
metadata_profile_id,
|
metadata_profile_id,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: Some(tags),
|
tag_input_string: Some(tags),
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor,
|
monitor,
|
||||||
|
|||||||
@@ -1237,6 +1237,7 @@ mod tests {
|
|||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
metadata_profile_id: 4444,
|
metadata_profile_id: 4444,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor: MonitorType::Future,
|
monitor: MonitorType::Future,
|
||||||
@@ -1628,6 +1629,7 @@ mod tests {
|
|||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
metadata_profile_id: 4444,
|
metadata_profile_id: 4444,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor: MonitorType::Future,
|
monitor: MonitorType::Future,
|
||||||
@@ -1795,6 +1797,7 @@ mod tests {
|
|||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
metadata_profile_id: 4444,
|
metadata_profile_id: 4444,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor: MonitorType::Future,
|
monitor: MonitorType::Future,
|
||||||
|
|||||||
@@ -171,13 +171,40 @@ async fn main() -> Result<()> {
|
|||||||
cancellation_token.clone(),
|
cancellation_token.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
if args.command.is_none()
|
||||||
|
&& let Some(name) = args.global.servarr_name.as_deref()
|
||||||
|
{
|
||||||
|
let mut app = app.lock().await;
|
||||||
|
anyhow::ensure!(
|
||||||
|
app.server_tabs.select_tab_by_title(name.trim()),
|
||||||
|
"A Servarr titled '{}' was not found in your configuration file",
|
||||||
|
name.trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(command) => match command {
|
Some(command) => match command {
|
||||||
Command::Radarr(_) | Command::Sonarr(_) | Command::Lidarr(_) | Command::Readarr(_) => {
|
Command::Radarr(_) | Command::Sonarr(_) | Command::Lidarr(_) | Command::Readarr(_) => {
|
||||||
if spinner_disabled {
|
if spinner_disabled {
|
||||||
start_cli_no_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
start_cli_no_spinner(
|
||||||
|
config,
|
||||||
|
reqwest_client,
|
||||||
|
cancellation_token,
|
||||||
|
app,
|
||||||
|
command,
|
||||||
|
args.global.servarr_name.as_deref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
} else {
|
} else {
|
||||||
start_cli_with_spinner(config, reqwest_client, cancellation_token, app, command).await;
|
start_cli_with_spinner(
|
||||||
|
config,
|
||||||
|
reqwest_client,
|
||||||
|
cancellation_token,
|
||||||
|
app,
|
||||||
|
command,
|
||||||
|
args.global.servarr_name.as_deref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Completions { shell } => {
|
Command::Completions { shell } => {
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ pub struct AddAuthorBody {
|
|||||||
pub quality_profile_id: i64,
|
pub quality_profile_id: i64,
|
||||||
pub metadata_profile_id: i64,
|
pub metadata_profile_id: i64,
|
||||||
pub tags: Vec<i64>,
|
pub tags: Vec<i64>,
|
||||||
|
// Readarr-Resurrected persists the add body directly and rejects it when the images list is absent
|
||||||
|
pub images: Vec<Value>,
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub tag_input_string: Option<String>,
|
pub tag_input_string: Option<String>,
|
||||||
pub add_options: AddAuthorOptions,
|
pub add_options: AddAuthorOptions,
|
||||||
@@ -280,6 +282,7 @@ pub struct Book {
|
|||||||
pub ratings: Option<Ratings>,
|
pub ratings: Option<Ratings>,
|
||||||
pub release_date: Option<DateTime<Utc>>,
|
pub release_date: Option<DateTime<Utc>>,
|
||||||
pub statistics: Option<BookStatistics>,
|
pub statistics: Option<BookStatistics>,
|
||||||
|
#[serde(default)]
|
||||||
pub grabbed: bool,
|
pub grabbed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,6 +457,29 @@ pub struct ReadarrRelease {
|
|||||||
pub quality: QualityWrapper,
|
pub quality: QualityWrapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ReadarrReleaseResponse {
|
||||||
|
Releases(Vec<ReadarrRelease>),
|
||||||
|
// Chaptarr wraps the release list in a filter summary envelope, even behind its Readarr facade
|
||||||
|
Envelope { releases: Vec<ReadarrRelease> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ReadarrReleaseResponse {
|
||||||
|
fn default() -> Self {
|
||||||
|
ReadarrReleaseResponse::Releases(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadarrReleaseResponse {
|
||||||
|
pub fn into_releases(self) -> Vec<ReadarrRelease> {
|
||||||
|
match self {
|
||||||
|
ReadarrReleaseResponse::Releases(releases)
|
||||||
|
| ReadarrReleaseResponse::Envelope { releases } => releases,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BlocklistItem {
|
pub struct BlocklistItem {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ mod tests {
|
|||||||
AddAuthorSearchResult, BlocklistItem, BlocklistResponse, Book, BookFile, DownloadRecord,
|
AddAuthorSearchResult, BlocklistItem, BlocklistResponse, Book, BookFile, DownloadRecord,
|
||||||
DownloadsResponse, Edition, MediaInfo, MonitorType, NewItemMonitorType,
|
DownloadsResponse, Edition, MediaInfo, MonitorType, NewItemMonitorType,
|
||||||
ReadarrHistoryEventType, ReadarrHistoryItem, ReadarrHistoryWrapper, ReadarrRelease,
|
ReadarrHistoryEventType, ReadarrHistoryItem, ReadarrHistoryWrapper, ReadarrRelease,
|
||||||
ReadarrTask, ReadarrTaskName,
|
ReadarrReleaseResponse, ReadarrTask, ReadarrTaskName,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
DiskSpace, DownloadStatus, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log,
|
DiskSpace, DownloadStatus, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log,
|
||||||
@@ -497,6 +497,29 @@ mod tests {
|
|||||||
assert_eq!(readarr_serdeable, ReadarrSerdeable::Releases(releases));
|
assert_eq!(readarr_serdeable, ReadarrSerdeable::Releases(releases));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_readarr_release_response_default_is_an_empty_release_list() {
|
||||||
|
assert_eq!(
|
||||||
|
ReadarrReleaseResponse::default(),
|
||||||
|
ReadarrReleaseResponse::Releases(Vec::new())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_readarr_release_response_into_releases() {
|
||||||
|
let releases = vec![ReadarrRelease {
|
||||||
|
guid: "test".to_owned(),
|
||||||
|
..ReadarrRelease::default()
|
||||||
|
}];
|
||||||
|
let list = ReadarrReleaseResponse::Releases(releases.clone());
|
||||||
|
let envelope = ReadarrReleaseResponse::Envelope {
|
||||||
|
releases: releases.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(list.into_releases(), releases);
|
||||||
|
assert_eq!(envelope.into_releases(), releases);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_readarr_serdeable_from_security_config() {
|
fn test_readarr_serdeable_from_security_config() {
|
||||||
let security_config = SecurityConfig {
|
let security_config = SecurityConfig {
|
||||||
@@ -939,6 +962,22 @@ mod tests {
|
|||||||
assert_none!(&book.statistics);
|
assert_none!(&book.statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_book_deserialization_defaults_grabbed_when_absent() {
|
||||||
|
let book_json = json!({
|
||||||
|
"id": 1,
|
||||||
|
"title": "The Name of the Wind",
|
||||||
|
"authorId": 3,
|
||||||
|
"foreignBookId": "test-foreign-book-id",
|
||||||
|
"monitored": true
|
||||||
|
});
|
||||||
|
|
||||||
|
let book: Book = serde_json::from_value(book_json).unwrap();
|
||||||
|
|
||||||
|
assert!(!book.grabbed);
|
||||||
|
assert!(book.monitored);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edition_deserialization() {
|
fn test_edition_deserialization() {
|
||||||
let edition_json = json!({
|
let edition_json = json!({
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ impl ReadarrData<'_> {
|
|||||||
statistics: Some(BookStatistics {
|
statistics: Some(BookStatistics {
|
||||||
book_file_count: 3,
|
book_file_count: 3,
|
||||||
total_book_count: 7,
|
total_book_count: 7,
|
||||||
size_on_disk: 2469606195,
|
size_on_disk: 2457600,
|
||||||
percent_of_books: 42.86,
|
percent_of_books: 42.86,
|
||||||
}),
|
}),
|
||||||
..Book::default()
|
..Book::default()
|
||||||
@@ -538,7 +538,7 @@ impl ReadarrData<'_> {
|
|||||||
author_id: 1,
|
author_id: 1,
|
||||||
book_id: 1,
|
book_id: 1,
|
||||||
path: "/nfs/books/Test Author/Test Book.epub".to_owned(),
|
path: "/nfs/books/Test Author/Test Book.epub".to_owned(),
|
||||||
size: 3543348019,
|
size: 3355443,
|
||||||
date_added: date,
|
date_added: date,
|
||||||
quality: quality.clone(),
|
quality: quality.clone(),
|
||||||
media_info: Some(MediaInfo {
|
media_info: Some(MediaInfo {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ mod tests {
|
|||||||
"trackFileCount": 8,
|
"trackFileCount": 8,
|
||||||
"trackCount": 10,
|
"trackCount": 10,
|
||||||
"totalTrackCount": 10,
|
"totalTrackCount": 10,
|
||||||
"sizeOnDisk": 1024,
|
"sizeOnDisk": 317162578,
|
||||||
"percentOfTracks": 99.9
|
"percentOfTracks": 99.9
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ pub mod test_utils {
|
|||||||
"trackFileCount": 8,
|
"trackFileCount": 8,
|
||||||
"trackCount": 10,
|
"trackCount": 10,
|
||||||
"totalTrackCount": 10,
|
"totalTrackCount": 10,
|
||||||
"sizeOnDisk": 1024,
|
"sizeOnDisk": 317162578,
|
||||||
"percentOfTracks": 99.9
|
"percentOfTracks": 99.9
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
@@ -250,7 +250,7 @@ pub mod test_utils {
|
|||||||
track_file_count: 8,
|
track_file_count: 8,
|
||||||
track_count: 10,
|
track_count: 10,
|
||||||
total_track_count: 10,
|
total_track_count: 10,
|
||||||
size_on_disk: 1024,
|
size_on_disk: 317162578,
|
||||||
percent_of_tracks: 99.9,
|
percent_of_tracks: 99.9,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::models::stateful_table::StatefulTable;
|
|||||||
use crate::models::{Route, ScrollableText};
|
use crate::models::{Route, ScrollableText};
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
use crate::network::{Network, RequestMethod};
|
use crate::network::{Network, RequestMethod};
|
||||||
use crate::utils::{convert_runtime, convert_to_gb};
|
use crate::utils::{convert_runtime, format_size};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indoc::formatdoc;
|
use indoc::formatdoc;
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
@@ -319,7 +319,7 @@ impl Network<'_, '_> {
|
|||||||
..
|
..
|
||||||
} = movie_response;
|
} = movie_response;
|
||||||
let (hours, minutes) = convert_runtime(runtime);
|
let (hours, minutes) = convert_runtime(runtime);
|
||||||
let size = convert_to_gb(size_on_disk);
|
let size = format_size(size_on_disk, 2);
|
||||||
let studio = studio.clone().unwrap_or_default();
|
let studio = studio.clone().unwrap_or_default();
|
||||||
let quality_profile = app
|
let quality_profile = app
|
||||||
.data
|
.data
|
||||||
@@ -374,7 +374,7 @@ impl Network<'_, '_> {
|
|||||||
IMDB: {imdb_rating}
|
IMDB: {imdb_rating}
|
||||||
Rotten Tomatoes: {rotten_tomatoes_rating}
|
Rotten Tomatoes: {rotten_tomatoes_rating}
|
||||||
Quality Profile: {quality_profile}
|
Quality Profile: {quality_profile}
|
||||||
Size: {size:.2} GB
|
Size: {size}
|
||||||
Path: {path}
|
Path: {path}
|
||||||
Studio: {studio}
|
Studio: {studio}
|
||||||
Genres: {}",
|
Genres: {}",
|
||||||
@@ -393,7 +393,7 @@ impl Network<'_, '_> {
|
|||||||
movie_details_modal.file_details = formatdoc!(
|
movie_details_modal.file_details = formatdoc!(
|
||||||
"Relative Path: {}
|
"Relative Path: {}
|
||||||
Absolute Path: {}
|
Absolute Path: {}
|
||||||
Size: {size:.2} GB
|
Size: {size}
|
||||||
Date Added: {}",
|
Date Added: {}",
|
||||||
file.relative_path,
|
file.relative_path,
|
||||||
file.path,
|
file.path,
|
||||||
|
|||||||
@@ -759,7 +759,7 @@ mod tests {
|
|||||||
IMDB:
|
IMDB:
|
||||||
Rotten Tomatoes:
|
Rotten Tomatoes:
|
||||||
Quality Profile: HD - 1080p
|
Quality Profile: HD - 1080p
|
||||||
Size: 0.00 GB
|
Size: 0.00 MB
|
||||||
Path: /nfs/movies
|
Path: /nfs/movies
|
||||||
Studio: 21st Century Alex
|
Studio: 21st Century Alex
|
||||||
Genres: cool, family, fun"
|
Genres: cool, family, fun"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use serde_json::{Value, json};
|
|||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::readarr_models::{
|
use crate::models::readarr_models::{
|
||||||
AddAuthorBody, AddAuthorSearchResult, Author, DeleteParams, EditAuthorParams, ReadarrCommandBody,
|
AddAuthorBody, AddAuthorSearchResult, Author, DeleteParams, EditAuthorParams, ReadarrCommandBody,
|
||||||
ReadarrHistoryItem, ReadarrRelease,
|
ReadarrHistoryItem, ReadarrRelease, ReadarrReleaseResponse,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||||
use crate::models::stateful_table::StatefulTable;
|
use crate::models::stateful_table::StatefulTable;
|
||||||
@@ -235,10 +235,15 @@ impl Network<'_, '_> {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
self
|
self
|
||||||
.handle_request::<(), Vec<ReadarrRelease>>(request_props, |release_vec, mut app| {
|
.handle_request::<(), ReadarrReleaseResponse>(request_props, |response, mut app| {
|
||||||
app.data.readarr_data.author_releases.set_items(release_vec);
|
app
|
||||||
|
.data
|
||||||
|
.readarr_data
|
||||||
|
.author_releases
|
||||||
|
.set_items(response.into_releases());
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.map(ReadarrReleaseResponse::into_releases)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::network::readarr_network) async fn get_readarr_author_history(
|
pub(in crate::network::readarr_network) async fn get_readarr_author_history(
|
||||||
|
|||||||
@@ -133,6 +133,48 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_author_releases_event_unwraps_the_release_envelope() {
|
||||||
|
let expected_releases = vec![torrent_release()];
|
||||||
|
let releases_json = json!({
|
||||||
|
"releases": [serde_json::from_str::<Value>(RELEASE_JSON).unwrap()],
|
||||||
|
"hiddenReleases": [],
|
||||||
|
"filterSummary": { "totalResults": 1, "displayedCount": 1 },
|
||||||
|
"siblingToggleEnabled": false
|
||||||
|
});
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(releases_json)
|
||||||
|
.query("authorId=3")
|
||||||
|
.build_for(ReadarrEvent::GetAuthorReleases(3))
|
||||||
|
.await;
|
||||||
|
{
|
||||||
|
let mut app = app.lock().await;
|
||||||
|
app.server_tabs.set_index(3);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.readarr_data
|
||||||
|
.author_releases
|
||||||
|
.set_items(vec![stale_release()]);
|
||||||
|
}
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_readarr_event(ReadarrEvent::GetAuthorReleases(3))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
|
||||||
|
let ReadarrSerdeable::Releases(releases) = result.unwrap() else {
|
||||||
|
panic!("Expected Releases")
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(releases, expected_releases);
|
||||||
|
assert_eq!(
|
||||||
|
app.lock().await.data.readarr_data.author_releases.items,
|
||||||
|
expected_releases
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_author_releases_event_failure() {
|
async fn test_handle_get_author_releases_event_failure() {
|
||||||
let stale_releases = vec![stale_release()];
|
let stale_releases = vec![stale_release()];
|
||||||
@@ -638,6 +680,7 @@ mod tests {
|
|||||||
"qualityProfileId": 1,
|
"qualityProfileId": 1,
|
||||||
"metadataProfileId": 1,
|
"metadataProfileId": 1,
|
||||||
"tags": [1, 2],
|
"tags": [1, 2],
|
||||||
|
"images": [],
|
||||||
"addOptions": {
|
"addOptions": {
|
||||||
"monitor": "all",
|
"monitor": "all",
|
||||||
"monitorNewItems": "all",
|
"monitorNewItems": "all",
|
||||||
@@ -680,6 +723,7 @@ mod tests {
|
|||||||
"qualityProfileId": 1,
|
"qualityProfileId": 1,
|
||||||
"metadataProfileId": 1,
|
"metadataProfileId": 1,
|
||||||
"tags": [1, 2],
|
"tags": [1, 2],
|
||||||
|
"images": [],
|
||||||
"addOptions": {
|
"addOptions": {
|
||||||
"monitor": "all",
|
"monitor": "all",
|
||||||
"monitorNewItems": "all",
|
"monitorNewItems": "all",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use serde_json::{Value, json};
|
|||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::readarr_models::{
|
use crate::models::readarr_models::{
|
||||||
Book, BookFile, DeleteParams, Edition, ReadarrCommandBody, ReadarrHistoryItem, ReadarrRelease,
|
Book, BookFile, DeleteParams, Edition, ReadarrCommandBody, ReadarrHistoryItem, ReadarrRelease,
|
||||||
|
ReadarrReleaseResponse,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
|
||||||
use crate::network::readarr_network::ReadarrEvent;
|
use crate::network::readarr_network::ReadarrEvent;
|
||||||
@@ -170,16 +171,19 @@ impl Network<'_, '_> {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
self
|
self
|
||||||
.handle_request::<(), Vec<ReadarrRelease>>(request_props, |release_vec, mut app| {
|
.handle_request::<(), ReadarrReleaseResponse>(request_props, |response, mut app| {
|
||||||
let book_details_modal = app
|
let book_details_modal = app
|
||||||
.data
|
.data
|
||||||
.readarr_data
|
.readarr_data
|
||||||
.book_details_modal
|
.book_details_modal
|
||||||
.get_or_insert_default();
|
.get_or_insert_default();
|
||||||
|
|
||||||
book_details_modal.book_releases.set_items(release_vec);
|
book_details_modal
|
||||||
|
.book_releases
|
||||||
|
.set_items(response.into_releases());
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.map(ReadarrReleaseResponse::into_releases)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::network::readarr_network) async fn get_books(
|
pub(in crate::network::readarr_network) async fn get_books(
|
||||||
|
|||||||
@@ -532,6 +532,57 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_book_releases_event_unwraps_the_release_envelope() {
|
||||||
|
let expected_releases = vec![torrent_release()];
|
||||||
|
let releases_json = json!({
|
||||||
|
"releases": [serde_json::from_str::<Value>(RELEASE_JSON).unwrap()],
|
||||||
|
"hiddenReleases": [],
|
||||||
|
"filterSummary": { "totalResults": 1, "displayedCount": 1 },
|
||||||
|
"siblingToggleEnabled": false
|
||||||
|
});
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(releases_json)
|
||||||
|
.query("bookId=7")
|
||||||
|
.build_for(ReadarrEvent::GetBookReleases(7))
|
||||||
|
.await;
|
||||||
|
{
|
||||||
|
let mut app = app.lock().await;
|
||||||
|
app.server_tabs.set_index(3);
|
||||||
|
let mut book_details_modal = BookDetailsModal::default();
|
||||||
|
book_details_modal
|
||||||
|
.book_releases
|
||||||
|
.set_items(vec![stale_release()]);
|
||||||
|
app.data.readarr_data.book_details_modal = Some(book_details_modal);
|
||||||
|
}
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_readarr_event(ReadarrEvent::GetBookReleases(7))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
|
||||||
|
let ReadarrSerdeable::Releases(releases) = result.unwrap() else {
|
||||||
|
panic!("Expected Releases")
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(releases, expected_releases);
|
||||||
|
assert_eq!(
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.readarr_data
|
||||||
|
.book_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.book_releases
|
||||||
|
.items,
|
||||||
|
expected_releases
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_book_releases_event_empty_book_details_modal() {
|
async fn test_handle_get_book_releases_event_empty_book_details_modal() {
|
||||||
let expected_releases = vec![torrent_release()];
|
let expected_releases = vec![torrent_release()];
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ pub mod test_utils {
|
|||||||
quality_profile_id: 1,
|
quality_profile_id: 1,
|
||||||
metadata_profile_id: 1,
|
metadata_profile_id: 1,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
|
images: Vec::new(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddAuthorOptions {
|
add_options: AddAuthorOptions {
|
||||||
monitor: MonitorType::All,
|
monitor: MonitorType::All,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::models::sonarr_models::{
|
|||||||
use crate::models::{Route, ScrollableText};
|
use crate::models::{Route, ScrollableText};
|
||||||
use crate::network::sonarr_network::SonarrEvent;
|
use crate::network::sonarr_network::SonarrEvent;
|
||||||
use crate::network::{Network, RequestMethod};
|
use crate::network::{Network, RequestMethod};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indoc::formatdoc;
|
use indoc::formatdoc;
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -222,12 +222,12 @@ impl Network<'_, '_> {
|
|||||||
overview.unwrap_or_default(),
|
overview.unwrap_or_default(),
|
||||||
));
|
));
|
||||||
if let Some(file) = episode_file {
|
if let Some(file) = episode_file {
|
||||||
let size = convert_to_gb(file.size);
|
let size = format_size(file.size, 2);
|
||||||
episode_details_modal.file_details = formatdoc!(
|
episode_details_modal.file_details = formatdoc!(
|
||||||
"
|
"
|
||||||
Relative Path: {}
|
Relative Path: {}
|
||||||
Absolute Path: {}
|
Absolute Path: {}
|
||||||
Size: {size:.2} GB
|
Size: {size}
|
||||||
Language: {}
|
Language: {}
|
||||||
Date Added: {}",
|
Date Added: {}",
|
||||||
file.relative_path,
|
file.relative_path,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
|||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::utils::convert_f64_to_gb;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "downloads_ui_tests.rs"]
|
#[path = "downloads_ui_tests.rs"]
|
||||||
@@ -97,12 +97,12 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
} else {
|
} else {
|
||||||
1f64 - (*sizeleft / *size)
|
1f64 - (*sizeleft / *size)
|
||||||
};
|
};
|
||||||
let file_size: f64 = convert_f64_to_gb(*size);
|
let file_size = format_size(*size as i64, 2);
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(title.to_owned()),
|
Cell::from(title.to_owned()),
|
||||||
Cell::from(format!("{:.0}%", percent * 100.0)),
|
Cell::from(format!("{:.0}%", percent * 100.0)),
|
||||||
Cell::from(format!("{file_size:.2} GB")),
|
Cell::from(file_size),
|
||||||
Cell::from(
|
Cell::from(
|
||||||
output_path
|
output_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::ui::widgets::managarr_table::ManagarrTable;
|
|||||||
use crate::ui::widgets::message::Message;
|
use crate::ui::widgets::message::Message;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use ratatui::layout::{Alignment, Constraint, Rect};
|
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||||
use ratatui::prelude::{Line, Stylize, Text};
|
use ratatui::prelude::{Line, Stylize, Text};
|
||||||
@@ -377,7 +377,7 @@ fn draw_album_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
&& active_lidarr_block != ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt,
|
&& active_lidarr_block != ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt,
|
||||||
app.should_text_scroll,
|
app.should_text_scroll,
|
||||||
);
|
);
|
||||||
let size = convert_to_gb(*size);
|
let size = format_size(*size, 1);
|
||||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||||
let peers = if seeders.is_none() || leechers.is_none() {
|
let peers = if seeders.is_none() || leechers.is_none() {
|
||||||
Text::from("")
|
Text::from("")
|
||||||
@@ -408,7 +408,7 @@ fn draw_album_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(rejected_str),
|
Cell::from(rejected_str),
|
||||||
Cell::from(title.to_string()),
|
Cell::from(title.to_string()),
|
||||||
Cell::from(indexer.clone()),
|
Cell::from(indexer.clone()),
|
||||||
Cell::from(format!("{size:.1} GB")),
|
Cell::from(size),
|
||||||
Cell::from(peers),
|
Cell::from(peers),
|
||||||
Cell::from(quality_name),
|
Cell::from(quality_name),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use crate::ui::widgets::managarr_table::ManagarrTable;
|
|||||||
use crate::ui::widgets::message::Message;
|
use crate::ui::widgets::message::Message;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use ratatui::layout::Alignment;
|
use ratatui::layout::Alignment;
|
||||||
use ratatui::text::Text;
|
use ratatui::text::Text;
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
@@ -223,7 +223,7 @@ fn draw_artist_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if let Some(stats) = current_selection.statistics.as_ref() {
|
if let Some(stats) = current_selection.statistics.as_ref() {
|
||||||
let size = convert_to_gb(stats.size_on_disk);
|
let size = format_size(stats.size_on_disk, 2);
|
||||||
artist_description.extend(vec![
|
artist_description.extend(vec![
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
"Albums: ".primary().bold(),
|
"Albums: ".primary().bold(),
|
||||||
@@ -235,7 +235,7 @@ fn draw_artist_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
"Size on Disk: ".primary().bold(),
|
"Size on Disk: ".primary().bold(),
|
||||||
format!("{size:.2} GB").default_color(),
|
size.default_color(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -282,13 +282,10 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
|| "N/A".to_owned(),
|
|| "N/A".to_owned(),
|
||||||
|s| format!("{}/{}", s.track_file_count, s.total_track_count),
|
|s| format!("{}/{}", s.track_file_count, s.total_track_count),
|
||||||
);
|
);
|
||||||
let size = album.statistics.as_ref().map_or_else(
|
let size = album
|
||||||
|| "N/A".to_owned(),
|
.statistics
|
||||||
|s| {
|
.as_ref()
|
||||||
let size = convert_to_gb(s.size_on_disk);
|
.map_or_else(|| "N/A".to_owned(), |s| format_size(s.size_on_disk, 2));
|
||||||
format!("{size:.2} GB")
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let duration_mins = album.duration / 60000;
|
let duration_mins = album.duration / 60000;
|
||||||
|
|
||||||
decorate_album_row_with_style(
|
decorate_album_row_with_style(
|
||||||
@@ -500,7 +497,7 @@ fn draw_artist_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
&& active_lidarr_block != ActiveLidarrBlock::ManualArtistSearchConfirmPrompt,
|
&& active_lidarr_block != ActiveLidarrBlock::ManualArtistSearchConfirmPrompt,
|
||||||
app.should_text_scroll,
|
app.should_text_scroll,
|
||||||
);
|
);
|
||||||
let size = convert_to_gb(*size);
|
let size = format_size(*size, 1);
|
||||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||||
let peers = if seeders.is_none() || leechers.is_none() {
|
let peers = if seeders.is_none() || leechers.is_none() {
|
||||||
Text::from("")
|
Text::from("")
|
||||||
@@ -531,7 +528,7 @@ fn draw_artist_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(rejected_str),
|
Cell::from(rejected_str),
|
||||||
Cell::from(title.to_string()),
|
Cell::from(title.to_string()),
|
||||||
Cell::from(indexer.clone()),
|
Cell::from(indexer.clone()),
|
||||||
Cell::from(format!("{size:.1} GB")),
|
Cell::from(size),
|
||||||
Cell::from(peers),
|
Cell::from(peers),
|
||||||
Cell::from(quality_name),
|
Cell::from(quality_name),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::ui::widgets::{
|
|||||||
confirmation_prompt::ConfirmationPrompt,
|
confirmation_prompt::ConfirmationPrompt,
|
||||||
popup::{Popup, Size},
|
popup::{Popup, Size},
|
||||||
};
|
};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
models::{
|
models::{
|
||||||
@@ -104,7 +104,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
let size = artist
|
let size = artist
|
||||||
.statistics
|
.statistics
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0f64, |stats| convert_to_gb(stats.size_on_disk));
|
.map_or(0, |stats| stats.size_on_disk);
|
||||||
let quality_profile = quality_profile_map
|
let quality_profile = quality_profile_map
|
||||||
.get_by_left(&artist.quality_profile_id)
|
.get_by_left(&artist.quality_profile_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
@@ -140,7 +140,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(metadata_profile),
|
Cell::from(metadata_profile),
|
||||||
Cell::from(albums.to_string()),
|
Cell::from(albums.to_string()),
|
||||||
Cell::from(tracks),
|
Cell::from(tracks),
|
||||||
Cell::from(format!("{size:.2} GB")),
|
Cell::from(format_size(size, 2)),
|
||||||
Cell::from(monitored.to_owned()),
|
Cell::from(monitored.to_owned()),
|
||||||
Cell::from(tags),
|
Cell::from(tags),
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ expression: output
|
|||||||
│ Tracks │ History │ Manual Search │
|
│ Tracks │ History │ Manual Search │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Quality │
|
||||||
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 GB 2 / 1 Lossless │
|
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 MB 2 / 1 Lossless │
|
||||||
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 GB Lossless │
|
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 MB Lossless │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ expression: output
|
|||||||
│ Tracks │ History │ Manual Search │
|
│ Tracks │ History │ Manual Search │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Quality │
|
||||||
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 GB 2 / 1 Lossless │
|
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 MB 2 / 1 Lossless │
|
||||||
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 GB Lossless │
|
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 MB Lossless │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ expression: output
|
|||||||
│ Tracks │ History │ Manual Search │
|
│ Tracks │ History │ Manual Search │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source Age ⛔ Title Indexer Size Peers Quality │
|
│ Source Age ⛔ Title Indexer Size Peers Quality │
|
||||||
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 GB 2 / 1 Lossless │
|
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 MB 2 / 1 Lossless │
|
||||||
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 GB Lossless │
|
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 MB Lossless │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ expression: output
|
|||||||
│ Tracks │ History │ Manual Search │
|
│ Tracks │ History │ Manual Search │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Quality │
|
||||||
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 GB 2 / 1 Lossless │
|
│=> torrent 1 days ⛔ Test Release kickass torrents 0.0 MB 2 / 1 Lossless │
|
||||||
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 GB Lossless │
|
│ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 MB Lossless │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search ││
|
││ Albums │ History │ Manual Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Monitored Title Type Tracks Duration Release Date Size ││
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 302.47 MB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Monitored Title │ │ Release Date Size ││
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album │ │ 2023-01-01 302.47 MB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title │ │ Size Peers Quality ││
|
││ Source ▼ Age ⛔ Title │ │ Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Releas│ │ 0.0 GB 2 / 1 Lossless ││
|
││=> torrent 1 days ⛔ Test Releas│ │ 0.0 MB 2 / 1 Lossless ││
|
||||||
││ usenet 1 days ⛔ Test Releas│ │ 0.0 GB Lossless ││
|
││ usenet 1 days ⛔ Test Releas│ │ 0.0 MB Lossless ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││────────────────────────────────────────│Rejection reasons: │───────────────────────────────────────││
|
││────────────────────────────────────────│Rejection reasons: │───────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title │• Unknown quality profile │e Peers Quality ││
|
││ Source ▼ Age ⛔ Title │• Unknown quality profile │e Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Relea│• Release is already mapped │ GB 2 / 1 Lossless ││
|
││=> torrent 1 days ⛔ Test Relea│• Release is already mapped │ MB 2 / 1 Lossless ││
|
||||||
││ usenet 1 days ⛔ Test Relea│ │ GB Lossless ││
|
││ usenet 1 days ⛔ Test Relea│ │ MB Lossless ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search ││
|
││ Albums │ History │ Manual Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Source Age ⛔ Title Indexer Size Peers Quality ││
|
││ Source Age ⛔ Title Indexer Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Release kickass torrents 0.0 GB 2 / 1 Lossless ││
|
││=> torrent 1 days ⛔ Test Release kickass torrents 0.0 MB 2 / 1 Lossless ││
|
||||||
││ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 GB Lossless ││
|
││ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 MB Lossless ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search ││
|
││ Albums │ History │ Manual Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title Indexer Size Peers Quality ││
|
││ Source ▼ Age ⛔ Title Indexer Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Release kickass torrents 0.0 GB 2 / 1 Lossless ││
|
││=> torrent 1 days ⛔ Test Release kickass torrents 0.0 MB 2 / 1 Lossless ││
|
||||||
││ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 GB Lossless ││
|
││ usenet 1 days ⛔ Test Release DrunkenSlug 0.0 MB Lossless ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search ││
|
││ Albums │ History │ Manual Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Monitored Title Type Tracks Duration Release Date Size ││
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 302.47 MB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search ││
|
││ Albums │ History │ Manual Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Monitored Title Type Tracks Duration Release Date Size ││
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 302.47 MB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Monitored Title │ │ Release Date Size ││
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album │ │ 2023-01-01 302.47 MB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title │ │ Size Peers Quality ││
|
││ Source ▼ Age ⛔ Title │ │ Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Releas│ │ 0.0 GB 2 / 1 Lossless ││
|
││=> torrent 1 days ⛔ Test Releas│ │ 0.0 MB 2 / 1 Lossless ││
|
||||||
││ usenet 1 days ⛔ Test Releas│ │ 0.0 GB Lossless ││
|
││ usenet 1 days ⛔ Test Releas│ │ 0.0 MB Lossless ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Monitored Title │ │ Release Date Size ││
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album │ │ 2023-01-01 302.47 MB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Monitored Title │ ╭───╮ │ Release Date Size ││
|
││ Monitored Title │ ╭───╮ │ Release Date Size ││
|
||||||
││=> 🏷 Test Album │ Delete Album Files: │ ✔ │ │ 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album │ Delete Album Files: │ ✔ │ │ 2023-01-01 302.47 MB ││
|
||||||
││ │ ╰───╯ │ ││
|
││ │ ╰───╯ │ ││
|
||||||
││ │ ╭───╮ │ ││
|
││ │ ╭───╮ │ ││
|
||||||
││ │ Add List Exclusion: │ │ │ ││
|
││ │ Add List Exclusion: │ │ │ ││
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search │ │ ││
|
││ Albums │ History │ Manual Search │ │ ││
|
||||||
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
││ Monitored Title │ │ Release Date Size ││
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album │ │ 2023-01-01 302.47 MB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│Artist: Alex │
|
│Artist: Alex │
|
||||||
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Monitor New Albums: │All Albums ▼ │ │ ││
|
││ Albums │ History │ Monitor New Albums: │All Albums ▼ │ │ ││
|
||||||
││──────────────────│ ╰─────────────────────────────────────────────────╯ │────────────────────││
|
││──────────────────│ ╰─────────────────────────────────────────────────╯ │────────────────────││
|
||||||
││ Monitored Titl│ ╭─────────────────────────────────────────────────╮ │ize ││
|
││ Monitored Titl│ ╭─────────────────────────────────────────────────╮ │ize ││
|
||||||
││=> 🏷 Test│ Quality Profile: │Lossless ▼ │ │.00 GB ││
|
││=> 🏷 Test│ Quality Profile: │Lossless ▼ │ │02.47 MB ││
|
||||||
││ │ ╰─────────────────────────────────────────────────╯ │ ││
|
││ │ ╰─────────────────────────────────────────────────╯ │ ││
|
||||||
││ │ ╭─────────────────────────────────────────────────╮ │ ││
|
││ │ ╭─────────────────────────────────────────────────╮ │ ││
|
||||||
││ │ Metadata Profile: │Standard ▼ │ │ ││
|
││ │ Metadata Profile: │Standard ▼ │ │ ││
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│Artist: Alex │
|
│Artist: Alex │
|
||||||
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│Artist: Alex │
|
│Artist: Alex │
|
||||||
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│Artist: Alex │
|
│Artist: Alex │
|
||||||
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
||||||
@@ -24,7 +24,7 @@ expression: output
|
|||||||
││ Albums │ History │ Manual Search ││
|
││ Albums │ History │ Manual Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Monitored Title Type Tracks Duration Release Date Size ││
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 0.00 GB ││
|
││=> 🏷 Test Album Album 8/10 0 min 2023-01-01 302.47 MB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ expression: output
|
|||||||
---
|
---
|
||||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags
|
||||||
=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex
|
=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use crate::{
|
|||||||
servarr_data::lidarr::lidarr_data::LidarrData,
|
servarr_data::lidarr::lidarr_data::LidarrData,
|
||||||
servarr_models::{DiskSpace, RootFolder},
|
servarr_models::{DiskSpace, RootFolder},
|
||||||
},
|
},
|
||||||
utils::convert_to_gb,
|
utils::format_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod blocklist;
|
mod blocklist;
|
||||||
@@ -176,8 +176,8 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
|||||||
let RootFolder {
|
let RootFolder {
|
||||||
path, free_space, ..
|
path, free_space, ..
|
||||||
} = &monitored_root_folders[i];
|
} = &monitored_root_folders[i];
|
||||||
let space: f64 = convert_to_gb(*free_space);
|
let space = format_size(*free_space, 2);
|
||||||
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
|
let root_folder_space = Paragraph::new(format!("{path}: {space} free"))
|
||||||
.block(borderless_block())
|
.block(borderless_block())
|
||||||
.default_color();
|
.default_color();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::ui::utils::layout_block_top_border;
|
|||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
mod add_root_folder_ui;
|
mod add_root_folder_ui;
|
||||||
|
|
||||||
@@ -69,11 +69,11 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
..
|
..
|
||||||
} = root_folders;
|
} = root_folders;
|
||||||
|
|
||||||
let space: f64 = convert_to_gb(*free_space);
|
let space = format_size(*free_space, 2);
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(path.to_owned()),
|
Cell::from(path.to_owned()),
|
||||||
Cell::from(format!("{space:.2} GB")),
|
Cell::from(space),
|
||||||
Cell::from(
|
Cell::from(
|
||||||
unmapped_folders
|
unmapped_folders
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ expression: output
|
|||||||
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
|
||||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
|
||||||
│=> Alex Person Continuing Lossless Standard 1 12/15 0.00 GB 🏷 alex │
|
│=> Alex Person Continuing Lossless Standard 1 12/15 0.01 MB 🏷 alex │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ impl DrawUi for CollectionDetailsUi {
|
|||||||
draw_popup(f, app, draw_collection_details, Size::Large);
|
draw_popup(f, app, draw_collection_details, Size::Large);
|
||||||
|
|
||||||
if context_option.unwrap_or(active_radarr_block) == ActiveRadarrBlock::ViewMovieOverview {
|
if context_option.unwrap_or(active_radarr_block) == ActiveRadarrBlock::ViewMovieOverview {
|
||||||
draw_popup(f, app, draw_movie_overview, Size::Large);
|
draw_popup(f, app, draw_movie_overview, Size::Medium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,40 +8,40 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭ Overview ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Test Collection ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│Collection movie blah blah blah │
|
│Overview: Collection blah blah blah │
|
||||||
│ │
|
│Root Folder Path: /nfs/movies │
|
||||||
│It was shot in Madison, Wisconsin: a city the cast never really left. │
|
│Quality Profile: HD - 1080p │
|
||||||
│ │
|
│Minimum Avai╭ Overview ─────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│The sequel took seven years to finish. │
|
│Monitored: Y│Collection movie blah blah blah │ │
|
||||||
│ │
|
│Search on Ad│ │ │
|
||||||
│ │
|
│ │It was shot in Madison, Wisconsin: a city the cast never really left. │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │The sequel took seven years to finish. │ │
|
||||||
│ │
|
│ Movies ──│ │───────────│
|
||||||
│ │
|
│ ✔ Title │ │ │
|
||||||
│ │
|
│=> ✔ Test │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -8,40 +8,40 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭ Overview ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Test Collection ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ │
|
│Overview: Collection blah blah blah │
|
||||||
│ │
|
│Root Folder Path: /nfs/movies │
|
||||||
│ Loading ... │
|
│Quality Profile: HD - 1080p │
|
||||||
│ │
|
│Minimum Avai╭ Overview ─────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ │
|
│Monitored: Y│ │ │
|
||||||
│ │
|
│Search on Ad│ │ │
|
||||||
│ │
|
│ │ Loading ... │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ Movies ──│ │───────────│
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ Loading ...│ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -8,40 +8,40 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭ Overview ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Test Collection ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ │
|
│Overview: Collection blah blah blah │
|
||||||
│ │
|
│Root Folder Path: /nfs/movies │
|
||||||
│ Loading ... │
|
│Quality Profile: HD - 1080p │
|
||||||
│ │
|
│Minimum Avai╭ Overview ─────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ │
|
│Monitored: Y│ │ │
|
||||||
│ │
|
│Search on Ad│ │ │
|
||||||
│ │
|
│ │ Loading ... │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ Movies ──│ │───────────│
|
||||||
│ │
|
│ ✔ Title │ │ │
|
||||||
│ │
|
│=> ✔ Test │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -8,40 +8,40 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭ Overview ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭ Test Collection ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│It was shot in Madison, Wisconsin: a city the cast never really left. │
|
│Overview: Collection blah blah blah │
|
||||||
│ │
|
│Root Folder Path: /nfs/movies │
|
||||||
│The sequel took seven years to finish. │
|
│Quality Profile: HD - 1080p │
|
||||||
│ │
|
│Minimum Avai╭ Overview ─────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ │
|
│Monitored: Y│It was shot in Madison, Wisconsin: a city the cast never really left. │ │
|
||||||
│ │
|
│Search on Ad│ │ │
|
||||||
│ │
|
│ │The sequel took seven years to finish. │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ Movies ──│ │───────────│
|
||||||
│ │
|
│ ✔ Title │ │ │
|
||||||
│ │
|
│=> ✔ Test │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ │ │ │
|
||||||
│ │
|
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
|||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "downloads_ui_tests.rs"]
|
#[path = "downloads_ui_tests.rs"]
|
||||||
@@ -96,12 +96,12 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
} else {
|
} else {
|
||||||
1f64 - (*sizeleft as f64 / *size as f64)
|
1f64 - (*sizeleft as f64 / *size as f64)
|
||||||
};
|
};
|
||||||
let file_size: f64 = convert_to_gb(*size);
|
let file_size = format_size(*size, 2);
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(title.to_owned()),
|
Cell::from(title.to_owned()),
|
||||||
Cell::from(format!("{:.0}%", percent * 100.0)),
|
Cell::from(format!("{:.0}%", percent * 100.0)),
|
||||||
Cell::from(format!("{file_size:.2} GB")),
|
Cell::from(file_size),
|
||||||
Cell::from(
|
Cell::from(
|
||||||
output_path
|
output_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
|||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::utils::{convert_runtime, convert_to_gb};
|
use crate::utils::{convert_runtime, format_size};
|
||||||
|
|
||||||
mod add_movie_ui;
|
mod add_movie_ui;
|
||||||
mod delete_movie_ui;
|
mod delete_movie_ui;
|
||||||
@@ -95,7 +95,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
let monitored = if movie.monitored { "🏷" } else { "" };
|
let monitored = if movie.monitored { "🏷" } else { "" };
|
||||||
let studio = movie.studio.clone().unwrap_or_default();
|
let studio = movie.studio.clone().unwrap_or_default();
|
||||||
let (hours, minutes) = convert_runtime(movie.runtime);
|
let (hours, minutes) = convert_runtime(movie.runtime);
|
||||||
let file_size: f64 = convert_to_gb(movie.size_on_disk);
|
let file_size = format_size(movie.size_on_disk, 2);
|
||||||
let certification = movie.certification.clone().unwrap_or_default();
|
let certification = movie.certification.clone().unwrap_or_default();
|
||||||
let quality_profile = quality_profile_map
|
let quality_profile = quality_profile_map
|
||||||
.get_by_left(&movie.quality_profile_id)
|
.get_by_left(&movie.quality_profile_id)
|
||||||
@@ -121,7 +121,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(format!("{hours}h {minutes}m")),
|
Cell::from(format!("{hours}h {minutes}m")),
|
||||||
Cell::from(certification),
|
Cell::from(certification),
|
||||||
Cell::from(movie.original_language.name.to_owned()),
|
Cell::from(movie.original_language.name.to_owned()),
|
||||||
Cell::from(format!("{file_size:.2} GB")),
|
Cell::from(file_size),
|
||||||
Cell::from(quality_profile),
|
Cell::from(quality_profile),
|
||||||
Cell::from(monitored.to_owned()),
|
Cell::from(monitored.to_owned()),
|
||||||
Cell::from(tags),
|
Cell::from(tags),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use crate::ui::widgets::loading_block::LoadingBlock;
|
|||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "movie_details_ui_tests.rs"]
|
#[path = "movie_details_ui_tests.rs"]
|
||||||
@@ -398,7 +398,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
|
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
|
||||||
app.should_text_scroll,
|
app.should_text_scroll,
|
||||||
);
|
);
|
||||||
let size = convert_to_gb(*size);
|
let size = format_size(*size, 1);
|
||||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||||
let peers = if seeders.is_none() || leechers.is_none() {
|
let peers = if seeders.is_none() || leechers.is_none() {
|
||||||
Text::from("")
|
Text::from("")
|
||||||
@@ -434,7 +434,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(rejected_str),
|
Cell::from(rejected_str),
|
||||||
Cell::from(title.to_string()),
|
Cell::from(title.to_string()),
|
||||||
Cell::from(indexer.clone()),
|
Cell::from(indexer.clone()),
|
||||||
Cell::from(format!("{size:.1} GB")),
|
Cell::from(size),
|
||||||
Cell::from(peers),
|
Cell::from(peers),
|
||||||
Cell::from(language),
|
Cell::from(language),
|
||||||
Cell::from(quality),
|
Cell::from(quality),
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ expression: output
|
|||||||
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
||||||
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
||||||
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 GB 25 / 3 English HD - 1080p │
|
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 MB 25 / 3 English HD - 1080p │
|
||||||
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 GB English HD - 1080p │
|
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 MB English HD - 1080p │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ expression: output
|
|||||||
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
||||||
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
||||||
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 GB 25 / 3 English HD - 1080p │
|
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 MB 25 / 3 English HD - 1080p │
|
||||||
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 GB English HD - 1080p │
|
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 MB English HD - 1080p │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ expression: output
|
|||||||
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
||||||
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source Age ⛔ Title Indexer Size Peers Language Quality │
|
│ Source Age ⛔ Title Indexer Size Peers Language Quality │
|
||||||
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 GB 25 / 3 English HD - 1080p │
|
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 MB 25 / 3 English HD - 1080p │
|
||||||
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 GB English HD - 1080p │
|
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 MB English HD - 1080p │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ expression: output
|
|||||||
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
||||||
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
||||||
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 GB 25 / 3 English HD - 1080p │
|
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 MB 25 / 3 English HD - 1080p │
|
||||||
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 GB English HD - 1080p │
|
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 MB English HD - 1080p │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ expression: output
|
|||||||
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
│ Details │ History │ File │ Cast │ Crew │ Manual Search │
|
||||||
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
│ Source ▼ Age ⛔ Title Indexer Size Peers Language Quality │
|
||||||
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 GB 25 / 3 English HD - 1080p │
|
│=> torrent 12 days ⛔ Some movie release The Pirate Bay 0.0 MB 25 / 3 English HD - 1080p │
|
||||||
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 GB English HD - 1080p │
|
│ usenet 22 days ⛔ Some Other movie rele The Pirate Bay 0.0 MB English HD - 1080p │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::ui::utils::{
|
|||||||
line_gauge_with_label, line_gauge_with_title, title_block,
|
line_gauge_with_label, line_gauge_with_title, title_block,
|
||||||
};
|
};
|
||||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -170,8 +170,8 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
|||||||
let RootFolder {
|
let RootFolder {
|
||||||
path, free_space, ..
|
path, free_space, ..
|
||||||
} = &monitored_root_folders[i];
|
} = &monitored_root_folders[i];
|
||||||
let space: f64 = convert_to_gb(*free_space);
|
let space = format_size(*free_space, 2);
|
||||||
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
|
let root_folder_space = Paragraph::new(format!("{path}: {space} free"))
|
||||||
.block(borderless_block())
|
.block(borderless_block())
|
||||||
.default_color();
|
.default_color();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
|||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{DrawUi, draw_input_box_popup, draw_popup};
|
use crate::ui::{DrawUi, draw_input_box_popup, draw_popup};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "root_folders_ui_tests.rs"]
|
#[path = "root_folders_ui_tests.rs"]
|
||||||
@@ -66,11 +66,11 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
..
|
..
|
||||||
} = root_folders;
|
} = root_folders;
|
||||||
|
|
||||||
let space: f64 = convert_to_gb(*free_space);
|
let space = format_size(*free_space, 2);
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(path.to_owned()),
|
Cell::from(path.to_owned()),
|
||||||
Cell::from(format!("{space:.2} GB")),
|
Cell::from(space),
|
||||||
Cell::from(
|
Cell::from(
|
||||||
unmapped_folders
|
unmapped_folders
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
|||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::utils::convert_f64_to_gb;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "downloads_ui_tests.rs"]
|
#[path = "downloads_ui_tests.rs"]
|
||||||
@@ -97,12 +97,12 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
} else {
|
} else {
|
||||||
1f64 - (*sizeleft / *size)
|
1f64 - (*sizeleft / *size)
|
||||||
};
|
};
|
||||||
let file_size: f64 = convert_f64_to_gb(*size);
|
let file_size = format_size(*size as i64, 2);
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(title.to_owned()),
|
Cell::from(title.to_owned()),
|
||||||
Cell::from(format!("{:.0}%", percent * 100.0)),
|
Cell::from(format!("{:.0}%", percent * 100.0)),
|
||||||
Cell::from(format!("{file_size:.2} GB")),
|
Cell::from(file_size),
|
||||||
Cell::from(
|
Cell::from(
|
||||||
output_path
|
output_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::ui::widgets::managarr_table::ManagarrTable;
|
|||||||
use crate::ui::widgets::message::Message;
|
use crate::ui::widgets::message::Message;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use ratatui::layout::Alignment;
|
use ratatui::layout::Alignment;
|
||||||
use ratatui::text::Text;
|
use ratatui::text::Text;
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
@@ -215,7 +215,7 @@ fn draw_author_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if let Some(stats) = current_selection.statistics.as_ref() {
|
if let Some(stats) = current_selection.statistics.as_ref() {
|
||||||
let size = convert_to_gb(stats.size_on_disk);
|
let size = format_size(stats.size_on_disk, 2);
|
||||||
author_description.extend(vec![
|
author_description.extend(vec![
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
"Books: ".primary().bold(),
|
"Books: ".primary().bold(),
|
||||||
@@ -227,7 +227,7 @@ fn draw_author_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
"Size on Disk: ".primary().bold(),
|
"Size on Disk: ".primary().bold(),
|
||||||
format!("{size:.2} GB").default_color(),
|
size.default_color(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -278,13 +278,10 @@ fn draw_books_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
|| "N/A".to_owned(),
|
|| "N/A".to_owned(),
|
||||||
|s| format!("{}/{}", s.book_file_count, s.total_book_count),
|
|s| format!("{}/{}", s.book_file_count, s.total_book_count),
|
||||||
);
|
);
|
||||||
let size = book.statistics.as_ref().map_or_else(
|
let size = book
|
||||||
|| "N/A".to_owned(),
|
.statistics
|
||||||
|s| {
|
.as_ref()
|
||||||
let size = convert_to_gb(s.size_on_disk);
|
.map_or_else(|| "N/A".to_owned(), |s| format_size(s.size_on_disk, 2));
|
||||||
format!("{size:.2} GB")
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
decorate_book_row_with_style(
|
decorate_book_row_with_style(
|
||||||
book,
|
book,
|
||||||
@@ -483,7 +480,7 @@ fn draw_author_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
&& active_readarr_block != ActiveReadarrBlock::ManualAuthorSearchConfirmPrompt,
|
&& active_readarr_block != ActiveReadarrBlock::ManualAuthorSearchConfirmPrompt,
|
||||||
app.should_text_scroll,
|
app.should_text_scroll,
|
||||||
);
|
);
|
||||||
let size = convert_to_gb(*size);
|
let size = format_size(*size, 1);
|
||||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||||
let peers = if seeders.is_none() || leechers.is_none() {
|
let peers = if seeders.is_none() || leechers.is_none() {
|
||||||
Text::from("")
|
Text::from("")
|
||||||
@@ -514,7 +511,7 @@ fn draw_author_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(rejected_str),
|
Cell::from(rejected_str),
|
||||||
Cell::from(title.to_string()),
|
Cell::from(title.to_string()),
|
||||||
Cell::from(indexer.clone()),
|
Cell::from(indexer.clone()),
|
||||||
Cell::from(format!("{size:.1} GB")),
|
Cell::from(size),
|
||||||
Cell::from(peers),
|
Cell::from(peers),
|
||||||
Cell::from(quality_name),
|
Cell::from(quality_name),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::ui::widgets::managarr_table::ManagarrTable;
|
|||||||
use crate::ui::widgets::message::Message;
|
use crate::ui::widgets::message::Message;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use ratatui::layout::{Alignment, Constraint, Rect};
|
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||||
use ratatui::prelude::{Line, Stylize, Text};
|
use ratatui::prelude::{Line, Stylize, Text};
|
||||||
@@ -234,7 +234,7 @@ fn draw_book_file_info(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
..
|
..
|
||||||
} = book_file;
|
} = book_file;
|
||||||
|
|
||||||
let size = convert_to_gb(*size);
|
let size = format_size(*size, 2);
|
||||||
let audio_info = media_info
|
let audio_info = media_info
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|media_info| {
|
.map(|media_info| {
|
||||||
@@ -249,7 +249,7 @@ fn draw_book_file_info(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(path.clone()),
|
Cell::from(path.clone()),
|
||||||
Cell::from(format!("{size:.2} GB")),
|
Cell::from(size),
|
||||||
Cell::from(quality.quality.name.clone()),
|
Cell::from(quality.quality.name.clone()),
|
||||||
Cell::from(audio_info),
|
Cell::from(audio_info),
|
||||||
])
|
])
|
||||||
@@ -395,7 +395,7 @@ fn draw_book_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
&& active_readarr_block != ActiveReadarrBlock::ManualBookSearchConfirmPrompt,
|
&& active_readarr_block != ActiveReadarrBlock::ManualBookSearchConfirmPrompt,
|
||||||
app.should_text_scroll,
|
app.should_text_scroll,
|
||||||
);
|
);
|
||||||
let size = convert_to_gb(*size);
|
let size = format_size(*size, 1);
|
||||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||||
let peers = if seeders.is_none() || leechers.is_none() {
|
let peers = if seeders.is_none() || leechers.is_none() {
|
||||||
Text::from("")
|
Text::from("")
|
||||||
@@ -426,7 +426,7 @@ fn draw_book_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(rejected_str),
|
Cell::from(rejected_str),
|
||||||
Cell::from(title.to_string()),
|
Cell::from(title.to_string()),
|
||||||
Cell::from(indexer.clone()),
|
Cell::from(indexer.clone()),
|
||||||
Cell::from(format!("{size:.1} GB")),
|
Cell::from(size),
|
||||||
Cell::from(peers),
|
Cell::from(peers),
|
||||||
Cell::from(quality_name),
|
Cell::from(quality_name),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::ui::widgets::{
|
|||||||
confirmation_prompt::ConfirmationPrompt,
|
confirmation_prompt::ConfirmationPrompt,
|
||||||
popup::{Popup, Size},
|
popup::{Popup, Size},
|
||||||
};
|
};
|
||||||
use crate::utils::convert_to_gb;
|
use crate::utils::format_size;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
models::{
|
models::{
|
||||||
@@ -103,7 +103,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
let size = author
|
let size = author
|
||||||
.statistics
|
.statistics
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0f64, |stats| convert_to_gb(stats.size_on_disk));
|
.map_or(0, |stats| stats.size_on_disk);
|
||||||
let quality_profile = quality_profile_map
|
let quality_profile = quality_profile_map
|
||||||
.get_by_left(&author.quality_profile_id)
|
.get_by_left(&author.quality_profile_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
@@ -134,7 +134,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Cell::from(quality_profile),
|
Cell::from(quality_profile),
|
||||||
Cell::from(metadata_profile),
|
Cell::from(metadata_profile),
|
||||||
Cell::from(books.to_string()),
|
Cell::from(books.to_string()),
|
||||||
Cell::from(format!("{size:.2} GB")),
|
Cell::from(format_size(size, 2)),
|
||||||
Cell::from(monitored.to_owned()),
|
Cell::from(monitored.to_owned()),
|
||||||
Cell::from(tags),
|
Cell::from(tags),
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ expression: output
|
|||||||
││ Books │ History │ Author Search ││
|
││ Books │ History │ Author Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Monitored Title Files Release Date Size ││
|
││ Monitored Title Files Release Date Size ││
|
||||||
││=> 🏷 Test Book 3/7 2023-05-20 2.30 GB ││
|
││=> 🏷 Test Book 3/7 2023-05-20 2.34 MB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ expression: output
|
|||||||
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
|
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
|
||||||
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
|
||||||
││ Monitored Title │ │Date Size ││
|
││ Monitored Title │ │Date Size ││
|
||||||
││=> 🏷 Test Book │ │20 2.30 GB ││
|
││=> 🏷 Test Book │ │20 2.34 MB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ expression: output
|
|||||||
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
|
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
|
||||||
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
|
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title │ │ Size Peers Quality ││
|
││ Source ▼ Age ⛔ Title │ │ Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Torren│ │ 0.0 GB 2 / 1 EPUB ││
|
││=> torrent 1 days ⛔ Test Torren│ │ 0.0 MB 2 / 1 EPUB ││
|
||||||
││ usenet 1 days ⛔ Test Usenet│ │ 0.0 GB EPUB ││
|
││ usenet 1 days ⛔ Test Usenet│ │ 0.0 MB EPUB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ expression: output
|
|||||||
││ Books │ History │ Author Search │ Torrent Release? │ ││
|
││ Books │ History │ Author Search │ Torrent Release? │ ││
|
||||||
││────────────────────────────────────────│ │───────────────────────────────────────││
|
││────────────────────────────────────────│ │───────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title │ │e Peers Quality ││
|
││ Source ▼ Age ⛔ Title │ │e Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Torre│Rejection reasons: │ GB 2 / 1 EPUB ││
|
││=> torrent 1 days ⛔ Test Torre│Rejection reasons: │ MB 2 / 1 EPUB ││
|
||||||
││ usenet 1 days ⛔ Test Usene│• Unknown quality profile │ GB EPUB ││
|
││ usenet 1 days ⛔ Test Usene│• Unknown quality profile │ MB EPUB ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
││ │ │ ││
|
││ │ │ ││
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ expression: output
|
|||||||
││ Books │ History │ Author Search ││
|
││ Books │ History │ Author Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Source Age ⛔ Title Indexer Size Peers Quality ││
|
││ Source Age ⛔ Title Indexer Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Torrent Release kickass torrents 0.0 GB 2 / 1 EPUB ││
|
││=> torrent 1 days ⛔ Test Torrent Release kickass torrents 0.0 MB 2 / 1 EPUB ││
|
||||||
││ usenet 1 days ⛔ Test Usenet Release DrunkenSlug 0.0 GB EPUB ││
|
││ usenet 1 days ⛔ Test Usenet Release DrunkenSlug 0.0 MB EPUB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ expression: output
|
|||||||
││ Books │ History │ Author Search ││
|
││ Books │ History │ Author Search ││
|
||||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
││ Source ▼ Age ⛔ Title Indexer Size Peers Quality ││
|
││ Source ▼ Age ⛔ Title Indexer Size Peers Quality ││
|
||||||
││=> torrent 1 days ⛔ Test Torrent Release kickass torrents 0.0 GB 2 / 1 EPUB ││
|
││=> torrent 1 days ⛔ Test Torrent Release kickass torrents 0.0 MB 2 / 1 EPUB ││
|
||||||
││ usenet 1 days ⛔ Test Usenet Release DrunkenSlug 0.0 GB EPUB ││
|
││ usenet 1 days ⛔ Test Usenet Release DrunkenSlug 0.0 MB EPUB ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
││ ││
|
││ ││
|
||||||
|
|||||||