use std::path::{Path, PathBuf}; use tauri::Manager; pub fn resolve_bundled_binary_path( app_handle: &tauri::AppHandle, engine: &str, ) -> Result { let binary_name = crate::platform::engine_binary_name(engine); let target = crate::platform::target_triple(); if let Ok(resource_dir) = app_handle.path().resource_dir() { for candidate in packaged_candidates(&resource_dir, &target, &binary_name) { if candidate.is_file() { log::info!("Resolved bundled '{}' at: {:?}", engine, candidate); return Ok(candidate); } } } if let Ok(exe_path) = std::env::current_exe() { for candidate in executable_relative_candidates(&exe_path, &target, &binary_name) { if candidate.is_file() { log::info!("Resolved bundled '{}' at: {:?}", engine, candidate); return Ok(candidate); } } } if let Ok(cwd) = std::env::current_dir() { for candidate in development_candidates(&cwd, &target, &binary_name) { if candidate.is_file() { let absolute = candidate.canonicalize().map_err(|error| { format!("Failed to canonicalize '{}': {error}", candidate.display()) })?; log::info!("Resolved bundled '{}' at: {:?}", engine, absolute); return Ok(absolute); } } } Err(format!( "Could not find bundled binary '{}' for target '{}' (expected name: {})", engine, target, binary_name )) } fn packaged_candidates(resource_dir: &Path, target: &str, binary_name: &str) -> Vec { let mut candidates = vec![ resource_dir .join("engine-dist") .join(target) .join(binary_name), resource_dir.join("engines").join(target).join(binary_name), ]; if cfg!(target_os = "macos") { candidates.push(resource_dir.join("binaries").join(binary_name)); candidates.push(resource_dir.join(binary_name)); } candidates } fn executable_relative_candidates( executable: &Path, target: &str, binary_name: &str, ) -> Vec { let Some(executable_dir) = executable.parent() else { return Vec::new(); }; let mut candidates = vec![ executable_dir .join("engine-dist") .join(target) .join(binary_name), executable_dir .join("engines") .join(target) .join(binary_name), ]; if cfg!(target_os = "macos") { if let Some(contents_dir) = executable_dir.parent() { candidates.push( contents_dir .join("Resources") .join("engine-dist") .join(target) .join(binary_name), ); candidates.push( contents_dir .join("Resources") .join("binaries") .join(binary_name), ); } } candidates } fn development_candidates(cwd: &Path, target: &str, binary_name: &str) -> Vec { let roots = [cwd.to_path_buf(), cwd.join("src-tauri")]; let mut candidates = Vec::new(); for root in roots { candidates.push(root.join("engine-dist").join(target).join(binary_name)); candidates.push(root.join("binaries").join(target).join(binary_name)); if cfg!(target_os = "macos") { candidates.push(root.join("binaries").join(binary_name)); } } candidates } pub fn ytdlp_internal_dir(binary_path: &Path) -> Option { binary_path.parent().map(|parent| parent.join("_internal")) } #[cfg(test)] mod tests { use super::{development_candidates, packaged_candidates}; use std::path::Path; #[test] fn canonical_packaged_layout_is_target_scoped() { let candidates = packaged_candidates( Path::new("/resources"), "x86_64-unknown-linux-gnu", "yt-dlp-x86_64-unknown-linux-gnu", ); assert_eq!( candidates[0], Path::new( "/resources/engine-dist/x86_64-unknown-linux-gnu/yt-dlp-x86_64-unknown-linux-gnu" ) ); } #[test] fn canonical_development_layout_is_target_scoped() { let candidates = development_candidates( Path::new("/repo"), "x86_64-pc-windows-msvc", "aria2c-x86_64-pc-windows-msvc.exe", ); assert_eq!( candidates[0], Path::new("/repo/engine-dist/x86_64-pc-windows-msvc/aria2c-x86_64-pc-windows-msvc.exe") ); } }