use crate::fetch::html_redirect::extract_linkedin_redirect;
use crate::{fetch, resolve_entry_from_url};
use std::fmt;
use std::{fs, io::Write, path::Path};
use uxn_tal_defined::ProtocolParser;
#[derive(Debug)]
pub struct DownloaderRedirect {
pub url: String,
}
impl fmt::Display for DownloaderRedirect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HTML redirect detected: {}", self.url)
}
}
impl std::error::Error for DownloaderRedirect {}
pub fn resolve_and_fetch_entry(
raw: &str,
) -> Result<(std::path::PathBuf, std::path::PathBuf), Box<dyn std::error::Error>> {
let url = if raw.starts_with("uxntal:") {
let parsed = ProtocolParser::parse(raw);
if !parsed.url.is_empty() {
parsed.url.clone()
} else {
raw.to_string()
}
} else {
raw.to_string()
};
if fetch::parse_repo(&url).is_some() {
let roms_dir =
crate::paths::uxntal_roms_get_path().ok_or("Failed to get uxntal roms directory")?;
let rom_dir = roms_dir.join(format!("{}", crate::util::hash_url(&url)));
std::fs::create_dir_all(&rom_dir)?;
let res = fetch::fetch_repo_tree(&url, &rom_dir)?;
Ok((res.entry_local, rom_dir))
} else {
resolve_entry_from_url(raw)
}
}
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
pub fn http_get(url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if url.contains("git.sr.ht") {
if let Ok(out) = std::process::Command::new("curl")
.args(["-L", "-sS", url])
.output()
{
if out.status.success() {
return Ok(out.stdout);
} else {
return Err(format!(
"curl failed with exit code {} for {}",
out.status.code().unwrap_or(-1),
url
)
.into());
}
} else {
return Err("Failed to spawn curl process".into());
}
}
let resp = reqwest::blocking::get(url)?;
let final_url = resp.url().to_string();
let status = resp.status();
eprintln!("[DEBUG] HTTP status: {}", status);
eprintln!("[DEBUG] Final URL: {}", final_url);
eprintln!("[DEBUG] Response headers:");
for (k, v) in resp.headers().iter() {
eprintln!(" {}: {:?}", k, v);
}
let is_html_content = resp
.headers()
.get("content-type")
.is_some_and(|v| v.to_str().unwrap_or("").contains("text/html"));
let body_bytes = resp.bytes()?;
let body_str = String::from_utf8_lossy(&body_bytes);
eprintln!(
"[DEBUG] Response body (first 512 bytes):\n{}",
&body_str[..body_str.len().min(512)]
);
if status.is_success() && is_html_content {
if let Some(href) = extract_linkedin_redirect(&body_str) {
eprintln!("[DEBUG] Found HTML redirect link: {}", href);
return Err(Box::new(DownloaderRedirect { url: href }));
}
}
if final_url != url {
eprintln!(
"[DEBUG] Redirect detected: {} -> {} (status: {})",
url, final_url, status
);
}
if status.as_u16() == 403 {
if let Ok(out) = std::process::Command::new("curl")
.args(["-L", "-sS", url])
.output()
{
if out.status.success() {
return Ok(out.stdout);
} else {
return Err(format!(
"curl failed with exit code {} for {}",
out.status.code().unwrap_or(-1),
url
)
.into());
}
}
}
if status.as_u16() == 404 {
return Err(format!("HTTP 404 Not Found for {}", final_url).into());
}
if !status.is_success() {
return Err(format!("HTTP {} for {}", status, final_url).into());
}
Ok(body_bytes.to_vec())
}
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub fn http_get(_url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
Err("http_get is not available in browser WASM".into())
}
pub fn write_bytes(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
if let Some(p) = path.parent() {
fs::create_dir_all(p)?;
}
let mut f = fs::File::create(path)?;
f.write_all(bytes)?;
Ok(())
}