Compare commits

..

17 Commits

Author SHA1 Message Date
4f9bc34d23 docs: Updated the README so that the example configuration only includes references to Servarrs that are actually supported [#55] 2026-01-30 15:45:22 -07:00
a2aa9507a9 fix: Improved the first-time run behavior so that it outputs the default configuration file it tries to load to help users locate the file on first-runs 2026-01-30 15:36:26 -07:00
c791b985f0 docs: Updated README to tell users to use 'managarr config-path' as the default method to discover the location of the default configuration file 2026-01-29 12:56:59 -07:00
5c517a748c fix: 'managarr config-path' should work without a pre-existing config already in place [#54] 2026-01-29 12:54:45 -07:00
892c687077 docs: Updated README with config-path as another way to find the default config file for a given system 2026-01-29 10:26:12 -07:00
c6d5b98e86 feat: Implemented a 'config-path' command to print out the default Managarr configuration file path to help address #54 2026-01-29 10:23:05 -07:00
67e5114ec2 build: Removed #[allow(dead_code)] from the LIDARR_LOGO since it is now being utilized 2026-01-26 11:56:05 -07:00
fdc331865e feat: Full support for filtering disks and aggregating root folders in the UI's 'Stats' block 2026-01-26 11:10:59 -07:00
f388dccc08 feat: proper collapsing of root folder paths in the stats layer of the UI 2026-01-22 14:44:48 -07:00
64fad3b9bc refactor: Removed the filtering of monitored_storage_paths from the networking module and migrated all of it to the UI 2026-01-22 13:12:51 -07:00
3be7b09da8 feat: Added config option to filter for specific disk space paths to display in the UI (CLI is unaffected) 2026-01-22 10:49:30 -07:00
5f3123cd79 test: Updated snapshot tests to assert the paths are updated in the UI
Check / stable / fmt (push) Successful in 9m57s
Check / beta / clippy (push) Successful in 10m59s
Check / stable / clippy (push) Successful in 10m59s
Check / nightly / doc (push) Successful in 59s
Check / 1.89.0 / check (push) Successful in 1m7s
Test Suite / ubuntu / beta (push) Successful in 1m48s
Test Suite / ubuntu / stable (push) Successful in 1m43s
Test Suite / ubuntu / stable / coverage (push) Successful in 12m55s
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
2026-01-22 09:39:44 -07:00
d8f7febfe1 feat: Improved disk-space UI and CLI that shows the actual path being monitored instead of just a disk number
Check / stable / fmt (push) Has been cancelled
Check / beta / clippy (push) Has been cancelled
Check / stable / clippy (push) Has been cancelled
Check / nightly / doc (push) Has been cancelled
Check / 1.89.0 / check (push) Has been cancelled
Test Suite / ubuntu / beta (push) Has been cancelled
Test Suite / ubuntu / stable (push) Has been cancelled
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
Test Suite / ubuntu / stable / coverage (push) Has been cancelled
2026-01-22 09:36:58 -07:00
0bfbb44e3e feat: Implemented the forgotten lidarr list disk-space command
Check / stable / fmt (push) Successful in 9m59s
Check / beta / clippy (push) Successful in 10m58s
Check / stable / clippy (push) Has been cancelled
Check / nightly / doc (push) Has been cancelled
Check / 1.89.0 / check (push) Has been cancelled
Test Suite / ubuntu / beta (push) Has been cancelled
Test Suite / ubuntu / stable (push) Has been cancelled
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
Test Suite / ubuntu / stable / coverage (push) Has been cancelled
2026-01-22 09:06:38 -07:00
github-actions[bot]
c5161f828d chore: bump Cargo.toml to 0.7.0
Check / stable / fmt (push) Successful in 9m57s
Check / beta / clippy (push) Successful in 11m0s
Check / stable / clippy (push) Successful in 10m59s
Check / nightly / doc (push) Successful in 57s
Check / 1.89.0 / check (push) Successful in 1m0s
Test Suite / ubuntu / beta (push) Successful in 1m42s
Test Suite / ubuntu / stable (push) Successful in 1m42s
Test Suite / ubuntu / stable / coverage (push) Successful in 12m38s
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
2026-01-21 19:22:11 +00:00
github-actions[bot]
71c64167f0 bump: version 0.6.3 → 0.7.0 [skip ci] 2026-01-21 19:22:03 +00:00
Alex Clarke
4d3e00fd94 Merge pull request #52 from Dark-Alex-17/lidarr
Lidarr Support
2026-01-21 11:57:50 -07:00
35 changed files with 937 additions and 345 deletions
+77
View File
@@ -5,6 +5,83 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.7.0 (2026-01-21)
### Feat
- Blocklist support in Lidarr in both the CLI and TUI
- CLI and TUI support for track history and track details in Lidarr
- Lidarr UI support for album details popup
- Implemented TUI handler support for the Album Details popup in Lidarr
- Bulk added CLI support for tracks and album functionalities in Lidarr
- Implemented the manual artist discography search tab in Lidarr's artist details UI
- Lidarr CLI support for downloading a release
- CLI support for searching for discography releases in Lidarr
- Added TUI and CLI support for viewing Artist history in Lidarr
- Full Lidarr system support for both the CLI and TUI
- Full CLI and TUI support for the Lidarr Indexers tab
- Full support for adding a root folder in Lidarr from both the CLI and TUI
- naive lidarr root folder tab implementation. Needs improved add logic
- Downloads tab support in Lidarr
- Created a History tab in the Radarr UI and created a list history command and mark-history-item-as-failed command for Radarr
- Implemented the Lidarr History tab and CLI support
- TUI support for deleting a Lidarr album from the artist details popup
- CLI support for deleting an album from Lidarr
- Completed support for viewing Lidarr artist details
- Full CLI and TUI support for adding an artist to Lidarr
- Include the Lidarr artist disambiguation in the title of the Edit Artist popup
- Initial Lidarr support for searching for new artists
- Lidarr CLI commands to list quality profiles and metadata profiles
- Improved CLI readability by creating a separate Global Options section for global flags
- CLI support for deleting a tag in Lidarr
- Lidarr CLI support for listing and adding tags
- Added CLI and TUI support for editing Lidarr artists
- Support for updating all Lidarr artists in both the CLI and TUI
- Added Lidarr CLI support for fetching the host config and the security config
- Created Lidarr commands: 'get artist-details' and 'get system-status'
- Fetch the artist members as part of the artist details query
- Support for toggling the monitoring of a given artist via the CLI and TUI
- Full support for deleting an artist via CLI and TUI
- TUI support for Lidarr library
- CLI support for listing artists
- Improved UI speed and responsiveness
### Fix
- Sonarr network wasn't checking for the user to be using the sorting block when populating season details
- Sonarr CLI was not properly filtering out episode and season releases when manually searching for releases
- Sonarr manual search TUI and CLI incorrectly displaying the same unfiltered results for both season and episode searches
- Slowed down the automatic text scrolling in tables so the text is readable
- Expanded the history item details size so that it can include all the available information for a given item; was previously being cut off on some screens
- Bug in submitting the update series prompt in the series details UI in Sonarr
- Don't include Lidarr artist disambiguation in Edit popup title when it is empty
- Refactored how quality profiles, language profiles, and metadata profiles are populated for each servarr so they sort using the ID to mimic the web UI better
- Added the correct keybinding context to the Lidarr edit artist popup
- Improved fault tolerance for search result tables and test all indexer results tables
- Prevented additional empty slice errors in indexer tables
- Fixed a bug in all Servarr implementations to not try to get the current selection of a search table when an error is returned from the API
- Fixed an issue with the Managarr table that would incorrectly try to display things before is_loading was ready
- Fixed a bug where the edit collection popup would not display when opening it from collection details
### Refactor
- Refactored the SonarrEvent enum to not unnecessarily wrap dual series_id and season_number values in a tuple when both values can be passed directly
- Improved and simplified the implementation of history details for both Sonarr and Lidarr
- Let serde serialize Add Series and Add Movie enums instead of calling to_string up front
- Use is_multiple_of for the tick counter in the UI module
- Updated all model tests to use purpose-built assertions to improve readability and maintainability
- Updated all handler tests to use purpose built assertions to improve readability and maintainability
- Used is_multiple_of to make life easier and cleaner in the app module
- Refactored all cli tests to use purpose-built assertions
- Improved test assertions in the app module
- Created dedicated proptests and assertions to clean up the handler unit tests
- Migrated the handle_table_events macro into a trait for better IDE support, created a TableEventAdapter wrapper for the KeyEventHandlers to make it so that the trait can be used properly and a simple function to replace the previous call to the handle_table_events macro
- Simplified both the table_handler macro and the stateful_table implementation
- Improved error handling for the tail-logs subcommand to propagate errors up the stack instead of exiting there.
- Added accessor methods to servarr_data structs, replaced for loops with functional iterator chains, eliminated mutable state tracking, and updated network module to use get_or_insert_default() for modal options
- Improved error handling project-wide and cleaned up some regexes with unnecessary escapes (tail_logs and interpolate_env_vars)
- Refactored to use more idiomatic let-else statements where applicable
## v0.6.3 (2025-12-13)
### Fix
Generated
+176 -221
View File
@@ -118,9 +118,9 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "2.1.1"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85"
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
dependencies = [
"anstyle",
"bstr",
@@ -133,9 +133,9 @@ dependencies = [
[[package]]
name = "assertables"
version = "9.8.3"
version = "9.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbada39b42413d4db3d9460f6e791702490c40f72924378a1b6fc1a4181188fd"
checksum = "4dcd1f7f2f608b9a888a851f234086946c2ca1dfeadf1431c5082fee0942eeb6"
[[package]]
name = "async-trait"
@@ -305,9 +305,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.51"
version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -327,9 +327,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.42"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -341,9 +341,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.54"
version = "4.5.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
dependencies = [
"clap_builder",
"clap_derive",
@@ -351,9 +351,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.54"
version = "4.5.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
dependencies = [
"anstream",
"anstyle",
@@ -373,9 +373,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.49"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck",
"proc-macro2",
@@ -385,9 +385,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.6"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
[[package]]
name = "colorchoice"
@@ -397,11 +397,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "3.0.0"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -420,14 +420,15 @@ dependencies = [
[[package]]
name = "confy"
version = "0.6.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45b1f4c00870f07dc34adcac82bb6a72cc5aabca8536ba1797e01df51d2ce9a0"
checksum = "8807c397789cbe02bbdb1a27ea5f345584132808697b2a3f957c829829ee4814"
dependencies = [
"directories",
"etcetera",
"lazy_static",
"serde",
"serde_yaml",
"thiserror 1.0.69",
"thiserror 2.0.18",
]
[[package]]
@@ -561,6 +562,16 @@ dependencies = [
"darling_macro 0.20.11",
]
[[package]]
name = "darling"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
dependencies = [
"darling_core 0.21.3",
"darling_macro 0.21.3",
]
[[package]]
name = "darling"
version = "0.23.0"
@@ -585,6 +596,20 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "darling_core"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.114",
]
[[package]]
name = "darling_core"
version = "0.23.0"
@@ -609,6 +634,17 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "darling_macro"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [
"darling_core 0.21.3",
"quote",
"syn 2.0.114",
]
[[package]]
name = "darling_macro"
version = "0.23.0"
@@ -671,11 +707,11 @@ dependencies = [
[[package]]
name = "derive_setters"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9"
checksum = "b7e6f6fa1f03c14ae082120b84b3c7fbd7b8588d924cf2d7c3daf9afd49df8b9"
dependencies = [
"darling 0.20.11",
"darling 0.21.3",
"proc-macro2",
"quote",
"syn 2.0.114",
@@ -715,15 +751,6 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
@@ -734,18 +761,6 @@ dependencies = [
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@@ -853,10 +868,21 @@ dependencies = [
]
[[package]]
name = "euclid"
version = "0.22.11"
name = "etcetera"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.59.0",
]
[[package]]
name = "euclid"
version = "0.22.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
dependencies = [
"num-traits",
]
@@ -890,9 +916,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.6"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "finl_unicode"
@@ -1029,9 +1055,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
@@ -1104,6 +1130,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "http"
version = "1.4.0"
@@ -1254,9 +1289,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.64"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -1418,9 +1453,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.46.0"
version = "1.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5"
checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8"
dependencies = [
"console",
"once_cell",
@@ -1463,15 +1498,6 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
@@ -1489,9 +1515,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "js-sys"
version = "0.3.83"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -1505,7 +1531,7 @@ checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b"
dependencies = [
"hashbrown",
"portable-atomic",
"thiserror 2.0.17",
"thiserror 2.0.18",
]
[[package]]
@@ -1522,9 +1548,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.179"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libredox"
@@ -1615,7 +1641,7 @@ dependencies = [
"serde-value",
"serde_json",
"serde_yaml",
"thiserror 2.0.17",
"thiserror 2.0.18",
"thread-id",
"typemap-ors",
"unicode-segmentation",
@@ -1643,7 +1669,7 @@ dependencies = [
[[package]]
name = "managarr"
version = "0.6.3"
version = "0.7.0"
dependencies = [
"anyhow",
"assert_cmd",
@@ -1668,7 +1694,7 @@ dependencies = [
"indicatif",
"indoc",
"insta",
"itertools 0.14.0",
"itertools",
"log",
"log4rs",
"managarr-tree-widget",
@@ -1878,9 +1904,9 @@ dependencies = [
[[package]]
name = "num-conv"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
name = "num-derive"
@@ -2006,9 +2032,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-src"
version = "300.5.4+3.5.4"
version = "300.5.5+3.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72"
checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709"
dependencies = [
"cc",
]
@@ -2026,12 +2052,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordered-float"
version = "2.10.1"
@@ -2270,9 +2290,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.105"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -2304,9 +2324,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.43"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
@@ -2333,7 +2353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core 0.9.3",
"rand_core 0.9.5",
]
[[package]]
@@ -2343,7 +2363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
"rand_core 0.9.5",
]
[[package]]
@@ -2354,9 +2374,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.3"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
@@ -2367,7 +2387,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core 0.9.3",
"rand_core 0.9.5",
]
[[package]]
@@ -2394,11 +2414,11 @@ dependencies = [
"compact_str",
"hashbrown",
"indoc",
"itertools 0.14.0",
"itertools",
"kasuari",
"lru",
"strum 0.27.2",
"thiserror 2.0.17",
"thiserror 2.0.18",
"unicode-segmentation",
"unicode-truncate",
"unicode-width",
@@ -2446,7 +2466,7 @@ dependencies = [
"hashbrown",
"indoc",
"instability",
"itertools 0.14.0",
"itertools",
"line-clipping",
"ratatui-core",
"strum 0.27.2",
@@ -2470,7 +2490,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.16",
"getrandom 0.2.17",
"libredox",
"thiserror 1.0.69",
]
@@ -2558,7 +2578,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
@@ -2596,9 +2616,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
version = "0.1.26"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "rustc_version"
@@ -2650,18 +2670,18 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.13.2"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.8"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"ring",
"rustls-pki-types",
@@ -2920,9 +2940,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "siphasher"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
@@ -2938,9 +2958,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
dependencies = [
"libc",
"windows-sys 0.60.2",
@@ -3193,11 +3213,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.17"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.17",
"thiserror-impl 2.0.18",
]
[[package]]
@@ -3213,9 +3233,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.17"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@@ -3224,34 +3244,34 @@ dependencies = [
[[package]]
name = "thread-id"
version = "5.0.0"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99043e46c5a15af379c06add30d9c93a6c0e8849de00d244c4a2c417da128d80"
checksum = "2010d27add3f3240c1fef7959f46c814487b216baee662af53be645ba7831c07"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
name = "time"
version = "0.3.44"
version = "0.3.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5"
dependencies = [
"deranged",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"serde_core",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "tinystr"
@@ -3326,9 +3346,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.9.10+spec-1.1.0"
version = "0.9.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
dependencies = [
"serde_core",
"serde_spanned",
@@ -3374,9 +3394,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "tower"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
@@ -3483,20 +3503,20 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330"
checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
dependencies = [
"itertools 0.13.0",
"itertools",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.2.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
@@ -3557,9 +3577,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.19.0"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [
"atomic",
"getrandom 0.3.4",
@@ -3654,18 +3674,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.106"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
"cfg-if",
"once_cell",
@@ -3676,11 +3696,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.56"
version = "0.4.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
dependencies = [
"cfg-if",
"futures-util",
"js-sys",
"once_cell",
"wasm-bindgen",
@@ -3689,9 +3710,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.106"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3699,9 +3720,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.106"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -3712,18 +3733,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.106"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.83"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3983,15 +4004,6 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -4028,21 +4040,6 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -4085,12 +4082,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -4103,12 +4094,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
@@ -4121,12 +4106,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -4151,12 +4130,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
@@ -4169,12 +4142,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
@@ -4187,12 +4154,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
@@ -4205,12 +4166,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
@@ -4234,9 +4189,9 @@ dependencies = [
[[package]]
name = "wit-bindgen"
version = "0.46.0"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
[[package]]
name = "writeable"
@@ -4275,18 +4230,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.33"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.33"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0"
dependencies = [
"proc-macro2",
"quote",
@@ -4355,6 +4310,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.12"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "managarr"
version = "0.6.3"
version = "0.7.0"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI and CLI to manage your Servarrs"
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
@@ -24,7 +24,7 @@ anyhow = "1.0.68"
backtrace = "0.3.74"
bimap = { version = "0.6.3", features = ["serde"] }
chrono = { version = "0.4.38", features = ["serde"] }
confy = { version = "0.6.0", default-features = false, features = [
confy = { version = "2.0.0", default-features = false, features = [
"yaml_conf",
] }
crossterm = "0.28.1"
+43 -45
View File
@@ -259,6 +259,8 @@ Commands:
lidarr Commands for manging your Lidarr instance
completions Generate shell completions for the Managarr CLI
tail-logs Tail Managarr logs
config-path Print the full path to the default configuration file.
This file can be changed to another location using the '--config-file' flag
help Print this message or the help of the given subcommand(s)
Options:
@@ -266,14 +268,23 @@ Options:
-V, --version Print version
Global Options:
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
--disable-spinner Disable the spinner (can sometimes make parsing output
challenging) [env: MANAGARR_DISABLE_SPINNER=]
--config-file <CONFIG_FILE> The Managarr configuration file to use; defaults to the
path shown by 'managarr config-path' [env:
MANAGARR_CONFIG_FILE=]
--themes-file <THEMES_FILE> The Managarr themes file to use [env:
MANAGARR_THEMES_FILE=]
--theme <THEME> The name of the Managarr theme to use [env:
MANAGARR_THEME=]
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify
the name of the instance configuration that you want to
use.
This is useful when you have multiple instances of the same Servarr defined in your config file.
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
This is useful when you have multiple instances of the
same Servarr defined in your config file.
By default, if left empty, the first configured Servarr
instance listed in the config file will be used.
```
All subcommands also have detailed help menus to show you how to use them. For example, to see all available commands for Sonarr, you would run:
@@ -330,21 +341,11 @@ $ managarr radarr list movies | jq '.[] | select(.title == "Ad Astra") | .id'
Managarr assumes reasonable defaults to connect to each service (i.e. Radarr is on localhost:7878),
but all servers will require you to input the API token.
The configuration file is located somewhere different for each OS.
The configuration file is located somewhere different for each OS, so run the following command to print out the default
location of the `managarr` configuration file for your system:
### Linux
```
$HOME/.config/managarr/config.yml
```
### Mac
```
$HOME/Library/Application Support/managarr/config.yml
```
### Windows
```
%APPDATA%/Roaming/managarr/config.yml
```shell
managarr config-path
```
## Specify Which Configuration File to Use
@@ -364,42 +365,39 @@ radarr:
port: 7878
api_token: someApiToken1234567890
ssl_cert_path: /path/to/radarr.crt # Required to enable SSL
sonarr:
- uri: http://htpc.local/sonarr # Example of using the 'uri' key instead of 'host' and 'port'
api_token: someApiToken1234567890
- uri: http://htpc.local/radarr # Example of using the 'uri' key instead of 'host' and 'port'
api_token: someApiToken1234567890
sonarr:
- host: 192.168.0.89
port: 8989
api_token_file: /root/.config/sonarr_api_token # Example of loading the API token from a file instead of hardcoding it in the configuration file
- name: Anime Sonarr # An example of a custom name for a secondary Sonarr instance
host: 192.168.0.89
host: 192.168.1.89
port: 8989
api_token: someApiToken1234567890
readarr:
- host: 192.168.0.87
port: 8787
api_token_file: /root/.config/readarr_api_token # Example of loading the API token from a file instead of hardcoding it in the configuration file
lidarr:
- host: 192.168.0.86
port: 8686
api_token: ${MY_LIDARR_API_TOKEN} # Example of configuring using environment variables
whisparr:
- host: 192.168.0.69
port: 6969
monitored_storage_paths: # Filter which Root Folders or Disk Storage you want displayed in the UI's 'Stats' block
# Note: Setting these values does not affect what shows up in the 'Root Folders' tab of the UI.
- /nfs # An example disk (i.e. '<servarr> list disk-space' command) you want displayed in the UI under 'Storage:'
- /media # An example root folder you want displayed in the UI
# Root folders collapse up to the super-directory to reduce duplication in the UI. For example:
# if you have root folders '/media/tv', '/media/cartoons' and '/media/reality', and you set this
# monitored path, the UI will show '/media/[tv,cartoons,reality]' under Root Folders
- host: 192.168.1.86
port: 8686
api_token: someApiToken1234567890
ssl_cert_path: /path/to/whisparr.crt
ssl_cert_path: /path/to/lidarr_1.crt
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
traefik-auth-bypass-key: someBypassKey1234567890
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
bazarr:
- host: 192.168.0.67
port: 6767
api_token: someApiToken1234567890
prowlarr:
- host: 192.168.0.96
port: 9696
api_token: someApiToken1234567890
tautulli:
- host: 192.168.0.81
port: 8181
api_token: someApiToken1234567890
```
### Example Multi-Instance Configuration:
+53 -1
View File
@@ -507,6 +507,56 @@ mod tests {
assert_none!(config.custom_headers);
}
#[test]
#[serial]
fn test_deserialize_optional_env_var_string_vec_is_present() {
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_STRING_VEC_OPTION", "/path1") };
let expected_monitored_paths = ["/path1", "/path2"];
let yaml_data = r#"
monitored_storage_paths:
- ${TEST_VAR_DESERIALIZE_STRING_VEC_OPTION}
- /path2
"#;
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_some_eq_x!(&config.monitored_storage_paths, &expected_monitored_paths);
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_STRING_VEC_OPTION") };
}
#[test]
#[serial]
fn test_deserialize_optional_env_var_string_vec_does_not_overwrite_non_env_value() {
unsafe {
std::env::set_var(
"TEST_VAR_DESERIALIZE_STRING_VEC_OPTION_NO_OVERWRITE",
"/path3",
)
};
let expected_monitored_paths = ["/path1", "/path2"];
let yaml_data = r#"
monitored_storage_paths:
- /path1
- /path2
"#;
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_some_eq_x!(&config.monitored_storage_paths, &expected_monitored_paths);
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_STRING_VEC_OPTION_NO_OVERWRITE") };
}
#[test]
fn test_deserialize_optional_env_var_string_vec_empty() {
let yaml_data = r#"
api_token: "test123"
"#;
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_none!(config.monitored_storage_paths);
}
#[test]
#[serial]
fn test_deserialize_optional_u16_env_var_is_present() {
@@ -620,10 +670,11 @@ mod tests {
let api_token = "thisisatest".to_owned();
let api_token_file = "/root/.config/api_token".to_owned();
let ssl_cert_path = "/some/path".to_owned();
let monitored_storage = vec!["/path1".to_owned(), "/path2".to_owned()];
let mut custom_headers = HeaderMap::new();
custom_headers.insert("X-Custom-Header", "value".parse().unwrap());
let expected_str = format!(
"ServarrConfig {{ name: Some(\"{name}\"), host: Some(\"{host}\"), port: Some({port}), uri: Some(\"{uri}\"), weight: Some({weight}), api_token: Some(\"***********\"), api_token_file: Some(\"{api_token_file}\"), ssl_cert_path: Some(\"{ssl_cert_path}\"), custom_headers: Some({{\"x-custom-header\": \"value\"}}) }}"
"ServarrConfig {{ name: Some(\"{name}\"), host: Some(\"{host}\"), port: Some({port}), uri: Some(\"{uri}\"), weight: Some({weight}), api_token: Some(\"***********\"), api_token_file: Some(\"{api_token_file}\"), ssl_cert_path: Some(\"{ssl_cert_path}\"), custom_headers: Some({{\"x-custom-header\": \"value\"}}), monitored_storage_paths: Some([\"/path1\", \"/path2\"]) }}"
);
let servarr_config = ServarrConfig {
name: Some(name),
@@ -635,6 +686,7 @@ mod tests {
api_token_file: Some(api_token_file),
ssl_cert_path: Some(ssl_cert_path),
custom_headers: Some(custom_headers),
monitored_storage_paths: Some(monitored_storage),
};
assert_str_eq!(format!("{servarr_config:?}"), expected_str);
+25 -4
View File
@@ -346,11 +346,11 @@ pub struct AppConfig {
}
impl AppConfig {
pub fn validate(&self) {
pub fn validate(&self, config_path: &str) {
if self.lidarr.is_none() && self.radarr.is_none() && self.sonarr.is_none() {
log_and_print_error(
"No Servarr configuration provided in the specified configuration file".to_owned(),
);
log_and_print_error(format!(
"No Servarrs are configured in the file: {config_path}"
));
process::exit(1);
}
@@ -436,6 +436,8 @@ pub struct ServarrConfig {
serialize_with = "serialize_header_map"
)]
pub custom_headers: Option<HeaderMap>,
#[serde(default, deserialize_with = "deserialize_optional_env_var_string_vec")]
pub monitored_storage_paths: Option<Vec<String>>,
}
impl ServarrConfig {
@@ -482,6 +484,7 @@ impl Default for ServarrConfig {
api_token_file: None,
ssl_cert_path: None,
custom_headers: None,
monitored_storage_paths: None,
}
}
}
@@ -548,6 +551,24 @@ where
}
}
fn deserialize_optional_env_var_string_vec<'de, D>(
deserializer: D,
) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt: Option<Vec<String>> = Option::deserialize(deserializer)?;
match opt {
Some(vec) => Ok(Some(
vec
.into_iter()
.map(|it| interpolate_env_vars(&it))
.collect(),
)),
None => Ok(None),
}
}
fn deserialize_u16_env_var<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: serde::Deserializer<'de>,
+9
View File
@@ -59,6 +59,8 @@ pub enum LidarrListCommand {
Artists,
#[command(about = "List all items in the Lidarr blocklist")]
Blocklist,
#[command(about = "List disk space details for all provisioned root folders in Lidarr")]
DiskSpace,
#[command(about = "List all active downloads in Lidarr")]
Downloads {
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
@@ -209,6 +211,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::DiskSpace => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetDiskSpace.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Downloads { count } => {
let resp = self
.network
@@ -28,6 +28,7 @@ mod tests {
#[values(
"artists",
"blocklist",
"disk-space",
"indexers",
"metadata-profiles",
"quality-profiles",
@@ -435,6 +436,7 @@ mod tests {
#[rstest]
#[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)]
#[case(LidarrListCommand::Blocklist, LidarrEvent::GetBlocklist)]
#[case(LidarrListCommand::DiskSpace, LidarrEvent::GetDiskSpace)]
#[case(LidarrListCommand::Indexers, LidarrEvent::GetIndexers)]
#[case(LidarrListCommand::MetadataProfiles, LidarrEvent::GetMetadataProfiles)]
#[case(LidarrListCommand::QualityProfiles, LidarrEvent::GetQualityProfiles)]
+7
View File
@@ -3,6 +3,7 @@ use std::sync::Arc;
use anyhow::Result;
use clap::{Subcommand, command};
use clap_complete::Shell;
use indoc::indoc;
use lidarr::{LidarrCliHandler, LidarrCommand};
use radarr::{RadarrCliHandler, RadarrCommand};
use sonarr::{SonarrCliHandler, SonarrCommand};
@@ -43,6 +44,12 @@ pub enum Command {
#[arg(long, help = "Disable colored log output")]
no_color: bool,
},
#[command(about = indoc!{"
Print the full path to the default configuration file.
This file can be changed to another location using the '--config-file' flag
"})]
ConfigPath,
}
pub trait CliCommandHandler<'a, 'b, T: Into<Command>> {
-2
View File
@@ -36,8 +36,6 @@ pub const READARR_LOGO: &str = "⠀⠀⠀⠀⠀⣀⣠⣤⣄⣀⠀⠀⠀⠀⠀
⠀⠀⠈⠳⣬⣙⠻⠿⠟⣋⣥⠞⠁⠀⠀
⠀⠀⠀⠀⠀⠉⠙⠛⠋⠉⠀⠀⠀⠀⠀
";
// Allowing this code for now since we'll eventually be implementing additional Servarr support and we'll need it then
#[allow(dead_code)]
pub const LIDARR_LOGO: &str = "⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀
⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀
⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄
+22 -6
View File
@@ -2,7 +2,7 @@
#[macro_use]
extern crate assertables;
use anyhow::Result;
use anyhow::{Context, Result};
use clap::{
Args, CommandFactory, Parser, crate_authors, crate_description, crate_name, crate_version,
};
@@ -86,7 +86,7 @@ struct GlobalOpts {
global = true,
value_parser,
env = "MANAGARR_CONFIG_FILE",
help = "The Managarr configuration file to use"
help = "The Managarr configuration file to use; defaults to the path shown by 'managarr config-path'"
)]
config_file: Option<PathBuf>,
#[arg(
@@ -127,15 +127,30 @@ async fn main() -> Result<()> {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
let args = Cli::parse();
let mut config = if let Some(ref config_file) = args.global.config_file {
load_config(config_file.to_str().expect("Invalid config file specified"))?
let config_file_path = confy::get_configuration_file_path("managarr", "config")?;
let default_config_path = config_file_path.display().to_string();
if matches!(args.command, Some(Command::ConfigPath)) {
println!("{default_config_path}");
return Ok(());
}
let (mut config, config_path) = if let Some(ref config_file) = args.global.config_file {
(
load_config(config_file.to_str().expect("Invalid config file specified"))?,
config_file.display().to_string(),
)
} else {
confy::load("managarr", "config")?
(
confy::load("managarr", "config")
.with_context(|| format!("Config file at '{default_config_path}' is invalid"))?,
default_config_path,
)
};
let theme_name = config.theme.clone();
let spinner_disabled = args.global.disable_spinner;
debug!("Managarr loaded using config: {config:?}");
config.validate();
config.validate(&config_path);
config.post_process_initialization();
let reqwest_client = build_network_client(&config);
@@ -170,6 +185,7 @@ async fn main() -> Result<()> {
generate(shell, &mut cli, "managarr", &mut io::stdout())
}
Command::TailLogs { no_color } => tail_logs(no_color).await?,
_ => {}
},
None => {
let app_nw = Arc::clone(&app);
+1
View File
@@ -296,6 +296,7 @@ mod tests {
#[test]
fn test_lidarr_serdeable_from_disk_spaces() {
let disk_spaces = vec![DiskSpace {
path: Some("/path".to_owned()),
free_space: 1,
total_space: 1,
}];
+1
View File
@@ -233,6 +233,7 @@ mod tests {
#[test]
fn test_radarr_serdeable_from_disk_spaces() {
let disk_spaces = vec![DiskSpace {
path: Some("/path".to_owned()),
free_space: 1,
total_space: 1,
}];
+1
View File
@@ -83,6 +83,7 @@ pub struct CommandBody {
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DiskSpace {
pub path: Option<String>,
#[serde(deserialize_with = "super::from_i64")]
pub free_space: i64,
#[serde(deserialize_with = "super::from_i64")]
+1
View File
@@ -427,6 +427,7 @@ mod tests {
#[test]
fn test_sonarr_serdeable_from_disk_spaces() {
let disk_spaces = vec![DiskSpace {
path: Some("/path".to_owned()),
free_space: 1,
total_space: 1,
}];
@@ -16,21 +16,34 @@ mod tests {
async fn test_handle_get_diskspace_event() {
let diskspace_json = json!([
{
"path": "/path1",
"freeSpace": 1111,
"totalSpace": 2222,
},
{
"path": "/path2",
"freeSpace": 3333,
"totalSpace": 4444
}
]);
let response: Vec<DiskSpace> = serde_json::from_value(diskspace_json.clone()).unwrap();
let (mock, app, _server) = MockServarrApi::get()
.returns(diskspace_json)
.build_for(LidarrEvent::GetDiskSpace)
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
let disk_space_vec = vec![
DiskSpace {
path: Some("/path1".to_owned()),
free_space: 1111,
total_space: 2222,
},
DiskSpace {
path: Some("/path2".to_owned()),
free_space: 3333,
total_space: 4444,
},
];
let result = network.handle_lidarr_event(LidarrEvent::GetDiskSpace).await;
@@ -40,8 +53,11 @@ mod tests {
panic!("Expected DiskSpaces");
};
assert_eq!(disk_spaces, response);
assert!(!app.lock().await.data.lidarr_data.disk_space_vec.is_empty());
assert_eq!(
app.lock().await.data.lidarr_data.disk_space_vec,
disk_space_vec
);
assert_eq!(disk_spaces, disk_space_vec);
}
#[tokio::test]
+1
View File
@@ -862,6 +862,7 @@ pub(in crate::network) mod test_utils {
host,
port,
api_token: Some("test1234".to_owned()),
monitored_storage_paths: Some(vec!["/path1".to_owned()]),
..ServarrConfig::default()
};
@@ -17,10 +17,12 @@ mod tests {
let (mock, app, _server) = MockServarrApi::get()
.returns(json!([
{
"path": "/path1",
"freeSpace": 1111,
"totalSpace": 2222,
},
{
"path": "/path2",
"freeSpace": 3333,
"totalSpace": 4444
}
@@ -30,10 +32,12 @@ mod tests {
let mut network = test_network(&app);
let disk_space_vec = vec![
DiskSpace {
path: Some("/path1".to_owned()),
free_space: 1111,
total_space: 2222,
},
DiskSpace {
path: Some("/path2".to_owned()),
free_space: 3333,
total_space: 4444,
},
+1
View File
@@ -4,6 +4,7 @@ use chrono::DateTime;
pub fn diskspace() -> DiskSpace {
DiskSpace {
path: Some("/path".to_owned()),
free_space: 6500,
total_space: 8675309,
}
@@ -113,10 +113,12 @@ mod tests {
let (mock, app, _server) = MockServarrApi::get()
.returns(json!([
{
"path": "/path1",
"freeSpace": 1111,
"totalSpace": 2222,
},
{
"path": "/path2",
"freeSpace": 3333,
"totalSpace": 4444
}
@@ -127,10 +129,12 @@ mod tests {
let mut network = test_network(&app);
let disk_space_vec = vec![
DiskSpace {
path: Some("/path1".to_owned()),
free_space: 1111,
total_space: 2222,
},
DiskSpace {
path: Some("/path2".to_owned()),
free_space: 3333,
total_space: 4444,
},
+17 -10
View File
@@ -1,5 +1,3 @@
use std::{cmp, iter};
#[cfg(test)]
use crate::ui::ui_test_utils::test_utils::Utc;
use chrono::Duration;
@@ -14,6 +12,7 @@ use ratatui::{
text::Text,
widgets::Paragraph,
};
use std::{cmp, iter};
use super::{
DrawUi, draw_tabs,
@@ -28,6 +27,7 @@ use crate::ui::lidarr_ui::downloads::DownloadsUi;
use crate::ui::lidarr_ui::indexers::IndexersUi;
use crate::ui::lidarr_ui::root_folders::RootFoldersUi;
use crate::ui::lidarr_ui::system::SystemUi;
use crate::ui::utils::{extract_monitored_disk_space_vec, extract_monitored_root_folders};
use crate::{
app::App,
logos::LIDARR_LOGO,
@@ -100,6 +100,8 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
start_time,
..
} = &app.data.lidarr_data;
let monitored_disk_space_vec = extract_monitored_disk_space_vec(app, disk_space_vec.clone());
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
let mut constraints = vec![
Constraint::Length(1),
@@ -110,7 +112,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
constraints.append(
&mut iter::repeat_n(
Constraint::Length(1),
disk_space_vec.len() + root_folders.items.len() + 1,
monitored_disk_space_vec.len() + monitored_root_folders.len() + 1,
)
.collect(),
);
@@ -146,12 +148,17 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(uptime_paragraph, stat_item_areas[1]);
f.render_widget(storage, stat_item_areas[2]);
for i in 0..disk_space_vec.len() {
for i in 0..monitored_disk_space_vec.len() {
let DiskSpace {
path,
free_space,
total_space,
} = &disk_space_vec[i];
let title = format!("Disk {}", i + 1);
} = &monitored_disk_space_vec[i];
let title = if let Some(path) = path {
path
} else {
&format!("Disk {}", i + 1)
};
let ratio = if *total_space == 0 {
0f64
} else {
@@ -163,12 +170,12 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(space_gauge, stat_item_areas[i + 3]);
}
f.render_widget(folders, stat_item_areas[disk_space_vec.len() + 3]);
f.render_widget(folders, stat_item_areas[monitored_disk_space_vec.len() + 3]);
for i in 0..root_folders.items.len() {
for i in 0..monitored_root_folders.len() {
let RootFolder {
path, free_space, ..
} = &root_folders.items[i];
} = &monitored_root_folders[i];
let space: f64 = convert_to_gb(*free_space);
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
.block(borderless_block())
@@ -176,7 +183,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(
root_folder_space,
stat_item_areas[i + disk_space_vec.len() + 4],
stat_item_areas[i + monitored_disk_space_vec.len() + 4],
)
}
} else {
+29 -21
View File
@@ -1,15 +1,3 @@
#[cfg(test)]
use crate::ui::ui_test_utils::test_utils::Utc;
use chrono::Duration;
#[cfg(not(test))]
use chrono::Utc;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::prelude::Stylize;
use ratatui::text::Text;
use ratatui::widgets::{Paragraph, Row};
use std::{cmp, iter};
use crate::app::App;
use crate::logos::RADARR_LOGO;
use crate::models::Route;
@@ -27,11 +15,23 @@ use crate::ui::radarr_ui::library::LibraryUi;
use crate::ui::radarr_ui::root_folders::RootFoldersUi;
use crate::ui::radarr_ui::system::SystemUi;
use crate::ui::styles::ManagarrStyle;
#[cfg(test)]
use crate::ui::ui_test_utils::test_utils::Utc;
use crate::ui::utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
borderless_block, extract_monitored_disk_space_vec, extract_monitored_root_folders, layout_block,
line_gauge_with_label, line_gauge_with_title, title_block,
};
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::utils::convert_to_gb;
use chrono::Duration;
#[cfg(not(test))]
use chrono::Utc;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::prelude::Stylize;
use ratatui::text::Text;
use ratatui::widgets::{Paragraph, Row};
use std::{cmp, iter};
mod blocklist;
mod collections;
@@ -93,6 +93,8 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
start_time,
..
} = &app.data.radarr_data;
let monitored_disk_space_vec = extract_monitored_disk_space_vec(app, disk_space_vec.clone());
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
let mut constraints = vec![
Constraint::Length(1),
@@ -103,7 +105,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
constraints.append(
&mut iter::repeat_n(
Constraint::Length(1),
disk_space_vec.len() + root_folders.items.len() + 1,
monitored_disk_space_vec.len() + monitored_root_folders.len() + 1,
)
.collect(),
);
@@ -139,12 +141,17 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(uptime_paragraph, stat_item_areas[1]);
f.render_widget(storage, stat_item_areas[2]);
for i in 0..disk_space_vec.len() {
for i in 0..monitored_disk_space_vec.len() {
let DiskSpace {
path,
free_space,
total_space,
} = &disk_space_vec[i];
let title = format!("Disk {}", i + 1);
} = &monitored_disk_space_vec[i];
let title = if let Some(path) = path {
path
} else {
&format!("Disk {}", i + 1)
};
let ratio = if *total_space == 0 {
0f64
} else {
@@ -156,12 +163,13 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(space_gauge, stat_item_areas[i + 3]);
}
f.render_widget(folders, stat_item_areas[disk_space_vec.len() + 3]);
f.render_widget(folders, stat_item_areas[monitored_disk_space_vec.len() + 3]);
for i in 0..root_folders.items.len() {
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
for i in 0..monitored_root_folders.len() {
let RootFolder {
path, free_space, ..
} = &root_folders.items[i];
} = &monitored_root_folders[i];
let space: f64 = convert_to_gb(*free_space);
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
.block(borderless_block())
@@ -169,7 +177,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(
root_folder_space,
stat_item_areas[i + disk_space_vec.len() + 4],
stat_item_areas[i + monitored_disk_space_vec.len() + 4],
)
}
} else {
@@ -9,7 +9,7 @@ expression: output
│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀ │
│Storage: ││ ││ ⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │
│Root Folders: ││ ││ ⢿⣧⠀⠈⠀⠀⠀⠀⠀⠀⢀⡟⠀⣸⡿ │
│/nfs: 204800.00 GB free ││ ││ ⠘⣿⣄⠀⠻⣿⣿⡇⠀⢀⠞⠀⣠⣿⠃ │
│ ││ ││ ⠀⠘⢿⣦⣄⠀⠉⠁⠀⠀⣠⣴⡿⠃⠀ │
@@ -9,7 +9,7 @@ expression: output
│Lidarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
│Uptime: 0d 00:00:44 │ Key Alt Key Description │━━━━━━━━━━━━━━━━━││ ⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀ │
│Storage: │=> a add │ ││ ⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │
│Root Folders: │ m toggle monitoring │ ││ ⢿⣧⠀⠈⠀⠀⠀⠀⠀⠀⢀⡟⠀⣸⡿ │
│/nfs: 204800.00 GB free │ o sort │ ││ ⠘⣿⣄⠀⠻⣿⣿⡇⠀⢀⠞⠀⣠⣿⠃ │
│ │ del delete │ ││ ⠀⠘⢿⣦⣄⠀⠉⠁⠀⠀⣠⣴⡿⠃⠀ │
@@ -12,7 +12,7 @@ expression: output
│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⢠⣾⠟⠋⠀⠀⢀⣀⠀⠙⠻⣷⡄⠀ │
│Storage: ││ ││ ⢠⣿⠋⠀⣴⠃⠀⢸⣿⣿⣦⡀⠙⣿⡄ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣾⡟⠀⢸⠃⠀⠀⠀⠈⠉⠉⠁⠀⢹⣷ │
│Root Folders: ││ ││ ⢿⣧⠀⠈⠀⠀⠀⠀⠀⠀⢀⡟⠀⣸⡿ │
│/nfs: 204800.00 GB free ││ ││ ⠘⣿⣄⠀⠻⣿⣿⡇⠀⢀⠞⠀⣠⣿⠃ │
│ ││ ││ ⠀⠘⢿⣦⣄⠀⠉⠁⠀⠀⣠⣴⡿⠃⠀ │
@@ -9,7 +9,7 @@ expression: output
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⣿⡇⠀⠈⠙⠻⢿⣶⣤⡀⠀⠀⠀⠀ │
│Storage: ││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠈⠙⠻⢷⣦⡄⠀ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠻⠀ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠻⠀ │
│Root Folders: ││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⢀⣠⣴⣾⠿⠀⠀ │
│/nfs: 204800.00 GB free ││ ││ ⠀⢿⡇⠀⠀⣀⣤⣶⡿⠛⠉⠀⠀⠀⠀ │
│ ││ ││ ⠀⠀⠰⠶⡿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀ │
@@ -9,7 +9,7 @@ expression: output
│Radarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
│Uptime: 0d 00:00:44 │ Key Alt Key Description │━━━━━━━━━━━━━━━━━││ ⠀⣿⡇⠀⠈⠙⠻⢿⣶⣤⡀⠀⠀⠀⠀ │
│Storage: │=> a add │ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠈⠙⠻⢷⣦⡄⠀ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠻⠀ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠻⠀ │
│Root Folders: │ m toggle monitoring │ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⢀⣠⣴⣾⠿⠀⠀ │
│/nfs: 204800.00 GB free │ o sort │ ││ ⠀⢿⡇⠀⠀⣀⣤⣶⡿⠛⠉⠀⠀⠀⠀ │
│ │ del delete │ ││ ⠀⠀⠰⠶⡿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀ │
@@ -12,7 +12,7 @@ expression: output
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⣿⡇⠀⠈⠙⠻⢿⣶⣤⡀⠀⠀⠀⠀ │
│Storage: ││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠈⠙⠻⢷⣦⡄⠀ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠻⠀ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠻⠀ │
│Root Folders: ││ ││ ⠀⣿⡇⠀⠀⠀⠀⠀⢀⣠⣴⣾⠿⠀⠀ │
│/nfs: 204800.00 GB free ││ ││ ⠀⢿⡇⠀⠀⣀⣤⣶⡿⠛⠉⠀⠀⠀⠀ │
│ ││ ││ ⠀⠀⠰⠶⡿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀ │
@@ -9,7 +9,7 @@ expression: output
│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⣠⡀⠈⠻⣿⣿⣿⣿⣿⣿⠟⠁⢀⣀⠀ │
│Storage: ││ ││ ⢰⣿⣿⣦⠐⠄⠉⠉⠉⠉⠠⠂⣰⣿⣿⡆ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │
│Root Folders: ││ ││ ⣿⣿⣿⣿⡇⠀⠻⣿⣿⠟⠀⠸⣿⣿⣿⣿ │
│/nfs: 204800.00 GB free ││ ││ ⠸⣿⣿⠟⠠⠂⠀⢀⡀⠀⠐⠄⠻⣿⣿⠇ │
│ ││ ││ ⠀⠙⠁⢀⣴⣾⣿⣿⣿⣿⣷⣦⡀⠈⠋⠀ │
@@ -9,7 +9,7 @@ expression: output
│Sonarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
│Uptime: 0d 00:00:44 │ Key Alt Key Description │━━━━━━━━━━━━━━━━━││ ⠀⣠⡀⠈⠻⣿⣿⣿⣿⣿⣿⠟⠁⢀⣀⠀ │
│Storage: │=> a add │ ││ ⢰⣿⣿⣦⠐⠄⠉⠉⠉⠉⠠⠂⣰⣿⣿⡆ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━│ e edit │ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │
│Root Folders: │ m toggle monitoring │ ││ ⣿⣿⣿⣿⡇⠀⠻⣿⣿⠟⠀⠸⣿⣿⣿⣿ │
│/nfs: 204800.00 GB free │ o sort │ ││ ⠸⣿⣿⠟⠠⠂⠀⢀⡀⠀⠐⠄⠻⣿⣿⠇ │
│ │ del delete │ ││ ⠀⠙⠁⢀⣴⣾⣿⣿⣿⣿⣷⣦⡀⠈⠋⠀ │
@@ -12,7 +12,7 @@ expression: output
│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
│Uptime: 0d 00:00:44 ││50% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ⠀⣠⡀⠈⠻⣿⣿⣿⣿⣿⣿⠟⠁⢀⣀⠀ │
│Storage: ││ ││ ⢰⣿⣿⣦⠐⠄⠉⠉⠉⠉⠠⠂⣰⣿⣿⡆ │
Disk 1: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │
/path: 100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━││ ││ ⣿⣿⣿⣿⡆⠀⣴⣿⣿⣦⠀⢰⣿⣿⣿⣿ │
│Root Folders: ││ ││ ⣿⣿⣿⣿⡇⠀⠻⣿⣿⠟⠀⠸⣿⣿⣿⣿ │
│/nfs: 204800.00 GB free ││ ││ ⠸⣿⣿⠟⠠⠂⠀⢀⡀⠀⠐⠄⠻⣿⣿⠇ │
│ ││ ││ ⠀⠙⠁⢀⣴⣾⣿⣿⣿⣿⣷⣦⡀⠈⠋⠀ │
+26 -19
View File
@@ -1,5 +1,3 @@
use std::{cmp, iter};
#[cfg(test)]
use crate::ui::ui_test_utils::test_utils::Utc;
use blocklist::BlocklistUi;
@@ -18,8 +16,18 @@ use ratatui::{
widgets::Paragraph,
};
use root_folders::RootFoldersUi;
use std::{cmp, iter};
use system::SystemUi;
use super::{
DrawUi, draw_tabs,
styles::ManagarrStyle,
utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
},
widgets::loading_block::LoadingBlock,
};
use crate::ui::utils::{extract_monitored_disk_space_vec, extract_monitored_root_folders};
use crate::{
app::App,
logos::SONARR_LOGO,
@@ -32,15 +40,6 @@ use crate::{
utils::convert_to_gb,
};
use super::{
DrawUi, draw_tabs,
styles::ManagarrStyle,
utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
},
widgets::loading_block::LoadingBlock,
};
mod blocklist;
mod downloads;
mod history;
@@ -101,6 +100,8 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
start_time,
..
} = &app.data.sonarr_data;
let monitored_disk_space_vec = extract_monitored_disk_space_vec(app, disk_space_vec.clone());
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
let mut constraints = vec![
Constraint::Length(1),
@@ -111,7 +112,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
constraints.append(
&mut iter::repeat_n(
Constraint::Length(1),
disk_space_vec.len() + root_folders.items.len() + 1,
monitored_disk_space_vec.len() + monitored_root_folders.len() + 1,
)
.collect(),
);
@@ -147,12 +148,18 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(uptime_paragraph, stat_item_areas[1]);
f.render_widget(storage, stat_item_areas[2]);
for i in 0..disk_space_vec.len() {
for i in 0..monitored_disk_space_vec.len() {
let DiskSpace {
path,
free_space,
total_space,
} = &disk_space_vec[i];
let title = format!("Disk {}", i + 1);
..
} = &monitored_disk_space_vec[i];
let title = if let Some(path) = path {
path
} else {
&format!("Disk {}", i + 1)
};
let ratio = if *total_space == 0 {
0f64
} else {
@@ -164,12 +171,12 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(space_gauge, stat_item_areas[i + 3]);
}
f.render_widget(folders, stat_item_areas[disk_space_vec.len() + 3]);
f.render_widget(folders, stat_item_areas[monitored_disk_space_vec.len() + 3]);
for i in 0..root_folders.items.len() {
for i in 0..monitored_root_folders.len() {
let RootFolder {
path, free_space, ..
} = &root_folders.items[i];
} = &monitored_root_folders[i];
let space: f64 = convert_to_gb(*free_space);
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
.block(borderless_block())
@@ -177,7 +184,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(
root_folder_space,
stat_item_areas[i + disk_space_vec.len() + 4],
stat_item_areas[i + monitored_disk_space_vec.len() + 4],
)
}
} else {
+121
View File
@@ -1,3 +1,5 @@
use crate::app::App;
use crate::models::servarr_models::{DiskSpace, RootFolder};
use crate::ui::THEME;
use crate::ui::styles::{
ManagarrStyle, default_style, failure_style, primary_style, secondary_style,
@@ -7,6 +9,8 @@ use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, BorderType, Borders, LineGauge, ListItem, Paragraph, Wrap};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
#[cfg(test)]
#[path = "utils_tests.rs"]
@@ -179,3 +183,120 @@ pub(super) fn decorate_peer_style(seeders: u64, leechers: u64, text: Text<'_>) -
text.success()
}
}
pub(super) fn extract_monitored_root_folders(
app: &App<'_>,
root_folders: Vec<RootFolder>,
) -> Vec<RootFolder> {
let monitored_paths = app
.server_tabs
.get_active_config()
.as_ref()
.unwrap()
.monitored_storage_paths
.as_ref();
if let Some(monitored_paths) = monitored_paths
&& !monitored_paths.is_empty()
{
let monitored_paths: Vec<PathBuf> = monitored_paths.iter().map(PathBuf::from).collect();
let mut collapsed_folders: HashMap<PathBuf, (RootFolder, Vec<String>)> = HashMap::new();
let mut unmatched_folders: Vec<RootFolder> = Vec::new();
for root_folder in root_folders {
let root_path = Path::new(&root_folder.path);
let matching_monitored_path = monitored_paths
.iter()
.filter(|mp| root_path.starts_with(mp))
.max_by_key(|mp| mp.components().count());
if let Some(monitored_path) = matching_monitored_path {
let subfolder_name = root_path
.strip_prefix(monitored_path)
.ok()
.and_then(|p| p.components().next())
.map(|c| c.as_os_str().to_string_lossy().to_string())
.unwrap_or_default();
collapsed_folders
.entry(monitored_path.clone())
.and_modify(|(_, subfolders)| {
if !subfolder_name.is_empty() && !subfolders.contains(&subfolder_name) {
subfolders.push(subfolder_name.clone());
}
})
.or_insert_with(|| {
let subfolders = if subfolder_name.is_empty() {
vec![]
} else {
vec![subfolder_name]
};
(root_folder.clone(), subfolders)
});
} else {
unmatched_folders.push(root_folder);
}
}
let mut result: Vec<RootFolder> = collapsed_folders
.into_iter()
.map(|(monitored_path, (mut root_folder, mut subfolders))| {
subfolders.sort();
let path_str = monitored_path.to_string_lossy();
root_folder.path = if subfolders.is_empty() {
path_str.to_string()
} else {
format!(
"{}/[{}]",
path_str.trim_end_matches('/'),
subfolders.join(",")
)
};
root_folder
})
.collect();
result.extend(unmatched_folders);
result.sort_by(|a, b| a.path.cmp(&b.path));
result
} else {
root_folders
}
}
pub(super) fn extract_monitored_disk_space_vec(
app: &App<'_>,
disk_space_vec: Vec<DiskSpace>,
) -> Vec<DiskSpace> {
let monitored_paths = app
.server_tabs
.get_active_config()
.as_ref()
.unwrap()
.monitored_storage_paths
.as_ref();
if let Some(monitored_paths) = monitored_paths
&& !monitored_paths.is_empty()
{
let monitored: HashSet<&str> = monitored_paths.iter().map(|s| s.as_str()).collect();
let mut seen_paths = HashSet::new();
let mut filtered_disk_space_vec = Vec::with_capacity(disk_space_vec.len());
for ds in disk_space_vec {
match ds.path.as_deref() {
None => filtered_disk_space_vec.push(ds),
Some(p) => {
if monitored.contains(p) && seen_paths.insert(p.to_owned()) {
filtered_disk_space_vec.push(ds)
}
}
}
}
filtered_disk_space_vec
} else {
disk_space_vec
}
}
+285 -1
View File
@@ -1,9 +1,12 @@
#[cfg(test)]
mod test {
use crate::app::{App, ServarrConfig};
use crate::models::servarr_models::{DiskSpace, RootFolder};
use crate::ui::styles::{ManagarrStyle, default_style, failure_style, secondary_style};
use crate::ui::utils::{
borderless_block, centered_rect, convert_to_minutes_hours_days, decorate_peer_style,
get_width_from_percentage, layout_block, layout_block_bottom_border, layout_block_top_border,
extract_monitored_disk_space_vec, extract_monitored_root_folders, get_width_from_percentage,
layout_block, layout_block_bottom_border, layout_block_top_border,
layout_block_top_border_with_title, layout_block_with_title, logo_block, style_block_highlight,
style_log_list_item, title_block, title_block_centered, title_style, unstyled_title_block,
};
@@ -278,6 +281,287 @@ mod test {
}
}
#[test]
fn test_extract_monitored_root_folders_collapses_subfolders() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(vec!["/nfs".to_owned()]),
..ServarrConfig::default()
});
let root_folders = vec![
RootFolder {
id: 1,
path: "/nfs/cartoons".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
},
RootFolder {
id: 2,
path: "/nfs/tv".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
},
RootFolder {
id: 3,
path: "/nfs/reality".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
},
];
let monitored_root_folders = extract_monitored_root_folders(&app, root_folders);
assert_eq!(monitored_root_folders.len(), 1);
assert_eq!(monitored_root_folders[0].path, "/nfs/[cartoons,reality,tv]");
assert_eq!(monitored_root_folders[0].free_space, 100);
}
#[test]
fn test_extract_monitored_root_folders_uses_most_specific_monitored_path() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(vec!["/nfs".to_owned(), "/".to_owned()]),
..ServarrConfig::default()
});
let root_folders = vec![
RootFolder {
id: 1,
path: "/nfs/cartoons".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
},
RootFolder {
id: 2,
path: "/nfs/tv".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
},
RootFolder {
id: 3,
path: "/other/movies".to_string(),
accessible: true,
free_space: 200,
unmapped_folders: None,
},
];
let monitored_root_folders = extract_monitored_root_folders(&app, root_folders);
assert_eq!(monitored_root_folders.len(), 2);
assert_eq!(monitored_root_folders[0].path, "/[other]");
assert_eq!(monitored_root_folders[0].free_space, 200);
assert_eq!(monitored_root_folders[1].path, "/nfs/[cartoons,tv]");
assert_eq!(monitored_root_folders[1].free_space, 100);
}
#[test]
fn test_extract_monitored_root_folders_preserves_unmatched_folders() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(vec!["/nfs".to_owned()]),
..ServarrConfig::default()
});
let root_folders = vec![
RootFolder {
id: 1,
path: "/nfs/tv".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
},
RootFolder {
id: 2,
path: "/other/movies".to_string(),
accessible: true,
free_space: 200,
unmapped_folders: None,
},
];
let monitored_root_folders = extract_monitored_root_folders(&app, root_folders);
assert_eq!(monitored_root_folders.len(), 2);
assert_eq!(monitored_root_folders[0].path, "/nfs/[tv]");
assert_eq!(monitored_root_folders[1].path, "/other/movies");
}
#[test]
fn test_extract_monitored_root_folders_returns_all_when_monitored_storage_paths_is_empty() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(vec![]),
..ServarrConfig::default()
});
let root_folders = vec![
RootFolder {
id: 1,
path: "/nfs".to_string(),
accessible: true,
free_space: 10,
unmapped_folders: None,
},
RootFolder {
id: 2,
path: "/nfs/some/subpath".to_string(),
accessible: true,
free_space: 10,
unmapped_folders: None,
},
];
let monitored_root_folders = extract_monitored_root_folders(&app, root_folders.clone());
assert_eq!(monitored_root_folders, root_folders);
}
#[test]
fn test_extract_monitored_root_folders_returns_all_when_monitored_storage_paths_is_none() {
let app = App::test_default();
let root_folders = vec![
RootFolder {
id: 1,
path: "/nfs".to_string(),
accessible: true,
free_space: 10,
unmapped_folders: None,
},
RootFolder {
id: 2,
path: "/nfs/some/subpath".to_string(),
accessible: true,
free_space: 10,
unmapped_folders: None,
},
];
let monitored_root_folders = extract_monitored_root_folders(&app, root_folders.clone());
assert_eq!(monitored_root_folders, root_folders);
}
#[test]
fn test_extract_monitored_root_folders_exact_match_shows_no_brackets() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(vec!["/nfs/tv".to_owned()]),
..ServarrConfig::default()
});
let root_folders = vec![RootFolder {
id: 1,
path: "/nfs/tv".to_string(),
accessible: true,
free_space: 100,
unmapped_folders: None,
}];
let monitored_root_folders = extract_monitored_root_folders(&app, root_folders);
assert_eq!(monitored_root_folders.len(), 1);
assert_eq!(monitored_root_folders[0].path, "/nfs/tv");
}
#[test]
fn test_extract_monitored_disk_space_vec() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(vec!["/data".to_owned(), "/downloads".to_owned()]),
..ServarrConfig::default()
});
let disk_space = DiskSpace {
path: Some("/data".to_string()),
free_space: 10,
total_space: 1000,
};
let disk_space_2 = DiskSpace {
path: Some("/downloads".to_string()),
free_space: 100,
total_space: 10000,
};
let disk_space_with_empty_path = DiskSpace {
path: None,
free_space: 10,
total_space: 1000,
};
let disk_spaces = vec![
disk_space.clone(),
disk_space_with_empty_path.clone(),
DiskSpace {
path: Some("/downloads/".to_string()),
free_space: 100,
total_space: 10000,
},
disk_space_2.clone(),
];
let monitored_disk_space = extract_monitored_disk_space_vec(&app, disk_spaces);
assert_eq!(
monitored_disk_space,
vec![disk_space, disk_space_with_empty_path, disk_space_2]
);
}
#[test]
fn test_extract_monitored_disk_space_vec_returns_all_when_monitored_storage_paths_is_empty() {
let mut app = App::test_default();
app.server_tabs.tabs[0].config = Some(ServarrConfig {
monitored_storage_paths: Some(Vec::new()),
..ServarrConfig::default()
});
let disk_spaces = vec![
DiskSpace {
path: Some("/nfs".to_string()),
free_space: 10,
total_space: 1000,
},
DiskSpace {
path: None,
free_space: 10,
total_space: 1000,
},
DiskSpace {
path: Some("/nfs/some/subpath".to_string()),
free_space: 10,
total_space: 1000,
},
];
let monitored_disk_space = extract_monitored_disk_space_vec(&app, disk_spaces.clone());
assert_eq!(monitored_disk_space, disk_spaces);
}
#[test]
fn test_extract_monitored_disk_space_vec_returns_all_when_monitored_storage_paths_is_none() {
let app = App::test_default();
let disk_spaces = vec![
DiskSpace {
path: Some("/nfs".to_string()),
free_space: 10,
total_space: 1000,
},
DiskSpace {
path: None,
free_space: 10,
total_space: 1000,
},
DiskSpace {
path: Some("/nfs/some/subpath".to_string()),
free_space: 10,
total_space: 1000,
},
];
let monitored_disk_space = extract_monitored_disk_space_vec(&app, disk_spaces.clone());
assert_eq!(monitored_disk_space, disk_spaces);
}
enum PeerStyle {
Failure,
Warning,
+1 -1
View File
@@ -146,7 +146,7 @@ pub(super) fn load_config(path: &str) -> Result<AppConfig> {
Ok(config)
}
Err(e) => {
log_and_print_error(format!("Unable to open config file: {e:?}"));
log_and_print_error(format!("Unable to open config file '{path}': {e:?}"));
process::exit(1);
}
}