uxn_tal/fetch/
patchstorage.rs1use super::{
2 downloader::{http_get, write_bytes},
3 provider::{FetchResult, Provider, RepoRef},
4};
5use std::path::{Path, PathBuf};
6use url::Url;
7
8pub struct PatchStorage;
9
10impl PatchStorage {
11 fn extract_download_url(html: &str) -> Option<String> {
12 use scraper::{Html, Selector};
14 let document = Html::parse_document(html);
15 let selector = Selector::parse("a.ps-patch-download").ok()?;
16 for element in document.select(&selector) {
17 if let Some(href) = element.value().attr("href") {
18 if href.ends_with(".orca") {
19 if href.starts_with("http") {
20 return Some(href.to_string());
21 } else {
22 return Some(format!("https://patchstorage.com{}", href));
23 }
24 }
25 }
26 }
27 None
28 }
29
30 fn fetch_file(url: &str, out_root: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
31 let html = http_get(url)?;
32 let html_str = String::from_utf8_lossy(&html);
33 let download_url = Self::extract_download_url(&html_str)
34 .ok_or_else(|| "PatchStorage: Download link not found".to_string())?;
35 let file_bytes = http_get(&download_url)?;
36 let filename = download_url
37 .split('/')
38 .next_back()
39 .ok_or("PatchStorage: Invalid download URL")?;
40 let local = out_root.join(filename);
41 write_bytes(&local, &file_bytes)?;
42 Ok(local)
43 }
44}
45
46impl Provider for PatchStorage {
47 fn parse_url(&self, url: &str) -> Option<RepoRef> {
48 let u = Url::parse(url).ok()?;
49 if u.domain()? != "patchstorage.com" {
50 return None;
51 }
52 Some(RepoRef {
53 host: "patchstorage.com".into(),
54 owner: "".into(),
55 repo: "".into(),
56 branch: "".into(),
57 path: Some(u.path().trim_start_matches('/').to_string()),
58 })
59 }
60
61 fn fetch_tal_tree(
62 &self,
63 r: &RepoRef,
64 out_root: &Path,
65 ) -> Result<FetchResult, Box<dyn std::error::Error>> {
66 let url = format!(
67 "https://patchstorage.com/{}",
68 r.path.as_deref().unwrap_or("")
69 );
70 let filename = url.split('/').next_back().unwrap_or("");
71 let mut entry_file: Option<PathBuf> = None;
72 if !filename.is_empty() {
73 let local = out_root.join(filename);
74 if local.is_file() {
75 eprintln!("[PatchStorage] Using cached file: {}", local.display());
76 entry_file = Some(local);
77 }
78 }
79 if entry_file.is_none() {
81 if let Ok(entries) = std::fs::read_dir(out_root) {
82 for entry in entries.flatten() {
83 let path = entry.path();
84 if path.extension().map(|e| e == "orca").unwrap_or(false) && path.is_file() {
85 eprintln!("[PatchStorage] Using cached file: {}", path.display());
86 entry_file = Some(path);
87 break;
88 }
89 }
90 }
91 }
92 if let Some(local) = entry_file {
93 return Ok(FetchResult {
94 entry_local: local.clone(),
95 all_files: vec![local],
96 });
97 }
98 let local = Self::fetch_file(&url, out_root)?;
100 Ok(FetchResult {
101 entry_local: local.clone(),
102 all_files: vec![local],
103 })
104 }
105}