diff --git a/CHANGELOG.md b/CHANGELOG.md index 6048334..7c849b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- The database server no longer logs an error each time the healthcheck runs, about 2,900 a day, from a clear-out of DeadAir's old copies that had already run (#196). +- A page left open no longer misses updates after a check for new episodes: the note that each feed not due was skipped, one per feed, filled the queue they share (#197). - Checking for new episodes no longer waits up to 12 seconds each time on a clear-out of dead feeds from the Directory, which now runs once a day (#193). - All Subscriptions, and every item list, comes up faster, most of all the first time after a quiet spell: it took half a second to two (#188). diff --git a/src/db.rs b/src/db.rs index ac5d569..bee63a2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -99,22 +99,6 @@ async fn create_missing(orm: &sea_orm::DatabaseConnection) -> Result<()> { let sql = "ALTER TABLE deadair_prefs ADD COLUMN pause_ms BIGINT"; orm.execute_unprepared(sql).await.with_context(|| sql.to_owned())?; } - // DeadAir made a shortened copy of each file until it played the original through a - // playlist (#181). The copies go with their table, and the files are looked at again, which - // takes seconds each. Found only while the table is there, so nothing is written after. - let copies = Statement::from_string(orm.get_database_backend(), "SELECT path FROM deadair WHERE path IS NOT NULL"); - if let Ok(rows) = orm.query_all_raw(copies).await { - for r in rows { - // Only DeadAir's own files, `name.deadair.mp3`: anything else would be someone's original. - if let Ok(path) = r.try_get::("", "path") - && path.contains(".deadair.") - { - let _ = std::fs::remove_file(&path); - } - } - // IF EXISTS: the healthcheck's `ipx status` may open the database at the same moment. - orm.execute_unprepared("DROP TABLE IF EXISTS deadair").await.context("dropping DeadAir's copies")?; - } Ok(()) } @@ -2489,29 +2473,6 @@ pub fn now() -> i64 { mod tests { use super::*; - #[tokio::test] - async fn deadair_drops_its_old_copies_once() { - let db = Db::memory().await.unwrap(); - let dir = std::env::temp_dir().join(format!("ipx-deadair-old-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let (orig, copy) = (dir.join("a.mp3"), dir.join("a.deadair.mp3")); - std::fs::write(&orig, "original").unwrap(); - std::fs::write(©, "copy").unwrap(); - db.orm.execute_unprepared("CREATE TABLE deadair (enclosure_id BIGINT PRIMARY KEY, path TEXT)").await.unwrap(); - for (id, path) in [(1, ©), (2, &orig)] { - db.orm - .execute_unprepared(&format!("INSERT INTO deadair VALUES ({id}, '{}')", path.display())) - .await - .unwrap(); - } - create_missing(&db.orm).await.unwrap(); - assert!(!copy.exists(), "the copy goes"); - assert!(orig.exists(), "a path that is not DeadAir's own is never removed"); - assert!(db.orm.execute_unprepared("SELECT 1 FROM deadair").await.is_err(), "and the table"); - create_missing(&db.orm).await.unwrap(); - std::fs::remove_dir_all(&dir).unwrap(); - } - #[tokio::test] async fn deadair_looks_at_a_file_once_per_download_and_counts_an_episode_once() { let db = Db::memory().await.unwrap(); diff --git a/src/ipc.rs b/src/ipc.rs index ace7fa7..b1b5587 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -144,7 +144,12 @@ impl Emitter { // line per feed per tick for "not due yet" would push everything worth reading // out of the buffer within a few minutes. log_event(&e, self.tx.is_some()); - if let Some(tx) = &self.tx { + // A feed not due changes nothing a listener shows, and a scan has one for nearly every + // feed: 1,500 at once overflowed the channel's 1,024 for a page slow to read (#197). + let routine = matches!(&e, Event::FeedSkip { reason, .. } if reason.starts_with("not due")); + if let Some(tx) = &self.tx + && !routine + { // An error here only means nobody is listening yet. let _ = tx.send(e.clone()); } @@ -362,6 +367,17 @@ pub async fn proxy(path: &Path, cmd: &Command) -> Result<()> { mod tests { use super::*; + #[test] + fn a_feed_not_due_stays_off_the_live_updates() { + let (tx, mut rx) = broadcast::channel(4); + let out = Emitter::socket(tx, false); + for i in 0..10 { + out.emit(Event::FeedSkip { feed: format!("f{i}"), reason: "not due for 5m".into() }); + } + out.emit(Event::FeedSkip { feed: "g".into(), reason: "not modified".into() }); + assert!(matches!(rx.try_recv(), Ok(Event::FeedSkip { feed, .. }) if feed == "g"), "nothing missed"); + } + #[test] fn a_feed_that_moved_says_so_on_the_wire_and_in_words() { let e = Event::FeedMoved { feed: "x".into(), from: "http://a/f".into(), to: "https://a/f".into() }; diff --git a/src/web.rs b/src/web.rs index 66de803..ffa0be4 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1970,15 +1970,15 @@ async fn events( Sse::new(futures_util::StreamExt::flatten(stream)).keep_alive(axum::response::sse::KeepAlive::default()) } -/// The feed an event changed what the list shows of: its counts, error or last check. Not the -/// routine skip of a feed not due, dozens a minute that change nothing. +/// The feed an event changed what the list shows of: its counts, error or last check. The +/// routine skip of a feed not due never reaches here (`Emitter::emit`). fn changed_feed(ev: &Event) -> Option<&str> { match ev { Event::FeedDone { feed, .. } | Event::FeedError { feed, .. } | Event::FeedMoved { feed, .. } | Event::DownloadDone { feed, .. } => Some(feed), - Event::FeedSkip { feed, reason } if !reason.starts_with("not due") => Some(feed), + Event::FeedSkip { feed, .. } => Some(feed), _ => None, } }