diff --git a/src/download_system/transfer.rs b/src/download_system/transfer.rs index ccad29c..c466506 100644 --- a/src/download_system/transfer.rs +++ b/src/download_system/transfer.rs @@ -54,7 +54,18 @@ impl Transfer { let service_result = match app.check_imported(&target.to).await { Ok(r) => r, Err(e) => { - error!("Error retrieving history from {}: {}", app, e); + // A misconfigured/unreachable *arr fails for every + // transfer on every poll; throttle the log so it doesn't + // fill the disk over time (issue #21). Key on the app's + // existing name (no allocation on the suppressed path). + if self.app_data.state.should_log_arr_error(&app.name).await { + error!( + "Error retrieving history from {} (suppressing repeats for {:?}): {}", + app, + crate::state::StateManager::ARR_ERROR_LOG_INTERVAL, + e + ); + } false } }; diff --git a/src/state.rs b/src/state.rs index 6ee24fb..8bc68fc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -42,6 +42,10 @@ pub struct StateManager { /// 404); without this, every torrent-get would re-hit the API and re-log a /// warning. Retries are suppressed until [`Self::NAME_FAILURE_TTL`] passes. failed_names: Arc>>, + /// Last time a connection error was logged for each *arr, used to throttle + /// the log. A misconfigured Sonarr/Radarr fails on every poll for every + /// transfer, and logging each one filled users' disks over time (issue #21). + arr_error_logged: Arc>>, } impl StateManager { @@ -52,6 +56,26 @@ impl StateManager { local_complete: Arc::new(RwLock::new(HashSet::new())), file_names: Arc::new(RwLock::new(HashMap::new())), failed_names: Arc::new(RwLock::new(HashMap::new())), + arr_error_logged: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// Minimum time between logging the same *arr's connection error. + pub const ARR_ERROR_LOG_INTERVAL: Duration = Duration::from_secs(300); + + /// Returns true if an error for `app` should be logged now, throttling + /// repeats to at most one per [`Self::ARR_ERROR_LOG_INTERVAL`]. Keeps a + /// persistently unreachable/misconfigured *arr from filling the disk with + /// identical error lines on every poll (issue #21). + pub async fn should_log_arr_error(&self, app: &str) -> bool { + let mut map = self.arr_error_logged.write().await; + let now = Instant::now(); + match map.get(app) { + Some(at) if now.saturating_duration_since(*at) < Self::ARR_ERROR_LOG_INTERVAL => false, + _ => { + map.insert(app.to_string(), now); + true + } } }