diff --git a/src-tauri/src/ftp.rs b/src-tauri/src/ftp.rs index e06a902..9f06537 100644 --- a/src-tauri/src/ftp.rs +++ b/src-tauri/src/ftp.rs @@ -52,7 +52,9 @@ impl Ftp { .next() .ok_or_else(|| format!("Can't resolve {host}"))?; let stream = TcpStream::connect_timeout(&addr, connect_timeout) - .map_err(|e| format!("No FTP server at {host}:{port} ({e})"))?; + .map_err(|e| { + format!("Can't reach the PS5 at {host}:{port} ({e}). Check it's on, jailbroken, and running an FTP server.") + })?; stream.set_read_timeout(Some(TIMEOUT)).ok(); stream.set_write_timeout(Some(TIMEOUT)).ok(); stream.set_nodelay(true).ok(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6a862c5..ca6902e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -108,6 +108,16 @@ async fn ps5_status(value: Option) -> Result Option { + let mut p = PathBuf::from(path); + while !p.exists() { + p = p.parent()?.to_path_buf(); + } + pipeline::free_space(&p) +} + #[tauri::command] async fn discover_ps5() -> Result, String> { blocking(discover::discover).await @@ -152,7 +162,8 @@ pub fn run() { remove_job, resume_job, ps5_status, - discover_ps5 + discover_ps5, + free_space ]) .run(tauri::generate_context!()) .expect("error while running Prospack"); diff --git a/src-tauri/src/pipeline.rs b/src-tauri/src/pipeline.rs index 9b8cee8..c49d1cd 100644 --- a/src-tauri/src/pipeline.rs +++ b/src-tauri/src/pipeline.rs @@ -950,6 +950,11 @@ mod e2e { }; settings::save(&s).unwrap(); + // Wizard checks against the fake PS5: ShadowMountPlus yes, Game Compressor not yet. + let st = ps5::status(&s); + assert!(st.online && st.shadowmount && !st.game_compressor, "{st:?}"); + assert!(ps5::status(&Settings { ps5_host: "".into(), ..s.clone() }).error.unwrap().contains("No PS5 address set")); + let engine = Arc::new(Engine::new(&settings::data_dir())); let q = Queue::new(Arc::new(|_, _| {}), engine); let id = q.add(crate::dump::inspect(&dump).unwrap()).unwrap(); @@ -979,6 +984,7 @@ mod e2e { assert_eq!(rec["sourceSize"], local); assert_eq!(rec["compressionSourceSize"], j.dump.total_bytes); assert!(ps5_root.join("data/GameCompressor/validations/PPSA00001.json").is_file()); + assert!(ps5::status(&s).game_compressor, "the record created /data/GameCompressor"); let ml = std::fs::read_to_string(ps5_root.join("data/shadowmount/manual.lst")).unwrap(); assert_eq!(ml, "/data/homebrew/PPSA00001.ffpfsc\n"); assert!(dump.join("ampr_emu.index").is_file(), "MkPFS should have generated the AMPR index"); diff --git a/src-tauri/src/ps5.rs b/src-tauri/src/ps5.rs index fa6bbfe..02454cf 100644 --- a/src-tauri/src/ps5.rs +++ b/src-tauri/src/ps5.rs @@ -21,10 +21,15 @@ pub struct Ps5Status { pub free_bytes: Option, pub total_bytes: Option, pub shadowmount: bool, + /// /data/GameCompressor exists (optional; used for the validated badge). + pub game_compressor: bool, } pub fn connect(s: &Settings) -> Result { - Ftp::connect(&s.ps5_host, s.ftp_port, Duration::from_secs(4)) + if s.ps5_host.trim().is_empty() { + return Err("No PS5 address set. Open Settings → Run setup again, or enter your PS5's IP in Settings.".into()); + } + Ftp::connect(s.ps5_host.trim(), s.ftp_port, Duration::from_secs(4)) } /// Read-only check: FTP login + NOOP, ShadowMountPlus config present, and free space from @@ -35,14 +40,19 @@ pub fn status(s: &Settings) -> Ps5Status { Ok(f) => f, Err(e) => return Ps5Status { error: Some(e), ..Default::default() }, }; + // (connect() already rejects an empty address with a helpful message.) if let Err(e) = ftp.noop() { return Ps5Status { error: Some(e), ..Default::default() }; } let latency_ms = Some(t0.elapsed().as_millis() as u64); let shadowmount = ftp.size(&format!("{SMP_DIR}/config.ini")).ok().flatten().is_some(); + let game_compressor = ftp + .list("/data") + .map(|entries| entries.iter().any(|e| e.is_dir && e.name == "GameCompressor")) + .unwrap_or(false); ftp.quit(); let (free_bytes, total_bytes) = space(s).unwrap_or((None, None)); - Ps5Status { online: true, latency_ms, error: None, free_bytes, total_bytes, shadowmount } + Ps5Status { online: true, latency_ms, error: None, free_bytes, total_bytes, shadowmount, game_compressor } } /// GET /api/space?path=/data on the PS5 web file manager. Optional; a missing server is fine.