Revert device state on all capture run failure paths

This commit is contained in:
Jan-Peter von Hunnius 2026-10-02 08:51:51 +02:00
parent 119a59f145
commit 75589d2383
2 changed files with 49 additions and 37 deletions

View file

@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `plans/code-review-v2.md` (completed action items) - `plans/code-review-v2.md` (completed action items)
### Fixed ### Fixed
- `capture run` no longer leaves BlueStacks offline when a step fails after the device is set up: CA injection, proxy listener bind, proxy/reverse setup, and game launch errors — and Ctrl+C during setup — now run the same device revert as the happy path instead of skipping it (`revert_device_state` is now unconditional once the device is connected)
- `trash.csv` Config column now lists only the copies to delete: for cars where just one tune is useless (e.g. owned 332 + best-everywhere 323) only the dead tune is shown instead of all owned configs - `trash.csv` Config column now lists only the copies to delete: for cars where just one tune is useless (e.g. owned 332 + best-everywhere 323) only the dead tune is shown instead of all owned configs
- Cars the records site knows but has no times for (typically new prize cars, returned as `200` with an empty body) no longer abort `fetch` after 3 retries — they are skipped with an info log - Cars the records site knows but has no times for (typically new prize cars, returned as `200` with an empty body) no longer abort `fetch` after 3 retries — they are skipped with an info log
- Empty timing files are now treated like missing files instead of producing `EOF while parsing a value` parse errors - Empty timing files are now treated like missing files instead of producing `EOF while parsing a value` parse errors

View file

@ -695,43 +695,8 @@ async fn command_run(config: &CaptureConfig) -> Result<()> {
if sdk_level > 28 { if sdk_level > 28 {
println!("warning: Android {sdk_level} detected, the tmpfs CA injection is only tested up to Android 9"); println!("warning: Android {sdk_level} detected, the tmpfs CA injection is only tested up to Android 9");
} }
inject_ca(config, &cert_pem, &project_root)?; let outcome = tokio::select! {
result = capture_session(config, &project_root, &cert_pem, &key_pem) => result,
let listener = tokio::net::TcpListener::bind(std::net::SocketAddr::from((
[127, 0, 0, 1],
config.proxy.port,
)))
.await
.map_err(|error| {
format!(
"cannot listen on 127.0.0.1:{} (HTTP Toolkit still running?): {error}",
config.proxy.port
)
})?;
let state = Arc::new(CaptureState {
found: AtomicBool::new(false),
done: tokio::sync::Notify::new(),
project_root: project_root.clone(),
output: output_path(config, &project_root),
pending: std::sync::Mutex::new(None),
});
let proxy =
build_proxy(listener, &cert_pem, &key_pem, CaptureHandler { state: state.clone() })?;
tokio::spawn(proxy);
set_device_proxy(config, config.proxy.port)?;
setup_adb_reverse(config, config.proxy.port)?;
println!("proxy active on 127.0.0.1:{}, launching {GAME_PACKAGE} ...", config.proxy.port);
launch_game(config)?;
println!(
"waiting for the garage request (timeout {}s, Ctrl+C to abort)",
config.capture.timeout_secs
);
let outcome: Result<()> = tokio::select! {
_ = state.done.notified() => Ok(()),
_ = tokio::time::sleep(Duration::from_secs(config.capture.timeout_secs)) => Err("timed out waiting for the garage request".into()),
_ = tokio::signal::ctrl_c() => Err("aborted".into()), _ = tokio::signal::ctrl_c() => Err("aborted".into()),
}; };
@ -754,6 +719,52 @@ async fn command_run(config: &CaptureConfig) -> Result<()> {
} }
} }
async fn capture_session(
config: &CaptureConfig,
project_root: &Path,
cert_pem: &str,
key_pem: &str,
) -> Result<()> {
inject_ca(config, cert_pem, project_root)?;
let listener = tokio::net::TcpListener::bind(std::net::SocketAddr::from((
[127, 0, 0, 1],
config.proxy.port,
)))
.await
.map_err(|error| {
format!(
"cannot listen on 127.0.0.1:{} (HTTP Toolkit still running?): {error}",
config.proxy.port
)
})?;
let state = Arc::new(CaptureState {
found: AtomicBool::new(false),
done: tokio::sync::Notify::new(),
project_root: project_root.to_path_buf(),
output: output_path(config, project_root),
pending: std::sync::Mutex::new(None),
});
let proxy = build_proxy(listener, cert_pem, key_pem, CaptureHandler { state: state.clone() })?;
tokio::spawn(proxy);
set_device_proxy(config, config.proxy.port)?;
setup_adb_reverse(config, config.proxy.port)?;
println!("proxy active on 127.0.0.1:{}, launching {GAME_PACKAGE} ...", config.proxy.port);
launch_game(config)?;
println!(
"waiting for the garage request (timeout {}s, Ctrl+C to abort)",
config.capture.timeout_secs
);
tokio::select! {
_ = state.done.notified() => Ok(()),
_ = tokio::time::sleep(Duration::from_secs(config.capture.timeout_secs)) =>
Err("timed out waiting for the garage request".into()),
}
}
async fn command_setup(config: &CaptureConfig) -> Result<()> { async fn command_setup(config: &CaptureConfig) -> Result<()> {
let project_root = std::env::current_dir()?; let project_root = std::env::current_dir()?;
let (cert_pem, _) = load_or_create_ca(&project_root)?; let (cert_pem, _) = load_or_create_ca(&project_root)?;