From 75589d23830861fdba489b1db4ca7489d3ea3648 Mon Sep 17 00:00:00 2001 From: Jan-Peter von Hunnius Date: Fri, 2 Oct 2026 08:51:51 +0200 Subject: [PATCH] Revert device state on all capture run failure paths --- CHANGELOG.md | 1 + src/capture/windows.rs | 85 ++++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecbef7f..df66bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) ### 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 - 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 diff --git a/src/capture/windows.rs b/src/capture/windows.rs index 7094219..f50281a 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -695,43 +695,8 @@ async fn command_run(config: &CaptureConfig) -> Result<()> { if sdk_level > 28 { 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 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()), + let outcome = tokio::select! { + result = capture_session(config, &project_root, &cert_pem, &key_pem) => result, _ = 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<()> { let project_root = std::env::current_dir()?; let (cert_pem, _) = load_or_create_ca(&project_root)?;