refactor: delegate Miruro playback resolution to Terebi

This commit is contained in:
2026-10-22 09:45:03 +02:00
parent f21a1fbe63
commit 3fda432dbd
15 changed files with 248 additions and 825 deletions
+21 -128
View File
@@ -1,12 +1,11 @@
use super::*;
use ratatui::crossterm::event::KeyModifiers;
use std::{
collections::BTreeMap,
fs,
io::Write,
sync::atomic::{AtomicU64, Ordering},
};
use tubular::miruro::{Detail, SourceDocument};
use tubular::miruro::Detail;
struct Directory(PathBuf);
impl Directory {
@@ -53,16 +52,6 @@ fn series(media: &Media) -> miruro_browser::Series {
serde_json::json!({"episode_number":7, "absolute_number":53, "title":null, "duration_seconds":null}),
] }).unwrap()
}
fn source(video: &Video, token: &str) -> SourceDocument {
SourceDocument {
version: 1,
url: format!("https://vault.keeply.top/master.m3u8?token={token}"),
headers: BTreeMap::new(),
identity: Identity::from_key(&video.id).unwrap(),
title: Some(video.title.clone()),
subtitles: Vec::new(),
}
}
fn catalog_results(browser: &mut Browser) {
browser.accept(
browser.generation,
@@ -77,19 +66,6 @@ fn enter_series(browser: &mut Browser) {
let result = series(media);
browser.accept(browser.generation, Ok(Response::Series(result)));
}
fn sources(directory: &Directory) -> Vec<Value> {
let path = directory.0.join("playback-sessions");
if !path.exists() {
return Vec::new();
}
fs::read_dir(path)
.unwrap()
.map(|entry| {
serde_json::from_slice(&fs::read(entry.unwrap().path().join("source.json")).unwrap())
.unwrap()
})
.collect()
}
#[test]
fn metadata_popup_scrolls_and_closes_without_moving_the_episode_selection() {
@@ -188,7 +164,7 @@ fn catalog_series_back_forward_keep_native_query_selection_and_actual_episodes()
}
#[test]
fn latest_intent_and_navigation_reject_stale_catalog_series_and_sources() {
fn latest_intent_and_navigation_reject_stale_catalog_and_series() {
let directory = Directory::new();
let mut browser = directory.browser(Some("old"));
let old = browser.generation;
@@ -214,30 +190,10 @@ fn latest_intent_and_navigation_reject_stale_catalog_series_and_sources() {
};
let data = series(media);
browser.accept(browser.generation, Ok(Response::Series(data)));
let video = browser.screen().selected_video().unwrap();
browser.open();
let stale = browser.generation;
browser.history(false);
browser.accept(
stale,
Ok(Response::Source(
video.clone(),
Box::new(source(&video, "stale")),
)),
assert_eq!(
browser.screen().selected_video().unwrap().id,
"miruro:first:3"
);
assert!(sources(&directory).is_empty());
browser.history(true);
browser.open();
let stale = browser.generation;
browser.refresh();
browser.accept(
stale,
Ok(Response::Source(
video.clone(),
Box::new(source(&video, "refreshed-away")),
)),
);
assert!(sources(&directory).is_empty());
}
#[test]
@@ -274,12 +230,9 @@ fn manual_watch_status_refreshes_filters_and_preserves_other_providers() {
assert!(!browser.state.is_watched(&episode.id));
browser.preview = None;
browser.collection(CollectionKind::Watching);
browser.open();
assert!(browser.pending.is_some());
browser.key(KeyEvent::new(KeyCode::Char('M'), KeyModifiers::NONE));
assert!(browser.state.is_watched(&episode.id));
assert_eq!(browser.screen().len(), 0);
assert!(browser.pending.is_none());
browser.collection(CollectionKind::WatchLater);
browser.key(KeyEvent::new(KeyCode::Char('f'), KeyModifiers::NONE));
assert_eq!(browser.screen().len(), 0);
@@ -336,8 +289,6 @@ fn offline_collections_and_filtered_bulk_removal_never_touch_youtube() {
}
browser.collection(CollectionKind::Saved);
assert_eq!(browser.screen().len(), 1);
browser.open();
assert!(matches!(&browser.pending, Some(Request::Source(video)) if video.id == episode.id));
browser.remove(true);
assert_eq!(
browser.removal.as_ref().unwrap().1.as_slice(),
@@ -351,10 +302,8 @@ fn offline_collections_and_filtered_bulk_removal_never_touch_youtube() {
.state
.contains(CollectionKind::WatchLater, &episode.id)
);
let (_, report) = browser
.state
.miruro_session(&episode, &source(&episode, "progress"))
.unwrap();
let report = browser.state.progress_file(&episode).unwrap();
crate::video_state::register_identity(&report, &episode.id).unwrap();
let mut output = fs::OpenOptions::new();
output.write(true).create_new(true);
#[cfg(unix)]
@@ -391,89 +340,33 @@ fn offline_collections_and_filtered_bulk_removal_never_touch_youtube() {
}
#[test]
fn selected_episode_and_offline_reopen_write_fresh_validated_source_sessions() {
fn launcher_failure_does_not_mark_series_or_offline_episodes_as_started() {
let directory = Directory::new();
let mut browser = directory.browser(Some("anime"));
catalog_results(&mut browser);
enter_series(&mut browser);
let first = browser.screen().selected_video().unwrap();
browser.open();
let stale = browser.generation;
browser.select(Action::VideoNext);
browser.accept(
stale,
Ok(Response::Source(
first.clone(),
Box::new(source(&first, "not-selected")),
)),
);
assert!(sources(&directory).is_empty());
let selected = browser.screen().selected_video().unwrap();
assert_eq!(Identity::from_key(&selected.id).unwrap().episode, 7);
browser.save(CollectionKind::Saved);
browser.collection(CollectionKind::Saved);
browser.open();
browser.pending = None;
browser.accept(
browser.generation,
Ok(Response::Source(
selected.clone(),
Box::new(source(&first, "wrong-identity")),
)),
);
assert!(sources(&directory).is_empty());
browser.open();
browser.pending = None;
let mut invalid = source(&selected, "private-token");
invalid
.headers
.insert("X-Invalid".into(), "private-header\nvalue".into());
browser.accept(
browser.generation,
Ok(Response::Source(selected.clone(), Box::new(invalid))),
);
assert!(sources(&directory).is_empty());
assert!(!browser.status.contains("private-token"));
assert!(!browser.status.contains("private-header"));
for token in ["fresh-one", "fresh-two"] {
browser.open();
assert!(
matches!(&browser.pending, Some(Request::Source(video)) if video.id == selected.id && video.title == selected.title)
);
browser.pending = None; // Deliver the real source response without a network request.
browser.accept(
browser.generation,
Ok(Response::Source(
selected.clone(),
Box::new(source(&selected, token)),
)),
);
}
let written = sources(&directory);
assert_eq!(written.len(), 2);
assert!(
written
.iter()
.all(|source| source["identity"]["episode"] == 7
&& source["identity"]["media_id"] == "first")
);
assert!(
written
.iter()
.any(|source| source["url"].as_str().unwrap().ends_with("fresh-one"))
);
assert!(
written
.iter()
.any(|source| source["url"].as_str().unwrap().ends_with("fresh-two"))
);
assert!(!browser.state.is_watched(&selected.id)); // Spawn failure/CLI completion is not progress.
assert!(browser.screen().error.is_some());
assert!(!browser.state.is_watched(&selected.id));
assert!(
!browser
.state
.contains(CollectionKind::Watching, &selected.id)
);
assert!(browser.screen().error.is_some()); // Native launch errors remain visible.
browser.save(CollectionKind::Saved);
browser.collection(CollectionKind::Saved);
browser.open();
assert!(browser.screen().error.is_some());
assert!(!browser.state.is_watched(&selected.id));
assert!(
!browser
.state
.contains(CollectionKind::Watching, &selected.id)
);
assert!(browser.state.contains(CollectionKind::Saved, &selected.id));
}
#[test]