Compare commits
13 Commits
d9a2b1c6c4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a26444006b | |||
| ccf3e28323 | |||
| 6eea6b92fb | |||
|
6df68b8a66
|
|||
|
cbca6bd916
|
|||
|
92187c5f16
|
|||
|
366809d8c6
|
|||
|
dd93fe117d
|
|||
|
10e18af1bf
|
|||
|
a4d93692a9
|
|||
|
03d134bec9
|
|||
|
4cad9e1755
|
|||
| adb76bb603 |
@@ -76,15 +76,15 @@ jobs:
|
|||||||
RUSTDOCFLAGS: --cfg docsrs
|
RUSTDOCFLAGS: --cfg docsrs
|
||||||
msrv:
|
msrv:
|
||||||
# check that we can build using the minimal rust version that is specified by this crate
|
# check that we can build using the minimal rust version that is specified by this crate
|
||||||
name: 1.89.0 / check
|
name: 1.95.0 / check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install 1.89.0
|
- name: Install 1.95.0
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.89.0
|
toolchain: 1.95.0
|
||||||
|
|
||||||
- name: cargo +1.89.0 check
|
- name: cargo +1.95.0 check
|
||||||
run: cargo check
|
run: cargo check
|
||||||
|
|||||||
@@ -5,6 +5,27 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## v0.7.3 (2026-06-25)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Implemented log rolling so the log file doesn't just grow exponentially [#60]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- addressed code review comments
|
||||||
|
- tail-logs subcommand follows log rollovers and sleeps to minimize idle CPU loops
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Refactored several usages of sort_by_key and match guards to utilize newer Rust version APIs
|
||||||
|
|
||||||
|
## v0.7.2 (2026-04-20)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Created a separate 'ssl' property for the config so users don't have to specify an ssl_cert_path to use SSL or use the uri workaround for HTTPS API access
|
||||||
|
|
||||||
## v0.7.1 (2026-02-04)
|
## v0.7.1 (2026-02-04)
|
||||||
|
|
||||||
### Feat
|
### Feat
|
||||||
|
|||||||
Generated
+364
-527
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.7.1"
|
version = "0.7.3"
|
||||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
description = "A TUI and CLI to manage your Servarrs"
|
description = "A TUI and CLI to manage your Servarrs"
|
||||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||||
@@ -10,7 +10,7 @@ homepage = "https://github.com/Dark-Alex-17/managarr"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.89.0"
|
rust-version = "1.95.0"
|
||||||
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
|
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
@@ -32,7 +32,7 @@ derivative = "2.2.0"
|
|||||||
human-panic = "2.0.6"
|
human-panic = "2.0.6"
|
||||||
indoc = "2.0.7"
|
indoc = "2.0.7"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
log4rs = { version = "1.4.0", features = ["file_appender"] }
|
log4rs = { version = "1.4.0", features = ["rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] }
|
||||||
regex = "1.12.2"
|
regex = "1.12.2"
|
||||||
reqwest = { version = "0.13.2", features = ["json"] }
|
reqwest = { version = "0.13.2", features = ["json"] }
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
@@ -63,7 +63,7 @@ managarr-tree-widget = "0.25.0"
|
|||||||
indicatif = "0.17.11"
|
indicatif = "0.17.11"
|
||||||
derive_setters = "0.1.9"
|
derive_setters = "0.1.9"
|
||||||
deunicode = "1.6.2"
|
deunicode = "1.6.2"
|
||||||
openssl = { version = "0.10.75", features = ["vendored"] }
|
openssl = { version = "0.10.79", features = ["vendored"] }
|
||||||
veil = "0.2.0"
|
veil = "0.2.0"
|
||||||
validate_theme_derive = "0.1.0"
|
validate_theme_derive = "0.1.0"
|
||||||
enum_display_style_derive = "0.1.0"
|
enum_display_style_derive = "0.1.0"
|
||||||
|
|||||||
@@ -338,7 +338,38 @@ $ managarr radarr list movies | jq '.[] | select(.title == "Ad Astra") | .id'
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
Managarr assumes reasonable defaults to connect to each service (i.e. Radarr is on localhost:7878),
|
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.
|
but all servers will require you to input the API token. This means that for each Servarr you configure,
|
||||||
|
if you define only the `api_token`, Managarr will assume the Servarr is running on `localhost` and on the
|
||||||
|
default port for that respective service. That is:
|
||||||
|
|
||||||
|
| Servarr | Default Host | Default Port |
|
||||||
|
|---------|--------------|--------------|
|
||||||
|
| Radarr | `localhost` | 7878 |
|
||||||
|
| Sonarr | `localhost` | 8989 |
|
||||||
|
| Lidarr | `localhost` | 8686 |
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> In general, all Servarrs store their API tokens under Settings -> General -> Security -> API Key in their web UIs.
|
||||||
|
|
||||||
|
## Minimum Configuration Requirements
|
||||||
|
The following configuration file will connect to each Servarr running on localhost with their default ports. The only
|
||||||
|
requirement for each is the specification of an API token.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
radarr:
|
||||||
|
# Connect to Radarr running on localhost:7878
|
||||||
|
- api_token: <your-radarr-api-token-here>
|
||||||
|
|
||||||
|
sonarr:
|
||||||
|
# Connect to sonarr running on localhost:8989
|
||||||
|
- api_token: <your-sonarr-api-token-here>
|
||||||
|
|
||||||
|
lidarr:
|
||||||
|
# Connect to lidarr running on localhost:8686
|
||||||
|
- api_token: <your-lidarr-api-token-here>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration File Location
|
||||||
|
|
||||||
The configuration file is located somewhere different for each OS, so run the following command to print out the default
|
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:
|
location of the `managarr` configuration file for your system:
|
||||||
|
|||||||
@@ -231,8 +231,8 @@ mod tests {
|
|||||||
let expected_keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
let expected_keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, desc)| {
|
.map(|(key, desc)| {
|
||||||
let (key, alt_key) = if key.alt.is_some() {
|
let (key, alt_key) = if let Some(key1) = key.alt {
|
||||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
(key.key.to_string(), key1.to_string())
|
||||||
} else {
|
} else {
|
||||||
(key.key.to_string(), String::new())
|
(key.key.to_string(), String::new())
|
||||||
};
|
};
|
||||||
@@ -338,8 +338,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn context_clue_to_keybinding_item(key: &KeyBinding, desc: &&str) -> KeybindingItem {
|
fn context_clue_to_keybinding_item(key: &KeyBinding, desc: &&str) -> KeybindingItem {
|
||||||
let (key, alt_key) = if key.alt.is_some() {
|
let (key, alt_key) = if let Some(key1) = key.alt {
|
||||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
(key.key.to_string(), key1.to_string())
|
||||||
} else {
|
} else {
|
||||||
(key.key.to_string(), String::new())
|
(key.key.to_string(), String::new())
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -506,18 +506,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for EditIndexerHandler<'
|
|||||||
.tags
|
.tags
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::EditIndexerPrompt => {
|
ActiveLidarrBlock::EditIndexerPrompt
|
||||||
if self.app.data.lidarr_data.selected_block.get_active_block()
|
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
== ActiveLidarrBlock::EditIndexerConfirmPrompt
|
== ActiveLidarrBlock::EditIndexerConfirmPrompt
|
||||||
&& matches_key!(confirm, self.key)
|
&& matches_key!(confirm, self.key) =>
|
||||||
{
|
{
|
||||||
self.app.data.lidarr_data.prompt_confirm = true;
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
self.app.data.lidarr_data.prompt_confirm_action =
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
Some(LidarrEvent::EditIndexer(self.build_edit_indexer_params()));
|
Some(LidarrEvent::EditIndexer(self.build_edit_indexer_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for IndexerSettingsHandl
|
|||||||
indexer_settings.maximum_size -= 1;
|
indexer_settings.maximum_size -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput => {
|
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput
|
||||||
if indexer_settings.rss_sync_interval > 0 {
|
if indexer_settings.rss_sync_interval > 0 =>
|
||||||
indexer_settings.rss_sync_interval -= 1;
|
{
|
||||||
}
|
indexer_settings.rss_sync_interval -= 1;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -591,16 +591,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a,
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::AddArtistPrompt => {
|
ActiveLidarrBlock::AddArtistPrompt
|
||||||
if self.app.data.lidarr_data.selected_block.get_active_block()
|
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
== ActiveLidarrBlock::AddArtistConfirmPrompt
|
== ActiveLidarrBlock::AddArtistConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.lidarr_data.prompt_confirm = true;
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
self.app.data.lidarr_data.prompt_confirm_action =
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
Some(LidarrEvent::AddArtist(self.build_add_artist_body()));
|
Some(LidarrEvent::AddArtist(self.build_add_artist_body()));
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,16 +293,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
self.app.data.lidarr_data.selected_block =
|
self.app.data.lidarr_data.selected_block =
|
||||||
BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||||
}
|
}
|
||||||
_ if matches_key!(toggle_monitoring, key) => {
|
_ if matches_key!(toggle_monitoring, key)
|
||||||
if !self.app.data.lidarr_data.albums.is_empty() {
|
&& !self.app.data.lidarr_data.albums.is_empty() =>
|
||||||
self.app.data.lidarr_data.prompt_confirm = true;
|
{
|
||||||
self.app.data.lidarr_data.prompt_confirm_action =
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
Some(LidarrEvent::ToggleAlbumMonitoring(self.extract_album_id()));
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::ToggleAlbumMonitoring(self.extract_album_id()));
|
||||||
|
|
||||||
self
|
self
|
||||||
.app
|
.app
|
||||||
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -428,18 +428,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for EditArtistHandler<'a
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::EditArtistPrompt => {
|
ActiveLidarrBlock::EditArtistPrompt
|
||||||
if self.app.data.lidarr_data.selected_block.get_active_block()
|
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
== ActiveLidarrBlock::EditArtistConfirmPrompt
|
== ActiveLidarrBlock::EditArtistConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.lidarr_data.prompt_confirm = true;
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
self.app.data.lidarr_data.prompt_confirm_action =
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
Some(LidarrEvent::EditArtist(self.build_edit_artist_params()));
|
Some(LidarrEvent::EditArtist(self.build_edit_artist_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,19 +505,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddRootFolderHandler
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::AddRootFolderPrompt => {
|
ActiveLidarrBlock::AddRootFolderPrompt
|
||||||
if self.app.data.lidarr_data.selected_block.get_active_block()
|
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
== ActiveLidarrBlock::AddRootFolderConfirmPrompt
|
== ActiveLidarrBlock::AddRootFolderConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.lidarr_data.prompt_confirm = true;
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::AddRootFolder(
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::AddRootFolder(
|
||||||
self.build_add_root_folder_body(),
|
self.build_add_root_folder_body(),
|
||||||
));
|
));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -139,8 +139,8 @@ pub fn handle_events(key: Key, app: &mut App<'_>) {
|
|||||||
|
|
||||||
pub fn populate_keymapping_table(app: &mut App<'_>) {
|
pub fn populate_keymapping_table(app: &mut App<'_>) {
|
||||||
let context_clue_to_keybinding_item = |key: &KeyBinding, desc: &&str| {
|
let context_clue_to_keybinding_item = |key: &KeyBinding, desc: &&str| {
|
||||||
let (key, alt_key) = if key.alt.is_some() {
|
let (key, alt_key) = if let Some(key1) = key.alt {
|
||||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
(key.key.to_string(), key1.to_string())
|
||||||
} else {
|
} else {
|
||||||
(key.key.to_string(), String::new())
|
(key.key.to_string(), String::new())
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -354,19 +354,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
|||||||
.path
|
.path
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::EditCollectionPrompt => {
|
ActiveRadarrBlock::EditCollectionPrompt
|
||||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||||
== ActiveRadarrBlock::EditCollectionConfirmPrompt
|
== ActiveRadarrBlock::EditCollectionConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
|
||||||
self.build_edit_collection_params(),
|
self.build_edit_collection_params(),
|
||||||
));
|
));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -507,18 +507,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
|||||||
.tags
|
.tags
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
ActiveRadarrBlock::EditIndexerPrompt
|
||||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||||
== ActiveRadarrBlock::EditIndexerConfirmPrompt
|
== ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||||
&& matches_key!(confirm, self.key)
|
&& matches_key!(confirm, self.key) =>
|
||||||
{
|
{
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action =
|
self.app.data.radarr_data.prompt_confirm_action =
|
||||||
Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params()));
|
Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,10 +114,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
|||||||
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput => {
|
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput => {
|
||||||
indexer_settings.availability_delay -= 1;
|
indexer_settings.availability_delay -= 1;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => {
|
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput
|
||||||
if indexer_settings.rss_sync_interval > 0 {
|
if indexer_settings.rss_sync_interval > 0 =>
|
||||||
indexer_settings.rss_sync_interval -= 1;
|
{
|
||||||
}
|
indexer_settings.rss_sync_interval -= 1;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@@ -272,19 +272,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
|||||||
.whitelisted_hardcoded_subs
|
.whitelisted_hardcoded_subs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::AllIndexerSettingsPrompt => {
|
ActiveRadarrBlock::AllIndexerSettingsPrompt
|
||||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||||
== ActiveRadarrBlock::IndexerSettingsConfirmPrompt
|
== ActiveRadarrBlock::IndexerSettingsConfirmPrompt
|
||||||
&& matches_key!(confirm, self.key)
|
&& matches_key!(confirm, self.key) =>
|
||||||
{
|
{
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action = Some(
|
self.app.data.radarr_data.prompt_confirm_action = Some(
|
||||||
RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()),
|
RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()),
|
||||||
);
|
);
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -539,16 +539,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::AddMoviePrompt => {
|
ActiveRadarrBlock::AddMoviePrompt
|
||||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||||
== ActiveRadarrBlock::AddMovieConfirmPrompt
|
== ActiveRadarrBlock::AddMovieConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action =
|
self.app.data.radarr_data.prompt_confirm_action =
|
||||||
Some(RadarrEvent::AddMovie(self.build_add_movie_body()));
|
Some(RadarrEvent::AddMovie(self.build_add_movie_body()));
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,18 +376,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::EditMoviePrompt => {
|
ActiveRadarrBlock::EditMoviePrompt
|
||||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||||
== ActiveRadarrBlock::EditMovieConfirmPrompt
|
== ActiveRadarrBlock::EditMovieConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.radarr_data.prompt_confirm = true;
|
self.app.data.radarr_data.prompt_confirm = true;
|
||||||
self.app.data.radarr_data.prompt_confirm_action =
|
self.app.data.radarr_data.prompt_confirm_action =
|
||||||
Some(RadarrEvent::EditMovie(self.build_edit_movie_params()));
|
Some(RadarrEvent::EditMovie(self.build_edit_movie_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,18 +506,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
|
|||||||
.tags
|
.tags
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::EditIndexerPrompt => {
|
ActiveSonarrBlock::EditIndexerPrompt
|
||||||
if self.app.data.sonarr_data.selected_block.get_active_block()
|
if self.app.data.sonarr_data.selected_block.get_active_block()
|
||||||
== ActiveSonarrBlock::EditIndexerConfirmPrompt
|
== ActiveSonarrBlock::EditIndexerConfirmPrompt
|
||||||
&& matches_key!(confirm, self.key)
|
&& matches_key!(confirm, self.key) =>
|
||||||
{
|
{
|
||||||
self.app.data.sonarr_data.prompt_confirm = true;
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
self.app.data.sonarr_data.prompt_confirm_action =
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
Some(SonarrEvent::EditIndexer(self.build_edit_indexer_params()));
|
Some(SonarrEvent::EditIndexer(self.build_edit_indexer_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
|
|||||||
indexer_settings.maximum_size -= 1;
|
indexer_settings.maximum_size -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput => {
|
ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput
|
||||||
if indexer_settings.rss_sync_interval > 0 {
|
if indexer_settings.rss_sync_interval > 0 =>
|
||||||
indexer_settings.rss_sync_interval -= 1;
|
{
|
||||||
}
|
indexer_settings.rss_sync_interval -= 1;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -606,16 +606,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::AddSeriesPrompt => {
|
ActiveSonarrBlock::AddSeriesPrompt
|
||||||
if self.app.data.sonarr_data.selected_block.get_active_block()
|
if self.app.data.sonarr_data.selected_block.get_active_block()
|
||||||
== ActiveSonarrBlock::AddSeriesConfirmPrompt
|
== ActiveSonarrBlock::AddSeriesConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.sonarr_data.prompt_confirm = true;
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
self.app.data.sonarr_data.prompt_confirm_action =
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
Some(SonarrEvent::AddSeries(self.build_add_series_body()));
|
Some(SonarrEvent::AddSeries(self.build_add_series_body()));
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,18 +450,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
|
|||||||
.tags
|
.tags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::EditSeriesPrompt => {
|
ActiveSonarrBlock::EditSeriesPrompt
|
||||||
if self.app.data.sonarr_data.selected_block.get_active_block()
|
if self.app.data.sonarr_data.selected_block.get_active_block()
|
||||||
== ActiveSonarrBlock::EditSeriesConfirmPrompt
|
== ActiveSonarrBlock::EditSeriesConfirmPrompt
|
||||||
&& matches_key!(confirm, key)
|
&& matches_key!(confirm, key) =>
|
||||||
{
|
{
|
||||||
self.app.data.sonarr_data.prompt_confirm = true;
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
self.app.data.sonarr_data.prompt_confirm_action =
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
Some(SonarrEvent::EditSeries(self.build_edit_series_params()));
|
Some(SonarrEvent::EditSeries(self.build_edit_series_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -893,7 +893,7 @@ mod tests {
|
|||||||
app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into());
|
app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into());
|
||||||
|
|
||||||
let mut expected_vec = movies_vec();
|
let mut expected_vec = movies_vec();
|
||||||
expected_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
expected_vec.sort_by_key(|a| a.id);
|
||||||
expected_vec.reverse();
|
expected_vec.reverse();
|
||||||
|
|
||||||
TableHandlerUnit::new(
|
TableHandlerUnit::new(
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ impl Network<'_, '_> {
|
|||||||
Route::Lidarr(ActiveLidarrBlock::BlocklistSortPrompt, _)
|
Route::Lidarr(ActiveLidarrBlock::BlocklistSortPrompt, _)
|
||||||
) {
|
) {
|
||||||
let mut blocklist_vec: Vec<BlocklistItem> = blocklist_resp.records;
|
let mut blocklist_vec: Vec<BlocklistItem> = blocklist_resp.records;
|
||||||
blocklist_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
blocklist_vec.sort_by_key(|a| a.id);
|
||||||
app.data.lidarr_data.blocklist.set_items(blocklist_vec);
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec);
|
||||||
app.data.lidarr_data.blocklist.apply_sorting_toggle(false);
|
app.data.lidarr_data.blocklist.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ impl Network<'_, '_> {
|
|||||||
Route::Lidarr(ActiveLidarrBlock::HistorySortPrompt, _)
|
Route::Lidarr(ActiveLidarrBlock::HistorySortPrompt, _)
|
||||||
) {
|
) {
|
||||||
let mut history_vec = history_response.records;
|
let mut history_vec = history_response.records;
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
app.data.lidarr_data.history.set_items(history_vec);
|
app.data.lidarr_data.history.set_items(history_vec);
|
||||||
app.data.lidarr_data.history.apply_sorting_toggle(false);
|
app.data.lidarr_data.history.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
self
|
self
|
||||||
.handle_request::<(), Vec<Album>>(request_props, |mut albums_vec, mut app| {
|
.handle_request::<(), Vec<Album>>(request_props, |mut albums_vec, mut app| {
|
||||||
albums_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
albums_vec.sort_by_key(|a| a.id);
|
||||||
app.data.lidarr_data.albums.set_items(albums_vec);
|
app.data.lidarr_data.albums.set_items(albums_vec);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -89,7 +89,7 @@ impl Network<'_, '_> {
|
|||||||
.get_or_insert_default();
|
.get_or_insert_default();
|
||||||
|
|
||||||
let mut history_vec = history_items;
|
let mut history_vec = history_items;
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
album_details_modal.album_history.set_items(history_vec);
|
album_details_modal.album_history.set_items(history_vec);
|
||||||
album_details_modal
|
album_details_modal
|
||||||
.album_history
|
.album_history
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ impl Network<'_, '_> {
|
|||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
Route::Lidarr(ActiveLidarrBlock::ArtistsSortPrompt, _)
|
Route::Lidarr(ActiveLidarrBlock::ArtistsSortPrompt, _)
|
||||||
) {
|
) {
|
||||||
artists_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
artists_vec.sort_by_key(|a| a.id);
|
||||||
app.data.lidarr_data.artists.set_items(artists_vec);
|
app.data.lidarr_data.artists.set_items(artists_vec);
|
||||||
app.data.lidarr_data.artists.apply_sorting_toggle(false);
|
app.data.lidarr_data.artists.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ impl Network<'_, '_> {
|
|||||||
let artist_history = &mut app.data.lidarr_data.artist_history;
|
let artist_history = &mut app.data.lidarr_data.artist_history;
|
||||||
|
|
||||||
if !is_sorting {
|
if !is_sorting {
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
artist_history.set_items(history_vec);
|
artist_history.set_items(history_vec);
|
||||||
artist_history.apply_sorting_toggle(false);
|
artist_history.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
self
|
self
|
||||||
.handle_request::<(), Vec<Track>>(request_props, |mut track_vec, mut app| {
|
.handle_request::<(), Vec<Track>>(request_props, |mut track_vec, mut app| {
|
||||||
track_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
track_vec.sort_by_key(|a| a.id);
|
||||||
let album_details_modal = app
|
let album_details_modal = app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
@@ -238,7 +238,7 @@ impl Network<'_, '_> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|it| it.track_id == track_id)
|
.filter(|it| it.track_id == track_id)
|
||||||
.collect();
|
.collect();
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
track_details_modal.track_history.set_items(history_vec);
|
track_details_modal.track_history.set_items(history_vec);
|
||||||
track_details_modal
|
track_details_modal
|
||||||
.track_history
|
.track_history
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl Network<'_, '_> {
|
|||||||
let log_lines = logs
|
let log_lines = logs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|log| {
|
.map(|log| {
|
||||||
if log.exception.is_some() {
|
if let Some(exception) = log.exception {
|
||||||
HorizontallyScrollableText::from(format!(
|
HorizontallyScrollableText::from(format!(
|
||||||
"{}|{}|{}|{}|{}",
|
"{}|{}|{}|{}|{}",
|
||||||
log.time,
|
log.time,
|
||||||
@@ -63,10 +63,7 @@ impl Network<'_, '_> {
|
|||||||
.exception_type
|
.exception_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("exception_type must exist when exception is present"),
|
.expect("exception_type must exist when exception is present"),
|
||||||
log
|
exception
|
||||||
.exception
|
|
||||||
.as_ref()
|
|
||||||
.expect("exception must exist in this branch")
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
HorizontallyScrollableText::from(format!(
|
HorizontallyScrollableText::from(format!(
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ impl Network<'_, '_> {
|
|||||||
Route::Radarr(ActiveRadarrBlock::BlocklistSortPrompt, _)
|
Route::Radarr(ActiveRadarrBlock::BlocklistSortPrompt, _)
|
||||||
) {
|
) {
|
||||||
let mut blocklist_vec = blocklist_resp.records;
|
let mut blocklist_vec = blocklist_resp.records;
|
||||||
blocklist_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
blocklist_vec.sort_by_key(|a| a.id);
|
||||||
app.data.radarr_data.blocklist.set_items(blocklist_vec);
|
app.data.radarr_data.blocklist.set_items(blocklist_vec);
|
||||||
app.data.radarr_data.blocklist.apply_sorting_toggle(false);
|
app.data.radarr_data.blocklist.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl Network<'_, '_> {
|
|||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
Route::Radarr(ActiveRadarrBlock::CollectionsSortPrompt, _)
|
Route::Radarr(ActiveRadarrBlock::CollectionsSortPrompt, _)
|
||||||
) {
|
) {
|
||||||
collections_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
collections_vec.sort_by_key(|a| a.id);
|
||||||
app.data.radarr_data.collections.set_items(collections_vec);
|
app.data.radarr_data.collections.set_items(collections_vec);
|
||||||
app.data.radarr_data.collections.apply_sorting_toggle(false);
|
app.data.radarr_data.collections.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ impl Network<'_, '_> {
|
|||||||
Route::Radarr(ActiveRadarrBlock::HistorySortPrompt, _)
|
Route::Radarr(ActiveRadarrBlock::HistorySortPrompt, _)
|
||||||
) {
|
) {
|
||||||
let mut history_vec = history_response.records;
|
let mut history_vec = history_response.records;
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
app.data.radarr_data.history.set_items(history_vec);
|
app.data.radarr_data.history.set_items(history_vec);
|
||||||
app.data.radarr_data.history.apply_sorting_toggle(false);
|
app.data.radarr_data.history.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ impl Network<'_, '_> {
|
|||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
Route::Radarr(ActiveRadarrBlock::MoviesSortPrompt, _)
|
Route::Radarr(ActiveRadarrBlock::MoviesSortPrompt, _)
|
||||||
) {
|
) {
|
||||||
movie_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
movie_vec.sort_by_key(|a| a.id);
|
||||||
app.data.radarr_data.movies.set_items(movie_vec);
|
app.data.radarr_data.movies.set_items(movie_vec);
|
||||||
app.data.radarr_data.movies.apply_sorting_toggle(false);
|
app.data.radarr_data.movies.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl Network<'_, '_> {
|
|||||||
let log_lines = logs
|
let log_lines = logs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|log| {
|
.map(|log| {
|
||||||
if log.exception.is_some() {
|
if let Some(exception) = log.exception {
|
||||||
HorizontallyScrollableText::from(format!(
|
HorizontallyScrollableText::from(format!(
|
||||||
"{}|{}|{}|{}|{}",
|
"{}|{}|{}|{}|{}",
|
||||||
log.time,
|
log.time,
|
||||||
@@ -80,10 +80,7 @@ impl Network<'_, '_> {
|
|||||||
.exception_type
|
.exception_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("exception_type must exist when exception is present"),
|
.expect("exception_type must exist when exception is present"),
|
||||||
log
|
exception
|
||||||
.exception
|
|
||||||
.as_ref()
|
|
||||||
.expect("exception must exist in this branch")
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
HorizontallyScrollableText::from(format!(
|
HorizontallyScrollableText::from(format!(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ impl Network<'_, '_> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
blocklist_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
blocklist_vec.sort_by_key(|a| a.id);
|
||||||
app.data.sonarr_data.blocklist.set_items(blocklist_vec);
|
app.data.sonarr_data.blocklist.set_items(blocklist_vec);
|
||||||
app.data.sonarr_data.blocklist.apply_sorting_toggle(false);
|
app.data.sonarr_data.blocklist.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ impl Network<'_, '_> {
|
|||||||
Route::Sonarr(ActiveSonarrBlock::HistorySortPrompt, _)
|
Route::Sonarr(ActiveSonarrBlock::HistorySortPrompt, _)
|
||||||
) {
|
) {
|
||||||
let mut history_vec = history_response.records;
|
let mut history_vec = history_response.records;
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
app.data.sonarr_data.history.set_items(history_vec);
|
app.data.sonarr_data.history.set_items(history_vec);
|
||||||
app.data.sonarr_data.history.apply_sorting_toggle(false);
|
app.data.sonarr_data.history.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
self
|
self
|
||||||
.handle_request::<(), Vec<Episode>>(request_props, |mut episode_vec, mut app| {
|
.handle_request::<(), Vec<Episode>>(request_props, |mut episode_vec, mut app| {
|
||||||
episode_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
episode_vec.sort_by_key(|a| a.id);
|
||||||
if !matches!(
|
if !matches!(
|
||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
Route::Sonarr(ActiveSonarrBlock::EpisodesSortPrompt, _)
|
Route::Sonarr(ActiveSonarrBlock::EpisodesSortPrompt, _)
|
||||||
@@ -151,7 +151,7 @@ impl Network<'_, '_> {
|
|||||||
.get_or_insert_default();
|
.get_or_insert_default();
|
||||||
|
|
||||||
let mut history_vec = history_response.records;
|
let mut history_vec = history_response.records;
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
episode_details_modal.episode_history.set_items(history_vec);
|
episode_details_modal.episode_history.set_items(history_vec);
|
||||||
episode_details_modal
|
episode_details_modal
|
||||||
.episode_history
|
.episode_history
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ impl Network<'_, '_> {
|
|||||||
|
|
||||||
if !is_sorting {
|
if !is_sorting {
|
||||||
let mut history_vec = history_items;
|
let mut history_vec = history_items;
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
season_details_modal.season_history.set_items(history_vec);
|
season_details_modal.season_history.set_items(history_vec);
|
||||||
season_details_modal
|
season_details_modal
|
||||||
.season_history
|
.season_history
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ impl Network<'_, '_> {
|
|||||||
let series_history = app.data.sonarr_data.series_history.get_or_insert_default();
|
let series_history = app.data.sonarr_data.series_history.get_or_insert_default();
|
||||||
|
|
||||||
if !is_sorting {
|
if !is_sorting {
|
||||||
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
history_vec.sort_by_key(|a| a.id);
|
||||||
series_history.set_items(history_vec);
|
series_history.set_items(history_vec);
|
||||||
series_history.apply_sorting_toggle(false);
|
series_history.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ impl Network<'_, '_> {
|
|||||||
app.get_current_route(),
|
app.get_current_route(),
|
||||||
Route::Sonarr(ActiveSonarrBlock::SeriesSortPrompt, _)
|
Route::Sonarr(ActiveSonarrBlock::SeriesSortPrompt, _)
|
||||||
) {
|
) {
|
||||||
series_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
series_vec.sort_by_key(|a| a.id);
|
||||||
app.data.sonarr_data.series.set_items(series_vec);
|
app.data.sonarr_data.series.set_items(series_vec);
|
||||||
app.data.sonarr_data.series.apply_sorting_toggle(false);
|
app.data.sonarr_data.series.apply_sorting_toggle(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl Network<'_, '_> {
|
|||||||
let log_lines = logs
|
let log_lines = logs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|log| {
|
.map(|log| {
|
||||||
if log.exception.is_some() {
|
if let Some(exception) = log.exception {
|
||||||
HorizontallyScrollableText::from(format!(
|
HorizontallyScrollableText::from(format!(
|
||||||
"{}|{}|{}|{}|{}",
|
"{}|{}|{}|{}|{}",
|
||||||
log.time,
|
log.time,
|
||||||
@@ -63,10 +63,7 @@ impl Network<'_, '_> {
|
|||||||
.exception_type
|
.exception_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("exception_type must exist when exception is present"),
|
.expect("exception_type must exist when exception is present"),
|
||||||
log
|
exception
|
||||||
.exception
|
|
||||||
.as_ref()
|
|
||||||
.expect("exception must exist in this branch")
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
HorizontallyScrollableText::from(format!(
|
HorizontallyScrollableText::from(format!(
|
||||||
|
|||||||
@@ -52,33 +52,30 @@ impl DrawUi for IndexersUi {
|
|||||||
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
|
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
|
||||||
Route::Lidarr(active_lidarr_block, _) => match active_lidarr_block {
|
Route::Lidarr(active_lidarr_block, _) => match active_lidarr_block {
|
||||||
ActiveLidarrBlock::TestIndexer => {
|
ActiveLidarrBlock::TestIndexer => {
|
||||||
if app.is_loading || app.data.lidarr_data.indexer_test_errors.is_none() {
|
if let Some(result) = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.indexer_test_errors
|
||||||
|
.as_ref()
|
||||||
|
.filter(|_| !app.is_loading)
|
||||||
|
{
|
||||||
|
let popup = if !result.is_empty() {
|
||||||
|
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
|
||||||
|
} else {
|
||||||
|
let message = Message::new("Indexer test succeeded!")
|
||||||
|
.title("Success")
|
||||||
|
.style(success_style().bold());
|
||||||
|
Popup::new(message).size(Size::Message)
|
||||||
|
};
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
} else {
|
||||||
let loading_popup = Popup::new(LoadingBlock::new(
|
let loading_popup = Popup::new(LoadingBlock::new(
|
||||||
app.is_loading || app.data.lidarr_data.indexer_test_errors.is_none(),
|
app.is_loading || app.data.lidarr_data.indexer_test_errors.is_none(),
|
||||||
title_block("Testing Indexer"),
|
title_block("Testing Indexer"),
|
||||||
))
|
))
|
||||||
.size(Size::LargeMessage);
|
.size(Size::LargeMessage);
|
||||||
f.render_widget(loading_popup, f.area());
|
f.render_widget(loading_popup, f.area());
|
||||||
} else {
|
|
||||||
let popup = {
|
|
||||||
let result = app
|
|
||||||
.data
|
|
||||||
.lidarr_data
|
|
||||||
.indexer_test_errors
|
|
||||||
.as_ref()
|
|
||||||
.expect("Test result is unpopulated");
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
|
|
||||||
} else {
|
|
||||||
let message = Message::new("Indexer test succeeded!")
|
|
||||||
.title("Success")
|
|
||||||
.style(success_style().bold());
|
|
||||||
Popup::new(message).size(Size::Message)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
f.render_widget(popup, f.area());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::DeleteIndexerPrompt => {
|
ActiveLidarrBlock::DeleteIndexerPrompt => {
|
||||||
|
|||||||
@@ -107,9 +107,8 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec
|
|||||||
} else {
|
} else {
|
||||||
queued
|
queued
|
||||||
};
|
};
|
||||||
let started_string = if event.started.is_some() {
|
let started_string = if let Some(date_time) = event.started {
|
||||||
let started =
|
let started = convert_to_minutes_hours_days(Utc::now().sub(date_time).num_minutes());
|
||||||
convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes());
|
|
||||||
|
|
||||||
if started != "now" {
|
if started != "now" {
|
||||||
format!("{started} ago")
|
format!("{started} ago")
|
||||||
@@ -120,8 +119,8 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec
|
|||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let duration = if event.duration.is_some() {
|
let duration = if let Some(dur) = &event.duration {
|
||||||
&event.duration.as_ref().unwrap()[..8]
|
&dur[..8]
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,33 +52,30 @@ impl DrawUi for IndexersUi {
|
|||||||
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
|
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
|
||||||
Route::Radarr(active_radarr_block, _) => match active_radarr_block {
|
Route::Radarr(active_radarr_block, _) => match active_radarr_block {
|
||||||
ActiveRadarrBlock::TestIndexer => {
|
ActiveRadarrBlock::TestIndexer => {
|
||||||
if app.is_loading || app.data.radarr_data.indexer_test_errors.is_none() {
|
if let Some(result) = app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.indexer_test_errors
|
||||||
|
.as_ref()
|
||||||
|
.filter(|_| !app.is_loading)
|
||||||
|
{
|
||||||
|
let popup = if !result.is_empty() {
|
||||||
|
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
|
||||||
|
} else {
|
||||||
|
let message = Message::new("Indexer test succeeded!")
|
||||||
|
.title("Success")
|
||||||
|
.style(success_style().bold());
|
||||||
|
Popup::new(message).size(Size::Message)
|
||||||
|
};
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
} else {
|
||||||
let loading_popup = Popup::new(LoadingBlock::new(
|
let loading_popup = Popup::new(LoadingBlock::new(
|
||||||
app.is_loading || app.data.radarr_data.indexer_test_errors.is_none(),
|
app.is_loading || app.data.radarr_data.indexer_test_errors.is_none(),
|
||||||
title_block("Testing Indexer"),
|
title_block("Testing Indexer"),
|
||||||
))
|
))
|
||||||
.size(Size::LargeMessage);
|
.size(Size::LargeMessage);
|
||||||
f.render_widget(loading_popup, f.area());
|
f.render_widget(loading_popup, f.area());
|
||||||
} else {
|
|
||||||
let popup = {
|
|
||||||
let result = app
|
|
||||||
.data
|
|
||||||
.radarr_data
|
|
||||||
.indexer_test_errors
|
|
||||||
.as_ref()
|
|
||||||
.expect("Test result is unpopulated");
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
|
|
||||||
} else {
|
|
||||||
let message = Message::new("Indexer test succeeded!")
|
|
||||||
.title("Success")
|
|
||||||
.style(success_style().bold());
|
|
||||||
Popup::new(message).size(Size::Message)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
f.render_widget(popup, f.area());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::DeleteIndexerPrompt => {
|
ActiveRadarrBlock::DeleteIndexerPrompt => {
|
||||||
|
|||||||
@@ -114,9 +114,8 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec
|
|||||||
} else {
|
} else {
|
||||||
queued
|
queued
|
||||||
};
|
};
|
||||||
let started_string = if event.started.is_some() {
|
let started_string = if let Some(date_time) = event.started {
|
||||||
let started =
|
let started = convert_to_minutes_hours_days(Utc::now().sub(date_time).num_minutes());
|
||||||
convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes());
|
|
||||||
|
|
||||||
if started != "now" {
|
if started != "now" {
|
||||||
format!("{started} ago")
|
format!("{started} ago")
|
||||||
|
|||||||
@@ -52,33 +52,30 @@ impl DrawUi for IndexersUi {
|
|||||||
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
|
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
|
||||||
Route::Sonarr(active_sonarr_block, _) => match active_sonarr_block {
|
Route::Sonarr(active_sonarr_block, _) => match active_sonarr_block {
|
||||||
ActiveSonarrBlock::TestIndexer => {
|
ActiveSonarrBlock::TestIndexer => {
|
||||||
if app.is_loading || app.data.sonarr_data.indexer_test_errors.is_none() {
|
if let Some(result) = app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.indexer_test_errors
|
||||||
|
.as_ref()
|
||||||
|
.filter(|_| !app.is_loading)
|
||||||
|
{
|
||||||
|
let popup = if !result.is_empty() {
|
||||||
|
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
|
||||||
|
} else {
|
||||||
|
let message = Message::new("Indexer test succeeded!")
|
||||||
|
.title("Success")
|
||||||
|
.style(success_style().bold());
|
||||||
|
Popup::new(message).size(Size::Message)
|
||||||
|
};
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
} else {
|
||||||
let loading_popup = Popup::new(LoadingBlock::new(
|
let loading_popup = Popup::new(LoadingBlock::new(
|
||||||
app.is_loading || app.data.sonarr_data.indexer_test_errors.is_none(),
|
app.is_loading || app.data.sonarr_data.indexer_test_errors.is_none(),
|
||||||
title_block("Testing Indexer"),
|
title_block("Testing Indexer"),
|
||||||
))
|
))
|
||||||
.size(Size::LargeMessage);
|
.size(Size::LargeMessage);
|
||||||
f.render_widget(loading_popup, f.area());
|
f.render_widget(loading_popup, f.area());
|
||||||
} else {
|
|
||||||
let popup = {
|
|
||||||
let result = app
|
|
||||||
.data
|
|
||||||
.sonarr_data
|
|
||||||
.indexer_test_errors
|
|
||||||
.as_ref()
|
|
||||||
.expect("Test result is unpopulated");
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
|
|
||||||
} else {
|
|
||||||
let message = Message::new("Indexer test succeeded!")
|
|
||||||
.title("Success")
|
|
||||||
.style(success_style().bold());
|
|
||||||
Popup::new(message).size(Size::Message)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
f.render_widget(popup, f.area());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::DeleteIndexerPrompt => {
|
ActiveSonarrBlock::DeleteIndexerPrompt => {
|
||||||
|
|||||||
@@ -107,9 +107,8 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec
|
|||||||
} else {
|
} else {
|
||||||
queued
|
queued
|
||||||
};
|
};
|
||||||
let started_string = if event.started.is_some() {
|
let started_string = if let Some(date_time) = event.started {
|
||||||
let started =
|
let started = convert_to_minutes_hours_days(Utc::now().sub(date_time).num_minutes());
|
||||||
convert_to_minutes_hours_days(Utc::now().sub(event.started.unwrap()).num_minutes());
|
|
||||||
|
|
||||||
if started != "now" {
|
if started != "now" {
|
||||||
format!("{started} ago")
|
format!("{started} ago")
|
||||||
@@ -120,8 +119,8 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec
|
|||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let duration = if event.duration.is_some() {
|
let duration = if let Some(dur) = &event.duration {
|
||||||
&event.duration.as_ref().unwrap()[..8]
|
&dur[..8]
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,11 +125,8 @@ where
|
|||||||
if let Some(content) = self.content
|
if let Some(content) = self.content
|
||||||
&& !self.is_loading
|
&& !self.is_loading
|
||||||
{
|
{
|
||||||
let (table_contents, table_state) = if content.filtered_items.is_some() {
|
let (table_contents, table_state) = if let Some(items) = &content.filtered_items {
|
||||||
(
|
(items, content.filtered_state.as_mut().unwrap())
|
||||||
content.filtered_items.as_ref().unwrap(),
|
|
||||||
content.filtered_state.as_mut().unwrap(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(&content.items, &mut content.state)
|
(&content.items, &mut content.state)
|
||||||
};
|
};
|
||||||
@@ -153,10 +150,11 @@ where
|
|||||||
|
|
||||||
StatefulWidget::render(table, table_area, buf, table_state);
|
StatefulWidget::render(table, table_area, buf, table_state);
|
||||||
|
|
||||||
if content.sort.is_some() && self.is_sorting {
|
if let Some(sort) = &mut content.sort
|
||||||
let selectable_list = SelectableList::new(content.sort.as_mut().unwrap(), |item| {
|
&& self.is_sorting
|
||||||
ListItem::new(Text::from(item.name))
|
{
|
||||||
});
|
let selectable_list =
|
||||||
|
SelectableList::new(sort, |item| ListItem::new(Text::from(item.name)));
|
||||||
Popup::new(selectable_list)
|
Popup::new(selectable_list)
|
||||||
.dimensions(20, 50)
|
.dimensions(20, 50)
|
||||||
.render(table_area, buf);
|
.render(table_area, buf);
|
||||||
|
|||||||
+55
-13
@@ -10,7 +10,10 @@ use anyhow::{Context, anyhow};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use log::{LevelFilter, error};
|
use log::{LevelFilter, error};
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::rolling_file::RollingFileAppender;
|
||||||
|
use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
|
||||||
|
use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
|
||||||
|
use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
|
||||||
use log4rs::config::{Appender, Root};
|
use log4rs::config::{Appender, Root};
|
||||||
use log4rs::encode::pattern::PatternEncoder;
|
use log4rs::encode::pattern::PatternEncoder;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -47,12 +50,24 @@ pub fn get_log_path() -> PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_logging_config() -> log4rs::Config {
|
pub fn init_logging_config() -> log4rs::Config {
|
||||||
let logfile = FileAppender::builder()
|
let log_path = get_log_path();
|
||||||
|
let archive_pattern = log_path
|
||||||
|
.with_file_name("managarr.{}.log")
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
|
let trigger = SizeTrigger::new(10 * 1024 * 1024);
|
||||||
|
let roller = FixedWindowRoller::builder()
|
||||||
|
.build(&archive_pattern, 3)
|
||||||
|
.expect("Failed to build log roller");
|
||||||
|
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
|
||||||
|
|
||||||
|
let logfile = RollingFileAppender::builder()
|
||||||
.encoder(Box::new(PatternEncoder::new(
|
.encoder(Box::new(PatternEncoder::new(
|
||||||
"{d(%Y-%m-%d %H:%M:%S%.3f)(utc)} <{i}> [{l}] {f}:{L} - {m}{n}",
|
"{d(%Y-%m-%d %H:%M:%S%.3f)(utc)} <{i}> [{l}] {f}:{L} - {m}{n}",
|
||||||
)))
|
)))
|
||||||
.build(get_log_path())
|
.build(log_path, Box::new(policy))
|
||||||
.unwrap();
|
.expect("Failed to build rolling file appender");
|
||||||
|
|
||||||
log4rs::Config::builder()
|
log4rs::Config::builder()
|
||||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
||||||
@@ -89,16 +104,29 @@ pub async fn tail_logs(no_color: bool) -> Result<()> {
|
|||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.with_context(|| "Unable to tail log file")?;
|
.with_context(|| "Unable to tail log file")?;
|
||||||
|
|
||||||
let mut lines = reader.lines();
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let mut line_buf = String::new();
|
||||||
tokio::spawn(async move {
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(Ok(line)) = lines.next() {
|
line_buf.clear();
|
||||||
if no_color {
|
match reader.read_line(&mut line_buf) {
|
||||||
println!("{line}");
|
Ok(0) => {
|
||||||
} else {
|
if was_log_rotated(&file_path, &mut reader) {
|
||||||
let colored_line = colorize_log_line(&line, &re);
|
continue;
|
||||||
println!("{colored_line}");
|
}
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
let line = line_buf.trim_end();
|
||||||
|
if no_color {
|
||||||
|
println!("{line}");
|
||||||
|
} else {
|
||||||
|
let colored_line = colorize_log_line(line, &re);
|
||||||
|
println!("{colored_line}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +134,20 @@ pub async fn tail_logs(no_color: bool) -> Result<()> {
|
|||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn was_log_rotated(file_path: &PathBuf, reader: &mut BufReader<File>) -> bool {
|
||||||
|
let current_pos = reader.stream_position().unwrap_or(0);
|
||||||
|
let file_len = fs::metadata(file_path).map(|m| m.len()).unwrap_or(0);
|
||||||
|
|
||||||
|
if file_len < current_pos
|
||||||
|
&& let Ok(new_file) = File::open(file_path)
|
||||||
|
{
|
||||||
|
*reader = BufReader::new(new_file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn colorize_log_line(line: &str, re: &Regex) -> String {
|
fn colorize_log_line(line: &str, re: &Regex) -> String {
|
||||||
if let Some(caps) = re.captures(line) {
|
if let Some(caps) = re.captures(line) {
|
||||||
let level = &caps["level"];
|
let level = &caps["level"];
|
||||||
|
|||||||
+55
-1
@@ -1,8 +1,11 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::utils::{convert_f64_to_gb, convert_runtime, convert_to_gb};
|
use crate::utils::{convert_f64_to_gb, convert_runtime, convert_to_gb, was_log_rotated};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_to_gb() {
|
fn test_convert_to_gb() {
|
||||||
@@ -23,4 +26,55 @@ mod tests {
|
|||||||
assert_eq!(hours, 2);
|
assert_eq!(hours, 2);
|
||||||
assert_eq!(minutes, 34);
|
assert_eq!(minutes, 34);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_was_log_rotated_returns_false_when_file_has_not_rotated() {
|
||||||
|
let path = std::env::temp_dir().join("managarr_test_no_rotation.log");
|
||||||
|
fs::write(&path, "line one\nline two\n").unwrap();
|
||||||
|
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
|
||||||
|
assert!(!was_log_rotated(&path, &mut reader));
|
||||||
|
|
||||||
|
fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_was_log_rotated_returns_true_and_reopens_reader_after_rotation() {
|
||||||
|
let path = std::env::temp_dir().join("managarr_test_rotation.log");
|
||||||
|
fs::write(&path, "original content that is long enough\n").unwrap();
|
||||||
|
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
|
||||||
|
fs::write(&path, "new\n").unwrap();
|
||||||
|
|
||||||
|
assert!(was_log_rotated(&path, &mut reader));
|
||||||
|
|
||||||
|
let mut line = String::new();
|
||||||
|
reader.read_line(&mut line).unwrap();
|
||||||
|
assert_eq!(line, "new\n");
|
||||||
|
|
||||||
|
fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_was_log_rotated_returns_false_when_file_grows() {
|
||||||
|
let path = std::env::temp_dir().join("managarr_test_growing.log");
|
||||||
|
fs::write(&path, "initial\n").unwrap();
|
||||||
|
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
|
||||||
|
let mut appender = fs::OpenOptions::new().append(true).open(&path).unwrap();
|
||||||
|
appender.write_all(b"more data\n").unwrap();
|
||||||
|
|
||||||
|
assert!(!was_log_rotated(&path, &mut reader));
|
||||||
|
|
||||||
|
fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user