fix(daemon): park vanished grabs instead of re-grabbing
Overrides #86: removing a torrent by hand in Transmission is human intent, not a gap to refill. Clear `wanted` and mark the target `parked` (a new movies/episodes state) rather than reopening it as `missing`, so neither targeted search nor RSS matching pick it back up. Blacklist stays untouched, since the release never failed policy. Closes #108
This commit is contained in:
@@ -399,7 +399,7 @@ impl ImportAction {
|
||||
) -> Result<Option<Vec<PathBuf>>, ImportError> {
|
||||
let Some(content) = self.transmission.torrent_content(infohash).await? else {
|
||||
// Gone from Transmission — the caller marks the grab vanished
|
||||
// and reopens the gap (§86).
|
||||
// and parks the target (#108).
|
||||
tracing::warn!(
|
||||
grab_id,
|
||||
infohash,
|
||||
@@ -660,11 +660,13 @@ impl ImportAction {
|
||||
))
|
||||
}
|
||||
|
||||
/// §86: a `downloaded` grab whose torrent Transmission no longer reports
|
||||
/// — removed by hand, not a policy failure. Marked `vanished` rather
|
||||
/// than `failed` so it does not feed the `needs_decision` queue
|
||||
/// (attention.rs), and nothing is blacklisted, since the release itself
|
||||
/// never failed policy.
|
||||
/// §86/#108: a `downloaded` grab whose torrent Transmission no longer
|
||||
/// reports — removed by hand, not a policy failure. Marked `vanished`
|
||||
/// rather than `failed` so it does not feed the `needs_decision` queue
|
||||
/// (attention.rs). Nothing is blacklisted, since the release itself
|
||||
/// never failed policy, and the movie is parked rather than reopened,
|
||||
/// since removing a torrent by hand is human intent, not a gap to
|
||||
/// refill.
|
||||
async fn vanish(&self, database: &Db, pending: &PendingImport) -> Result<Outcome, ImportError> {
|
||||
sqlx::query!(
|
||||
"UPDATE grabs SET state = 'vanished' WHERE id = ?",
|
||||
@@ -672,25 +674,25 @@ impl ImportAction {
|
||||
)
|
||||
.execute(database.pool())
|
||||
.await?;
|
||||
crate::grab::reopen_target(database, "movie", pending.movie_id).await?;
|
||||
crate::grab::park_target(database, "movie", pending.movie_id).await?;
|
||||
|
||||
tracing::warn!(
|
||||
grab_id = pending.grab_id,
|
||||
movie_id = pending.movie_id,
|
||||
title = pending.title,
|
||||
release = pending.release_name,
|
||||
"torrent vanished from Transmission; gap reopened"
|
||||
"torrent vanished from Transmission; movie parked"
|
||||
);
|
||||
Ok(Outcome::new(
|
||||
format!(
|
||||
"grab {} downloaded, torrent vanished from Transmission",
|
||||
pending.grab_id
|
||||
),
|
||||
format!("reopened movie {}", pending.movie_id),
|
||||
format!("parked movie {}", pending.movie_id),
|
||||
))
|
||||
}
|
||||
|
||||
/// TV counterpart of [`Self::vanish`]: reopens the episode, or the
|
||||
/// TV counterpart of [`Self::vanish`]: parks the episode, or the
|
||||
/// still-downloading episodes of a season pack.
|
||||
async fn vanish_tv(
|
||||
&self,
|
||||
@@ -707,20 +709,20 @@ impl ImportAction {
|
||||
Some(episode_id) => ("episode", episode_id),
|
||||
None => ("season", pending.season_id),
|
||||
};
|
||||
crate::grab::reopen_target(database, target_kind, target_id).await?;
|
||||
crate::grab::park_target(database, target_kind, target_id).await?;
|
||||
|
||||
tracing::warn!(
|
||||
grab_id = pending.grab_id,
|
||||
series = pending.series_title,
|
||||
release = pending.release_name,
|
||||
"torrent vanished from Transmission; gap reopened"
|
||||
"torrent vanished from Transmission; target parked"
|
||||
);
|
||||
Ok(Outcome::new(
|
||||
format!(
|
||||
"grab {} downloaded, torrent vanished from Transmission",
|
||||
pending.grab_id
|
||||
),
|
||||
format!("reopened {target_kind} {target_id}"),
|
||||
format!("parked {target_kind} {target_id}"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1691,11 +1693,12 @@ mod tests {
|
||||
assert!(outcomes.is_empty());
|
||||
}
|
||||
|
||||
/// §86: a `downloaded` grab whose torrent Transmission no longer reports
|
||||
/// — removed by hand, not a policy failure — is marked `vanished` and
|
||||
/// reopens the movie as a gap, without touching the blacklist.
|
||||
/// #108, overriding §86: a `downloaded` grab whose torrent Transmission
|
||||
/// no longer reports — removed by hand, not a policy failure — is marked
|
||||
/// `vanished` and parks the movie (`wanted` cleared) instead of
|
||||
/// reopening it as a gap, without touching the blacklist.
|
||||
#[tokio::test]
|
||||
async fn a_vanished_downloaded_grab_reopens_the_gap() {
|
||||
async fn a_vanished_downloaded_grab_parks_the_movie() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let database = Db::connect(dir.path().join("arr.db")).await.unwrap();
|
||||
database.migrate().await.unwrap();
|
||||
@@ -1752,11 +1755,13 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(grab_state, "vanished");
|
||||
let movie_state: String = sqlx::query_scalar("SELECT state FROM movies WHERE id = 1")
|
||||
.fetch_one(database.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(movie_state, "missing");
|
||||
let (movie_state, wanted): (String, bool) =
|
||||
sqlx::query_as("SELECT state, wanted FROM movies WHERE id = 1")
|
||||
.fetch_one(database.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(movie_state, "parked");
|
||||
assert!(!wanted, "the leaf intent is cleared, DESIGN.md §4.1");
|
||||
let blacklisted: i64 = sqlx::query_scalar("SELECT count(*) FROM blacklist")
|
||||
.fetch_one(database.pool())
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user