Add progress counter for car time downloads
Show a persistent single-line progress counter ("Downloading track times
for car 1234/7589") while fetch_times downloads timing data. A ProgressBar
from indicatif 0.17 is sized to the car list, cloned into each spawned
download task, and incremented after fetch_car completes so it tracks
completions rather than starts; Arc internals make this safe under the
concurrent semaphore. The bar is cleared via finish_and_clear() before
the error/warning logging loop so retry messages don't garble the line,
and indicatif renders nothing when stderr is not a TTY.
Also apply pending rustfmt reformatting to src/capture/windows.rs
(formatting only, no logic changes).
This commit is contained in:
parent
f829ad52a4
commit
cc73fe62fc
4 changed files with 136 additions and 74 deletions
|
|
@ -5,14 +5,14 @@ use std::sync::Arc;
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use base64::Engine;
|
||||
use http::request::Request;
|
||||
use http_body_util::BodyExt;
|
||||
use hudsucker::certificate_authority::RcgenAuthority;
|
||||
use hudsucker::rcgen::{
|
||||
BasicConstraints, CertificateParams, DnType, IsCa, Issuer, KeyPair, KeyUsagePurpose,
|
||||
PKCS_ECDSA_P256_SHA256,
|
||||
};
|
||||
use hudsucker::{Body, HttpContext, HttpHandler, Proxy, RequestOrResponse};
|
||||
use http::request::Request;
|
||||
use http_body_util::BodyExt;
|
||||
use md5::{Digest, Md5};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -74,11 +74,7 @@ impl Default for CaptureConfig {
|
|||
|
||||
impl Default for DeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
serial: "127.0.0.1:5555".to_string(),
|
||||
wait_secs: 180,
|
||||
shutdown_bluestacks: true,
|
||||
}
|
||||
Self { serial: "127.0.0.1:5555".to_string(), wait_secs: 180, shutdown_bluestacks: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,17 +96,13 @@ impl Default for ProxyConfig {
|
|||
|
||||
impl Default for CaptureSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
timeout_secs: 300,
|
||||
replay_timeout_secs: 30,
|
||||
}
|
||||
Self { timeout_secs: 300, replay_timeout_secs: 30 }
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_adb() -> String {
|
||||
if let Ok(local_app_data) = std::env::var("LOCALAPPDATA") {
|
||||
let candidate =
|
||||
format!("{local_app_data}\\Android\\Sdk\\platform-tools\\adb.exe");
|
||||
let candidate = format!("{local_app_data}\\Android\\Sdk\\platform-tools\\adb.exe");
|
||||
if Path::new(&candidate).exists() {
|
||||
return candidate;
|
||||
}
|
||||
|
|
@ -206,13 +198,11 @@ impl HttpHandler for CaptureHandler {
|
|||
&& host.contains("topdrives")
|
||||
&& path_and_query.contains("/sync")
|
||||
{
|
||||
let body_bytes = BodyExt::collect(std::mem::replace(
|
||||
request.body_mut(),
|
||||
Body::from(Vec::new()),
|
||||
))
|
||||
.await
|
||||
.map(|collected| collected.to_bytes().to_vec())
|
||||
.unwrap_or_default();
|
||||
let body_bytes =
|
||||
BodyExt::collect(std::mem::replace(request.body_mut(), Body::from(Vec::new())))
|
||||
.await
|
||||
.map(|collected| collected.to_bytes().to_vec())
|
||||
.unwrap_or_default();
|
||||
|
||||
let template = CapturedRequest {
|
||||
method: request.method().to_string(),
|
||||
|
|
@ -259,15 +249,13 @@ impl HttpHandler for CaptureHandler {
|
|||
return response;
|
||||
}
|
||||
|
||||
let bytes = match BodyExt::collect(std::mem::replace(
|
||||
response.body_mut(),
|
||||
Body::from(Vec::new()),
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(collected) => collected.to_bytes().to_vec(),
|
||||
Err(_) => return response,
|
||||
};
|
||||
let bytes =
|
||||
match BodyExt::collect(std::mem::replace(response.body_mut(), Body::from(Vec::new())))
|
||||
.await
|
||||
{
|
||||
Ok(collected) => collected.to_bytes().to_vec(),
|
||||
Err(_) => return response,
|
||||
};
|
||||
|
||||
if contains_marker(&bytes, GARAGE_MARKER) {
|
||||
if let Ok(json) = serde_json::from_slice::<serde_json::Value>(&bytes) {
|
||||
|
|
@ -321,7 +309,12 @@ async fn fetch_response(
|
|||
let lower = name.as_str().to_lowercase();
|
||||
if matches!(
|
||||
lower.as_str(),
|
||||
"host" | "content-length" | "accept-encoding" | "connection" | "proxy-connection" | "keep-alive"
|
||||
"host"
|
||||
| "content-length"
|
||||
| "accept-encoding"
|
||||
| "connection"
|
||||
| "proxy-connection"
|
||||
| "keep-alive"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -347,11 +340,10 @@ fn persist_capture(
|
|||
json: &serde_json::Value,
|
||||
template: Option<&CapturedRequest>,
|
||||
) -> Result<()> {
|
||||
let timestamp = time::OffsetDateTime::now_utc().format(
|
||||
&time::format_description::parse_borrowed::<2>(
|
||||
let timestamp =
|
||||
time::OffsetDateTime::now_utc().format(&time::format_description::parse_borrowed::<2>(
|
||||
"[year]-[month]-[day]T[hour]-[minute]-[second]",
|
||||
)?,
|
||||
)?;
|
||||
)?)?;
|
||||
let run_dir = project_root.join("captures").join(×tamp);
|
||||
std::fs::create_dir_all(&run_dir)?;
|
||||
|
||||
|
|
@ -561,19 +553,14 @@ fn inject_ca(config: &CaptureConfig, cert_pem: &str, project_root: &Path) -> Res
|
|||
let local_path = project_root.join("ca").join("topdrives-ca.pem");
|
||||
std::fs::write(&local_path, cert_pem)?;
|
||||
|
||||
let presence = adb_shell(
|
||||
config,
|
||||
&format!("test -f {store_path} && echo present || echo absent"),
|
||||
)?;
|
||||
let presence =
|
||||
adb_shell(config, &format!("test -f {store_path} && echo present || echo absent"))?;
|
||||
if presence.trim() == "present" {
|
||||
println!("CA already present in the system trust store");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
adb(
|
||||
config,
|
||||
&["push", local_path.to_string_lossy().as_ref(), staging_path],
|
||||
)?;
|
||||
adb(config, &["push", local_path.to_string_lossy().as_ref(), staging_path])?;
|
||||
adb_shell_root(
|
||||
config,
|
||||
"mkdir -p /data/local/tmp/cacerts-backup && cp /system/etc/security/cacerts/* /data/local/tmp/cacerts-backup/",
|
||||
|
|
@ -619,13 +606,15 @@ fn setup_adb_reverse(config: &CaptureConfig, port: u16) -> Result<()> {
|
|||
Err(_) => {
|
||||
let route = adb_shell(config, "ip route")?;
|
||||
let gateway = route.lines().find_map(|line| {
|
||||
line.strip_prefix("default via ")
|
||||
.and_then(|rest| rest.split(' ').next())
|
||||
line.strip_prefix("default via ").and_then(|rest| rest.split(' ').next())
|
||||
});
|
||||
match gateway {
|
||||
Some(gateway) => {
|
||||
println!("adb reverse failed, falling back to gateway address {gateway}:{port}");
|
||||
adb_shell(config, &format!("settings put global http_proxy {gateway}:{port}")).map(|_| ())
|
||||
println!(
|
||||
"adb reverse failed, falling back to gateway address {gateway}:{port}"
|
||||
);
|
||||
adb_shell(config, &format!("settings put global http_proxy {gateway}:{port}"))
|
||||
.map(|_| ())
|
||||
}
|
||||
None => Err("could not set up adb reverse and no default gateway found".into()),
|
||||
}
|
||||
|
|
@ -638,15 +627,7 @@ fn launch_game(config: &CaptureConfig) -> Result<()> {
|
|||
std::thread::sleep(Duration::from_secs(2));
|
||||
adb(
|
||||
config,
|
||||
&[
|
||||
"shell",
|
||||
"monkey",
|
||||
"-p",
|
||||
GAME_PACKAGE,
|
||||
"-c",
|
||||
"android.intent.category.LAUNCHER",
|
||||
"1",
|
||||
],
|
||||
&["shell", "monkey", "-p", GAME_PACKAGE, "-c", "android.intent.category.LAUNCHER", "1"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -671,8 +652,9 @@ fn build_proxy(
|
|||
Ok(Box::pin(proxy.start()))
|
||||
}
|
||||
|
||||
type ProxyFuture =
|
||||
std::pin::Pin<Box<dyn std::future::Future<Output = std::result::Result<(), hudsucker::Error>> + Send>>;
|
||||
type ProxyFuture = std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = std::result::Result<(), hudsucker::Error>> + Send>,
|
||||
>;
|
||||
|
||||
async fn command_run(config: &CaptureConfig) -> Result<()> {
|
||||
let project_root = std::env::current_dir()?;
|
||||
|
|
@ -704,15 +686,13 @@ async fn command_run(config: &CaptureConfig) -> Result<()> {
|
|||
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() })?;
|
||||
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
|
||||
);
|
||||
println!("proxy active on 127.0.0.1:{}, launching {GAME_PACKAGE} ...", config.proxy.port);
|
||||
launch_game(config)?;
|
||||
|
||||
println!(
|
||||
|
|
@ -770,7 +750,12 @@ async fn command_replay(config: &CaptureConfig) -> Result<()> {
|
|||
let lower = name.to_lowercase();
|
||||
if matches!(
|
||||
lower.as_str(),
|
||||
"host" | "content-length" | "accept-encoding" | "connection" | "proxy-connection" | "keep-alive"
|
||||
"host"
|
||||
| "content-length"
|
||||
| "accept-encoding"
|
||||
| "connection"
|
||||
| "proxy-connection"
|
||||
| "keep-alive"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -799,12 +784,7 @@ async fn command_replay(config: &CaptureConfig) -> Result<()> {
|
|||
);
|
||||
}
|
||||
let json: serde_json::Value = serde_json::from_slice(&bytes)?;
|
||||
persist_capture(
|
||||
&project_root,
|
||||
&output_path(config, &project_root),
|
||||
&json,
|
||||
Some(&template),
|
||||
)?;
|
||||
persist_capture(&project_root, &output_path(config, &project_root), &json, Some(&template))?;
|
||||
println!("replay finished successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -899,9 +879,7 @@ mod tests {
|
|||
let filename = android_ca_filename(CA_COMMON_NAME);
|
||||
assert_eq!(filename.len(), 10);
|
||||
assert!(filename.ends_with(".0"));
|
||||
assert!(filename[..8]
|
||||
.chars()
|
||||
.all(|character| character.is_ascii_hexdigit()));
|
||||
assert!(filename[..8].chars().all(|character| character.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue