Add image and artifact proxying
This commit is contained in:
parent
19b63f5a15
commit
a5d2a4615a
3 changed files with 166 additions and 18 deletions
80
src/cli.rs
80
src/cli.rs
|
@ -112,7 +112,7 @@ impl FilesystemArgs {
|
|||
for_variable = Some(value);
|
||||
}
|
||||
} else { buffer.push('=') },
|
||||
',' => if !in_string && !buffer.is_empty() {
|
||||
',' => if !in_string {
|
||||
let value: String = buffer.drain(..).collect();
|
||||
if let Some(var) = for_variable.take() {
|
||||
let var_trimmed = var.trim();
|
||||
|
@ -142,6 +142,14 @@ pub struct ProxyArgs {
|
|||
/// Proxy offerings from another store
|
||||
#[arg(name = "store", long, default_value_t = {"https://plugins.deckbrew.xyz".into()})]
|
||||
pub proxy_store: String,
|
||||
/// Proxy artifact (plugin download zips) urls
|
||||
#[arg(name = "artifacts", short, long)]
|
||||
pub intercept_artifacts: bool,
|
||||
/// Proxy preview image urls
|
||||
#[arg(name = "images", short, long)]
|
||||
pub intercept_images: bool,
|
||||
#[arg(name = "domain", default_value_t = {"http://localhost:22252".into()})]
|
||||
pub domain_root: String,
|
||||
}
|
||||
|
||||
impl ProxyArgs {
|
||||
|
@ -154,22 +162,61 @@ impl ProxyArgs {
|
|||
return Err(format!("Proxy descriptor too short"));
|
||||
}
|
||||
let mut buffer = Vec::new();
|
||||
let mut for_variable: Option<String> = None;
|
||||
let mut in_string = false;
|
||||
let mut escaped = false;
|
||||
let mut proxy_store = None;
|
||||
let mut intercept_artifacts = false;
|
||||
let mut intercept_images = false;
|
||||
let mut domain = None;
|
||||
for c in chars {
|
||||
match c {
|
||||
'}' => if escaped {
|
||||
buffer.push('}')
|
||||
} else {
|
||||
'}' if !escaped && !in_string => {
|
||||
return
|
||||
Ok(Self {
|
||||
proxy_store: if buffer.is_empty() { "https://plugins.deckbrew.xyz".into() } else { buffer.iter().collect() }
|
||||
proxy_store: if let Some(url) = proxy_store {
|
||||
url
|
||||
} else if buffer.is_empty() {
|
||||
"https://plugins.deckbrew.xyz".into()
|
||||
} else {
|
||||
buffer.iter().collect()
|
||||
},
|
||||
intercept_artifacts,
|
||||
intercept_images,
|
||||
domain_root: domain.unwrap_or_else(|| "http://localhost:22252".into()),
|
||||
})
|
||||
}
|
||||
'\\' => escaped = true,
|
||||
'"' if !escaped => in_string = !in_string,
|
||||
'=' if !escaped && !in_string => {
|
||||
let value: String = buffer.drain(..).collect();
|
||||
if for_variable.is_some() {
|
||||
return Err("Unexpected = in filesystem descriptor".to_owned());
|
||||
} else {
|
||||
for_variable = Some(value);
|
||||
}
|
||||
}
|
||||
',' if !escaped && !in_string => {
|
||||
let value: String = buffer.drain(..).collect();
|
||||
if let Some(var) = for_variable.take() {
|
||||
let var_trimmed = var.trim();
|
||||
match &var_trimmed as &str {
|
||||
"intercept" => {
|
||||
intercept_images = true;
|
||||
intercept_artifacts = true;
|
||||
},
|
||||
"img" | "images" | "intercept_images" => intercept_images = true,
|
||||
"dl" | "downloads" | "artifacts" | "intercept_artifacts" => intercept_artifacts = true,
|
||||
"url" | "store" | "proxy_store" => proxy_store = Some(value),
|
||||
"d" | "domain" => domain = Some(value),
|
||||
v => return Err(format!("Unexpected variable name {} in filesystem descriptor", v)),
|
||||
}
|
||||
}
|
||||
},
|
||||
'\\' if !escaped => escaped = true,
|
||||
c => {
|
||||
if escaped {
|
||||
escaped = false;
|
||||
buffer.push('\\');
|
||||
//buffer.push('\\');
|
||||
}
|
||||
buffer.push(c)
|
||||
},
|
||||
|
@ -179,7 +226,13 @@ impl ProxyArgs {
|
|||
}
|
||||
|
||||
fn to_descriptor(self) -> String {
|
||||
format!("{{{}}}", self.proxy_store)
|
||||
match (self.intercept_artifacts, self.intercept_images) {
|
||||
(true, true) => format!("{{url=\"{}\",intercept,}}", self.proxy_store),
|
||||
(true, false) => format!("{{url=\"{}\",intercept_artifacts,}}", self.proxy_store),
|
||||
(false, true) => format!("{{url=\"{}\",intercept_images,}}", self.proxy_store),
|
||||
(false, false) => format!("{{{}}}", self.proxy_store),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,7 +309,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn storage_descriptor() {
|
||||
let descriptor = "f{root=\"\",domain=\"\",stats=0}";
|
||||
let descriptor = "f{root=\"\",domain=\"\",stats=0,}";
|
||||
let parsed = StorageArgs::from_descriptor(&mut descriptor.chars());
|
||||
parsed.expect("StorageArgs parse error");
|
||||
let descriptor = "p{}";
|
||||
|
@ -272,7 +325,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn filesys_descriptor() {
|
||||
let descriptor = "{root='',domain='',stats:0}";
|
||||
let descriptor = "{root=\"\",domain=\"\",stats=0,}";
|
||||
let parsed = FilesystemArgs::from_descriptor(&mut descriptor.chars());
|
||||
parsed.expect("FilesystemArgs parse error");
|
||||
}
|
||||
|
@ -284,6 +337,13 @@ mod tests {
|
|||
parsed.expect("ProxyArgs parse error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_descriptor_complex() {
|
||||
let descriptor = "{url=\"https://plugins.example.com\", intercept, intercept_image,}";
|
||||
let parsed = ProxyArgs::from_descriptor(&mut descriptor.chars());
|
||||
parsed.expect("ProxyArgs parse error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_descriptor() {
|
||||
let descriptor = "[(f{}),(p{}),( )]";
|
||||
|
|
|
@ -26,6 +26,9 @@ fn build_storage_box(storage: &cli::StorageArgs) -> Box<dyn storage::IStorage> {
|
|||
)),
|
||||
cli::StorageArgs::Proxy(px) => Box::new(storage::ProxiedStorage::new(
|
||||
px.proxy_store.clone(),
|
||||
px.intercept_artifacts,
|
||||
px.intercept_images,
|
||||
px.domain_root.clone(),
|
||||
)),
|
||||
cli::StorageArgs::Empty => Box::new(storage::EmptyStorage),
|
||||
cli::StorageArgs::Merge(ls) => Box::new(storage::MergedStorage::new(
|
||||
|
|
|
@ -2,15 +2,23 @@ use decky_api::{StorePluginList, StorePluginVersion};
|
|||
|
||||
use super::IStorage;
|
||||
|
||||
const MAX_PROXY_RESPONSE_SIZE: u64 = 10_000_000;
|
||||
|
||||
pub struct ProxiedStorage {
|
||||
store_url: String,
|
||||
intercept_artifacts: bool,
|
||||
intercept_images: bool,
|
||||
domain_root: String,
|
||||
agent: ureq::Agent,
|
||||
}
|
||||
|
||||
impl ProxiedStorage {
|
||||
pub fn new(target_store: String) -> Self {
|
||||
pub fn new(target_store: String, intercept_artifacts: bool, intercept_images: bool, domain_root: String) -> Self {
|
||||
Self {
|
||||
store_url: target_store,
|
||||
intercept_artifacts: intercept_artifacts,
|
||||
intercept_images: intercept_images,
|
||||
domain_root: domain_root,
|
||||
agent: ureq::AgentBuilder::new()
|
||||
.tls_connector(std::sync::Arc::new(native_tls::TlsConnector::new().expect("Native TLS init failed")))
|
||||
.build(),
|
||||
|
@ -22,7 +30,31 @@ impl ProxiedStorage {
|
|||
}
|
||||
|
||||
fn default_artifact_url(ver: &StorePluginVersion) -> String {
|
||||
format!("https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/{}.zip", ver.hash)
|
||||
Self::default_artifact_url_by_hash(&ver.hash)
|
||||
}
|
||||
|
||||
fn default_artifact_url_by_hash(hash: &str) -> String {
|
||||
format!("https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/{}.zip", hash)
|
||||
}
|
||||
|
||||
fn proxied_artifact_url(ver: &StorePluginVersion, plugin_name: &str, domain_root: &str) -> String {
|
||||
// /plugins/{name}/{version}/{hash}.zip
|
||||
format!("{}/plugins/{}/{}/{}.zip", domain_root, plugin_name, ver.name, ver.hash)
|
||||
}
|
||||
|
||||
fn image_url_to_name(image_url: &str) -> &str {
|
||||
image_url
|
||||
.trim_start_matches("https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/artifact_images/")
|
||||
.trim_end_matches(".png")
|
||||
}
|
||||
|
||||
fn default_image_url_by_name(image_name: &str) -> String {
|
||||
format!("https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/artifact_images/{}.png", image_name)
|
||||
}
|
||||
|
||||
fn proxied_image_url(image_name: &str, domain_root: &str) -> String {
|
||||
// /plugins/{name}.png
|
||||
format!("{}/plugins/{}.png", domain_root, image_name)
|
||||
}
|
||||
|
||||
fn proxy_plugins(&self, query: &str) -> StorePluginList {
|
||||
|
@ -49,23 +81,76 @@ impl IStorage for ProxiedStorage {
|
|||
fn plugins(&self, query: &str) -> StorePluginList {
|
||||
let mut proxy = self.proxy_plugins(query);
|
||||
for plugin in &mut proxy {
|
||||
if self.intercept_images {
|
||||
plugin.image_url = Self::proxied_image_url(Self::image_url_to_name(&plugin.image_url), &self.domain_root);
|
||||
}
|
||||
for version in &mut plugin.versions {
|
||||
if version.artifact.is_none() {
|
||||
version.artifact = Some(Self::default_artifact_url(version));
|
||||
if self.intercept_artifacts {
|
||||
version.artifact = Some(Self::proxied_artifact_url(&version, &plugin.name, &self.domain_root));
|
||||
} else {
|
||||
if version.artifact.is_none() {
|
||||
version.artifact = Some(Self::default_artifact_url(version));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
proxy
|
||||
}
|
||||
|
||||
/*fn get_artifact(&self, name: &str, version: &str, hash: &str) -> Result<bytes::Bytes, std::io::Error> {
|
||||
self.fallback.get_artifact(name, version, hash)
|
||||
fn get_artifact(&self, _name: &str, _version: &str, hash: &str) -> Result<bytes::Bytes, std::io::Error> {
|
||||
let url = Self::default_artifact_url_by_hash(hash);
|
||||
match self.agent.get(&url).call() {
|
||||
Err(e) => {
|
||||
log::error!("Plugins proxy error for {}: {}", url, e);
|
||||
Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, e))
|
||||
},
|
||||
Ok(resp) => {
|
||||
let len: usize = resp.header("Content-Length")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap_or(MAX_PROXY_RESPONSE_SIZE as usize / 4);
|
||||
let mut buffer = Vec::with_capacity(len);
|
||||
use std::io::Read;
|
||||
match resp.into_reader()
|
||||
.take(MAX_PROXY_RESPONSE_SIZE)
|
||||
.read_to_end(&mut buffer) {
|
||||
Err(e) => {
|
||||
log::error!("Plugins json error for {}: {}", url, e);
|
||||
Err(e)
|
||||
}
|
||||
Ok(_) => Ok(buffer.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_image(&self, name: &str) -> Result<bytes::Bytes, std::io::Error> {
|
||||
self.fallback.get_image(name)
|
||||
let url = Self::default_image_url_by_name(name);
|
||||
match self.agent.get(&url).call() {
|
||||
Err(e) => {
|
||||
log::error!("Plugins image proxy error for {}: {}", url, e);
|
||||
Err(std::io::Error::new(std::io::ErrorKind::ConnectionAborted, e))
|
||||
},
|
||||
Ok(resp) => {
|
||||
let len: usize = resp.header("Content-Length")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap_or(MAX_PROXY_RESPONSE_SIZE as usize / 4);
|
||||
let mut buffer = Vec::with_capacity(len);
|
||||
use std::io::Read;
|
||||
match resp.into_reader()
|
||||
.take(MAX_PROXY_RESPONSE_SIZE)
|
||||
.read_to_end(&mut buffer) {
|
||||
Err(e) => {
|
||||
log::error!("Plugins json error for {}: {}", url, e);
|
||||
Err(e)
|
||||
}
|
||||
Ok(_) => Ok(buffer.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn get_statistics(&self) -> std::collections::HashMap<String, u64> {
|
||||
self.fallback.get_statistics()
|
||||
}*/
|
||||
|
|
Loading…
Reference in a new issue