Skip to content

Commit

Permalink
🔀 ✨ QoL Update: Refinements and Enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
ThijmenGThN authored Mar 11, 2024
2 parents 55a3068 + d8872fd commit e6acce2
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 139 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ edition = "2021"
tabled = { version = "0.15.0" }
bytesize = { version = "1.3.0" }
humantime = { version = "2.1.0" }
serde_json = { version = "1.0.114" }
ms-converter = { version = "1.4.0" }
serde = { version = "1.0.197", features = ["derive"] }
reqwest = { version = "0.11.24", features = ["blocking", "json"] }
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ RUN apt clean

COPY --from=build /swaparr/target/release/swaparr /usr/local/bin/swaparr

CMD ["sh", "-c", "swaparr $BASEURL $APIKEY $PLATFORM $TIME_THRESHOLD $SIZE_THRESHOLD $CHECK_INTERVAL $STRIKE_THRESHOLD $AGGRESSIVE_STRIKES"]
CMD ["sh", "-c", "swaparr"]
28 changes: 15 additions & 13 deletions src/health.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
use reqwest::blocking as request;
use reqwest::blocking::get;

use crate::logger::alert;
use crate::system::exit;
use crate::system::Envs;
use crate::{logger, system};

pub fn check(env: &Envs) {
pub fn check(env: &system::Envs) {
// Check if the API can be reached.
match request::get(&format!(
match get(&format!(
"{}/api/v3/health?apikey={}",
&env.baseurl, &env.apikey
env.baseurl, env.apikey
)) {
// Can be reached, continue.
Ok(res) => {
// Let's just assume that the APIKEY is
// invalid if the code returned is not "200".
if res.status() != 200 {
alert(
logger::alert(
"FATAL",
format!("The provided \"APIKEY\" is not valid."),
format!(
"Obtain the {} API key in Settings > General > API Key",
&env.platform
env.platform
),
None,
);
exit(1);
system::exit(1);
}
}
// Could not be reached.
Err(error) => {
alert(
logger::alert(
"FATAL",
format!(
"A connection to the {} API could not be established.",
&env.platform
env.platform
),
"Ensure that the API is accessible and try again.".to_string(),
Some(error.to_string()),
);
exit(1);
system::exit(1);
}
}
}
12 changes: 6 additions & 6 deletions src/logger.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use crate::system;

pub fn empty() {
println!(
"\n╭───────────────────────────────────────────────────────────────────────────╮\n│ No torrents found │\n╰───────────────────────────────────────────────────────────────────────────╯\n",
);
}

pub fn alert(method: &str, title: String, message: String, error: Option<String>) {
println!("\n ─ {}", method);
println!("╭─╮ {}", title);
Expand All @@ -21,9 +27,3 @@ pub fn banner(env: &system::Envs) {
println!("╰─╯ Aggresive strikes: {}", &env.aggresive_strikes);
println!(" ─ Checking every: {}\n", env.check_interval);
}

pub fn empty() {
println!(
"\n╭───────────────────────────────────────────────────────────────────────────╮\n│ No torrents found │\n╰───────────────────────────────────────────────────────────────────────────╯\n",
);
}
108 changes: 9 additions & 99 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,119 +14,29 @@ fn main() {
// Health check the API, verbosely checks if a connection can be established.
health::check(&env);

// Based on the platform, use a different strategy to approach radarr or sonarr their API.
let queue_get_url = {
let method = match env.platform.as_str() {
"radarr" => "Movie",
"sonarr" => "Series",
_ => {
logger::alert(
"FATAL",
"Unknown \"PLATFORM\" value.".to_string(),
"Either set it to \"radarr\" or \"sonarr\".".to_string(),
None,
);
system::exit(1);
}
};
let params = format!("includeUnknown{method}Items=true&include{method}=true");
format!(
"{}/api/v3/queue?{}&apikey={}",
env.baseurl, params, env.apikey
)
};

// ----- Display Settings -----

// Displays initial "banner" with set configurations.
logger::banner(&env);

// ----- Striker Runtime -----
// Get the queue get url based on the platform.
let queue_get_url = parser::env_to_queue_get(&env);

// List of striked torrents.
let mut strikelist: HashMap<u32, u32> = HashMap::new();

// Main striker-runtime thread.
loop {
// Table rows that will be pretty-printed to the terminal.
let mut table_contents: Vec<render::TableContent> = vec![];

// Get all active torrents from the queue.
let queue_items = queue::get(&queue_get_url, &env.platform);

// Cleanup torrents that no longer exists from strikes registry.
// Cleanup torrents that no longer exists in the strikelist.
strikelist.retain(|&k, _| queue_items.iter().any(|item| item.id == k));

// Loop over all active torrents from the queue.
for torrent in queue_items {
let id = torrent.id.clone();
let mut status = String::from("Normal");

// Add torrent id to strikes with default "0" if it does not exist yet.
let mut strikes: u32 = match strikelist.get(&id) {
Some(strikes) => strikes.clone(),
None => {
strikelist.insert(id, 0);
0
}
};

// -- Bypass Rules -- Rules that define if a torrent is eligible to be striked.

let mut bypass: bool = false;

// Torrent is being processed or the time is infinite.
if torrent.eta == 0 && !env.aggresive_strikes {
status = String::from("Pending");
bypass = true;
}

// Torrent is larger than set threshold.
let size_threshold_bytes = parser::string_bytesize_to_bytes(&env.size_threshold);
if torrent.size >= size_threshold_bytes {
status = String::from("Ignored");
bypass = true;
}

// -- Strike rules -- Rules that define when to strike a torrent.

if !bypass {
// Torrent will take longer than set threshold.
let time_threshold_ms = parser::string_hms_to_ms(&env.time_threshold);
if (torrent.eta >= time_threshold_ms) || (torrent.eta == 0 && env.aggresive_strikes)
{
// Increment strikes if it's below set maximum.
if strikes < env.strike_threshold {
strikes += 1;
strikelist.insert(id, strikes);
}
status = String::from("Striked");
}

// Torrent meets set amount of strikes, a request to delete will be sent.
if strikes >= env.strike_threshold {
let queue_delete_url = format!(
"{}/api/v3/queue/{}?removeFromClient=true&blocklist=true&apikey={}",
env.baseurl, id, env.apikey
);
queue::delete(&queue_delete_url);
status = String::from("Removed");
}
}

// -- Logging --

// Add torrent to pretty-print table.
table_contents.push(render::TableContent {
strikes: format!("{}/{}", strikes, env.strike_threshold),
status,
name: torrent.name.chars().take(32).collect::<String>(),
eta: parser::ms_to_eta_string(&torrent.eta),
size: format!("{:.2} GB", (torrent.size as f64 / 1000000000.0)).to_string(),
})
}
// Process torrents in the queue, a table with details will also be printed.
queue::process(queue_items, &mut strikelist, &env);

// Print table to terminal.
render::table(&table_contents);
println!(" ─ Checking again in {}..\n", &env.check_interval);

// CHECK_INTERVAL sleeper for the main thread.
sleep(Duration::from_millis(
match parser::string_to_ms(&env.check_interval) {
Ok(check_interval_ms) => check_interval_ms as u64,
Expand Down
37 changes: 33 additions & 4 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,36 @@ use bytesize::ByteSize;
use humantime::format_duration;
use ms_converter;

// -- This will pretty-print an ETA from milliseconds.
use crate::{logger, system};

// This uses the environment variables to construct the get queue API URL.
pub fn env_to_queue_get(env: &system::Envs) -> String {
// Translates platform to keyword used by the API.
let method = match env.platform.as_str() {
"radarr" => "Movie",
"sonarr" => "Series",
_ => {
// Supplied platform is not supported, throw an error.
logger::alert(
"FATAL",
"Unknown \"PLATFORM\" value.".to_string(),
"Either set it to \"radarr\" or \"sonarr\".".to_string(),
None,
);
system::exit(1);
}
};

// Constructs the entire GET request URL.
format!(
"{}/api/v3/queue?{}&apikey={}",
env.baseurl,
format!("includeUnknown{method}Items=true&include{method}=true"),
env.apikey
)
}

// This will pretty-print an ETA from milliseconds.
pub fn ms_to_eta_string(ms: &u64) -> String {
let eta = format_duration(Duration::from_millis(ms.clone())).to_string();

Expand All @@ -15,20 +44,20 @@ pub fn ms_to_eta_string(ms: &u64) -> String {
}
}

// -- Converts human-readable string (from radarr or sonarr API) to milliseconds.
// Converts human-readable string (from radarr or sonarr API) to milliseconds.
pub fn string_to_ms(string: &String) -> Result<i64, ms_converter::Error> {
ms_converter::ms(string)
}

// -- This will convert for example "1 TB", "512 MB", <"1.5 GB" to 1500000 (bytes)>.
// This will convert for example "1 TB", "512 MB", <"1.5 GB" to 1500000 (bytes)>.
pub fn string_bytesize_to_bytes(string: &String) -> u64 {
match string.parse::<ByteSize>() {
Ok(size) => size.as_u64(),
Err(_) => 0,
}
}

// -- Converts human-readable string (from radarr or sonarr API) to milliseconds.
// Converts human-readable string (from radarr or sonarr API) to milliseconds.
pub fn string_hms_to_ms(string: &String) -> u64 {
let parts: Vec<&str> = string.split(|c| c == ':' || c == '.').collect();

Expand Down
Loading

0 comments on commit e6acce2

Please sign in to comment.