mirror of https://github.com/wayvr-org/wayvr.git
switch downloader from hyper to urec
This commit is contained in:
parent
867c343c9b
commit
40430ea94e
|
|
@ -1415,7 +1415,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"glam",
|
"glam",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
|
||||||
"keyvalues-parser",
|
"keyvalues-parser",
|
||||||
"log",
|
"log",
|
||||||
"lz4_flex",
|
"lz4_flex",
|
||||||
|
|
@ -1425,6 +1424,7 @@ dependencies = [
|
||||||
"smol",
|
"smol",
|
||||||
"smol-hyper",
|
"smol-hyper",
|
||||||
"strum",
|
"strum",
|
||||||
|
"ureq",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wayvr-ipc",
|
"wayvr-ipc",
|
||||||
"wgui",
|
"wgui",
|
||||||
|
|
@ -2176,25 +2176,6 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
|
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "h2"
|
|
||||||
version = "0.4.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-waker",
|
|
||||||
"bytes",
|
|
||||||
"fnv",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"http",
|
|
||||||
"indexmap 2.14.0",
|
|
||||||
"slab",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
|
|
@ -2360,19 +2341,10 @@ version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
|
checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-waker",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"h2",
|
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
|
||||||
"itoa",
|
|
||||||
"pin-project-lite",
|
|
||||||
"smallvec",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"want",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4713,6 +4685,20 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rodio"
|
name = "rodio"
|
||||||
version = "0.22.2"
|
version = "0.22.2"
|
||||||
|
|
@ -4874,6 +4860,41 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.103.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|
@ -5449,6 +5470,12 @@ dependencies = [
|
||||||
"syn 2.0.118",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svgtypes"
|
name = "svgtypes"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
|
|
@ -5799,23 +5826,9 @@ version = "1.52.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-util"
|
|
||||||
version = "0.7.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "1.1.2+spec-1.1.0"
|
version = "1.1.2+spec-1.1.0"
|
||||||
|
|
@ -5929,12 +5942,6 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "try-lock"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
|
|
@ -5978,6 +5985,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"ureq",
|
||||||
"vulkano",
|
"vulkano",
|
||||||
"vulkano-shaders",
|
"vulkano-shaders",
|
||||||
"wgui",
|
"wgui",
|
||||||
|
|
@ -6033,6 +6041,41 @@ version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"flate2",
|
||||||
|
"log",
|
||||||
|
"percent-encoding",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"ureq-proto",
|
||||||
|
"utf8-zero",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq-proto"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
|
|
@ -6067,6 +6110,12 @@ dependencies = [
|
||||||
"xmlwriter",
|
"xmlwriter",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-zero"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8_iter"
|
name = "utf8_iter"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -6201,15 +6250,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "want"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
|
||||||
dependencies = [
|
|
||||||
"try-lock",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
|
@ -6557,6 +6597,15 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
|
@ -7247,6 +7296,12 @@ dependencies = [
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ xdg.workspace = true
|
||||||
|
|
||||||
async-native-tls = "0.6.0"
|
async-native-tls = "0.6.0"
|
||||||
http-body-util = "0.1.3"
|
http-body-util = "0.1.3"
|
||||||
hyper = { version = "1.10.1", features = ["client", "http1", "http2"] }
|
|
||||||
keyvalues-parser = {
|
keyvalues-parser = {
|
||||||
git = "https://codeberg.org/CosmicHarper/vdf-rs.git",
|
git = "https://codeberg.org/CosmicHarper/vdf-rs.git",
|
||||||
rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145"
|
rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145"
|
||||||
}
|
}
|
||||||
lz4_flex = { version = "0.13.1", features = ["frame"] }
|
lz4_flex = { version = "0.13.1", features = ["frame"] }
|
||||||
smol-hyper = "0.1.1"
|
smol-hyper = "0.1.1"
|
||||||
|
ureq = "3.3.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["monado"]
|
default = ["monado"]
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::util::{networking::http_client, steam_utils::AppID};
|
use crate::util::{networking::http_client, steam_utils::AppID};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use wlx_common::{async_executor::AsyncExecutor, cache_dir};
|
use wlx_common::cache_dir;
|
||||||
|
|
||||||
pub struct CoverArt {
|
pub struct CoverArt {
|
||||||
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
||||||
pub compressed_image_data: Vec<u8>,
|
pub compressed_image_data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_image(executor: AsyncExecutor, app_id: AppID) -> anyhow::Result<CoverArt> {
|
pub async fn request_image(app_id: AppID) -> anyhow::Result<CoverArt> {
|
||||||
let cache_file_path = format!("cover_arts/{}.bin", app_id);
|
let cache_file_path = format!("cover_arts/{}.bin", app_id);
|
||||||
|
|
||||||
// check if file already exists in cache directory
|
// check if file already exists in cache directory
|
||||||
|
|
@ -23,7 +23,7 @@ pub async fn request_image(executor: AsyncExecutor, app_id: AppID) -> anyhow::Re
|
||||||
app_id
|
app_id
|
||||||
);
|
);
|
||||||
|
|
||||||
match http_client::get_simple(&executor, &url).await {
|
match http_client::get_simple(&url).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
log::info!("Success");
|
log::info!("Success");
|
||||||
cache_dir::set_data(&cache_file_path, &response.data).await?;
|
cache_dir::set_data(&cache_file_path, &response.data).await?;
|
||||||
|
|
@ -55,11 +55,7 @@ pub struct AppDetailsJSONData {
|
||||||
pub developers: Vec<String>, // ["Valve"]
|
pub developers: Vec<String>, // ["Valve"]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_app_details_json_internal(
|
async fn get_app_details_json_internal(cache_file_path: &str, app_id: AppID) -> anyhow::Result<AppDetailsJSONData> {
|
||||||
executor: AsyncExecutor,
|
|
||||||
cache_file_path: &str,
|
|
||||||
app_id: AppID,
|
|
||||||
) -> anyhow::Result<AppDetailsJSONData> {
|
|
||||||
// check if file already exists in cache directory
|
// check if file already exists in cache directory
|
||||||
if let Some(data) = cache_dir::get_data(cache_file_path).await {
|
if let Some(data) = cache_dir::get_data(cache_file_path).await {
|
||||||
return Ok(serde_json::from_value(serde_json::from_slice(&data)?)?);
|
return Ok(serde_json::from_value(serde_json::from_slice(&data)?)?);
|
||||||
|
|
@ -68,7 +64,7 @@ async fn get_app_details_json_internal(
|
||||||
// Fetch from Steam API
|
// Fetch from Steam API
|
||||||
log::info!("Fetching app detail ID {}", app_id);
|
log::info!("Fetching app detail ID {}", app_id);
|
||||||
let url = format!("https://store.steampowered.com/api/appdetails?appids={}", app_id);
|
let url = format!("https://store.steampowered.com/api/appdetails?appids={}", app_id);
|
||||||
let response = http_client::get_simple(&executor, &url).await?;
|
let response = http_client::get_simple(&url).await?;
|
||||||
let res_utf8 = String::from_utf8(response.data)?;
|
let res_utf8 = String::from_utf8(response.data)?;
|
||||||
let root = serde_json::from_str::<serde_json::Value>(&res_utf8)?;
|
let root = serde_json::from_str::<serde_json::Value>(&res_utf8)?;
|
||||||
let body = root.get(&app_id).context("invalid body")?;
|
let body = root.get(&app_id).context("invalid body")?;
|
||||||
|
|
@ -88,10 +84,10 @@ async fn get_app_details_json_internal(
|
||||||
Ok(app_details)
|
Ok(app_details)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_app_details_json(executor: AsyncExecutor, app_id: AppID) -> Option<AppDetailsJSONData> {
|
pub async fn get_app_details_json(app_id: AppID) -> Option<AppDetailsJSONData> {
|
||||||
let cache_file_path = format!("app_details/{}.json", app_id);
|
let cache_file_path = format!("app_details/{}.json", app_id);
|
||||||
|
|
||||||
match get_app_details_json_internal(executor, &cache_file_path, app_id).await {
|
match get_app_details_json_internal(&cache_file_path, app_id).await {
|
||||||
Ok(r) => Some(r),
|
Ok(r) => Some(r),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to get app details: {:?}", e);
|
log::error!("Failed to get app details: {:?}", e);
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
//
|
|
||||||
// example smol+hyper usage derived from
|
|
||||||
// https://github.com/smol-rs/smol/blob/master/examples/hyper-client.rs
|
|
||||||
// under Apache-2.0 + MIT license.
|
|
||||||
// Repository URL: https://github.com/smol-rs/smol
|
|
||||||
//
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use async_native_tls::TlsStream;
|
use smol::{
|
||||||
use http_body_util::{BodyStream, Empty};
|
Task,
|
||||||
use hyper::Request;
|
channel::{Receiver, bounded},
|
||||||
use smol::{net::TcpStream, prelude::*};
|
io::AsyncWriteExt as _,
|
||||||
use std::convert::TryInto;
|
};
|
||||||
use std::pin::Pin;
|
use std::{io::Read as _, path::Path, str, sync::OnceLock};
|
||||||
use std::task::{Context, Poll};
|
|
||||||
use wlx_common::async_executor::AsyncExecutor;
|
const IO_BUFFER_SIZE: usize = 256 * 1024;
|
||||||
|
const CHANNEL_CAPACITY: usize = 4;
|
||||||
|
const MAX_INITIAL_ALLOCATION: usize = 8 * 1024 * 1024;
|
||||||
|
|
||||||
pub struct HttpClientResponse {
|
pub struct HttpClientResponse {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
@ -21,276 +17,224 @@ pub struct HttpClientResponse {
|
||||||
impl HttpClientResponse {
|
impl HttpClientResponse {
|
||||||
pub fn into_json<T>(self) -> anyhow::Result<T>
|
pub fn into_json<T>(self) -> anyhow::Result<T>
|
||||||
where
|
where
|
||||||
T: for<'a> serde::Deserialize<'a>,
|
T: serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
let utf8 = str::from_utf8(&self.data)?;
|
let utf8 = str::from_utf8(&self.data)?;
|
||||||
Ok(serde_json::from_str::<T>(utf8)?)
|
Ok(serde_json::from_str(utf8)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ProgressFuncData {
|
pub struct ProgressFuncData {
|
||||||
pub bytes_downloaded: u64,
|
pub bytes_downloaded: u64,
|
||||||
pub file_size: u64,
|
pub file_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ProgressFunc = Box<dyn Fn(ProgressFuncData)>;
|
pub type ProgressFunc<'a> = Box<dyn FnMut(ProgressFuncData) + 'a>;
|
||||||
|
|
||||||
pub struct GetParams<'a> {
|
pub struct GetParams<'a> {
|
||||||
pub executor: &'a AsyncExecutor,
|
|
||||||
pub url: &'a str,
|
pub url: &'a str,
|
||||||
pub on_progress: Option<ProgressFunc>,
|
pub on_progress: Option<ProgressFunc<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(params: GetParams<'_>) -> anyhow::Result<HttpClientResponse> {
|
struct DownloadStream {
|
||||||
let url: hyper::Uri = params.url.try_into()?;
|
file_size: u64,
|
||||||
let req = Request::builder()
|
chunks: Receiver<Vec<u8>>,
|
||||||
.header(
|
worker: Task<anyhow::Result<()>>,
|
||||||
hyper::header::HOST,
|
|
||||||
url.authority().context("invalid authority")?.clone().as_str(),
|
|
||||||
)
|
|
||||||
.uri(url)
|
|
||||||
.body(Empty::new())?;
|
|
||||||
|
|
||||||
let resp = fetch_and_follow_redirects(params.executor, req, params.on_progress, ¶ms.url).await?;
|
|
||||||
|
|
||||||
Ok(resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_and_follow_redirects(
|
static HTTP_AGENT: OnceLock<ureq::Agent> = OnceLock::new();
|
||||||
executor: &AsyncExecutor,
|
|
||||||
req: hyper::Request<Empty<&'static [u8]>>,
|
|
||||||
mut on_progress: Option<ProgressFunc>,
|
|
||||||
initial_url: &str,
|
|
||||||
) -> anyhow::Result<HttpClientResponse> {
|
|
||||||
log::info!("fetching URL \"{}\"", initial_url);
|
|
||||||
|
|
||||||
let resp = fetch(executor, req.clone()).await?;
|
fn http_agent() -> &'static ureq::Agent {
|
||||||
let status = resp.status();
|
HTTP_AGENT.get_or_init(|| {
|
||||||
|
ureq::Agent::config_builder()
|
||||||
|
.max_redirects(10)
|
||||||
|
.http_status_as_error(false)
|
||||||
|
.build()
|
||||||
|
.new_agent()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if status.is_redirection() {
|
/// Starts a blocking HTTP request and streams its body through a bounded
|
||||||
let next_req = follow_single_redirect(&req, &resp).await?;
|
/// asynchronous channel.
|
||||||
|
///
|
||||||
|
/// The request, TLS operations, and all BodyReader reads remain inside one
|
||||||
|
/// blocking worker. Only owned byte chunks cross back to the async task.
|
||||||
|
async fn start_download(url: &str, allow_missing_content_length: bool) -> anyhow::Result<DownloadStream> {
|
||||||
|
let url = url.to_owned();
|
||||||
|
let agent = http_agent().clone();
|
||||||
|
|
||||||
let max_redirects = 10;
|
let (metadata_tx, metadata_rx) = bounded::<u64>(1);
|
||||||
let mut current_req = next_req;
|
|
||||||
let mut redirects = 0;
|
|
||||||
loop {
|
|
||||||
let resp = fetch(executor, current_req.clone()).await?;
|
|
||||||
let resp_status = resp.status();
|
|
||||||
|
|
||||||
if resp_status.is_success() {
|
let (chunk_tx, chunk_rx) = bounded::<Vec<u8>>(CHANNEL_CAPACITY);
|
||||||
let (parts, body) = resp.into_parts();
|
|
||||||
|
|
||||||
let mut bytes_downloaded: u64 = 0;
|
let worker = smol::unblock(move || -> anyhow::Result<()> {
|
||||||
let mut file_size: u64 = 1;
|
log::info!("fetching URL \"{}\"", url);
|
||||||
|
|
||||||
if let Some(val) = parts.headers.get("Content-Length")
|
let response = agent
|
||||||
&& let Ok(str) = val.to_str()
|
.get(&url)
|
||||||
&& let Ok(s) = str.parse()
|
.header("Accept-Encoding", "identity")
|
||||||
{
|
.call()
|
||||||
file_size = s;
|
.with_context(|| format!("failed to fetch URL \"{url}\""))?;
|
||||||
}
|
|
||||||
|
|
||||||
let data = BodyStream::new(body)
|
if !response.status().is_success() {
|
||||||
.try_fold(Vec::new(), |mut body, chunk| {
|
anyhow::bail!("non-200 HTTP response: {}", response.status().as_u16(),);
|
||||||
if let Some(chunk) = chunk.data_ref() {
|
}
|
||||||
bytes_downloaded += chunk.len() as u64;
|
|
||||||
body.extend_from_slice(chunk);
|
|
||||||
|
|
||||||
if let Some(on_progress) = &mut on_progress {
|
let file_size = match response.body().content_length() {
|
||||||
on_progress(ProgressFuncData {
|
Some(file_size) => file_size,
|
||||||
bytes_downloaded,
|
|
||||||
file_size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(body)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
return Ok(HttpClientResponse { data });
|
None if allow_missing_content_length => 0,
|
||||||
} else if resp_status.is_redirection() {
|
|
||||||
redirects += 1;
|
None => {
|
||||||
if redirects >= max_redirects {
|
anyhow::bail!("HTTP response has no Content-Length header");
|
||||||
anyhow::bail!("too many redirects");
|
|
||||||
}
|
|
||||||
current_req = follow_single_redirect(¤t_req, &resp).await?;
|
|
||||||
} else {
|
|
||||||
anyhow::bail!("non-200 HTTP response: {}", resp_status.as_str());
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if metadata_tx.send_blocking(file_size).is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(metadata_tx);
|
||||||
|
|
||||||
|
let mut reader = response.into_body().into_reader();
|
||||||
|
let mut buffer = [0_u8; IO_BUFFER_SIZE];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let count = reader.read(&mut buffer).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed while reading HTTP response body \
|
||||||
|
from \"{url}\""
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = buffer[..count].to_vec();
|
||||||
|
|
||||||
|
if chunk_tx.send_blocking(chunk).is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let file_size = match metadata_rx.recv().await {
|
||||||
|
Ok(file_size) => file_size,
|
||||||
|
|
||||||
|
Err(_) => {
|
||||||
|
worker.await?;
|
||||||
|
|
||||||
|
anyhow::bail!("HTTP worker stopped before providing response metadata");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(DownloadStream {
|
||||||
|
file_size,
|
||||||
|
chunks: chunk_rx,
|
||||||
|
worker,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads a response into memory.
|
||||||
|
///
|
||||||
|
/// This fails if the server does not provide a Content-Length header.
|
||||||
|
pub async fn get(mut params: GetParams<'_>) -> anyhow::Result<HttpClientResponse> {
|
||||||
|
let DownloadStream {
|
||||||
|
file_size,
|
||||||
|
chunks,
|
||||||
|
worker,
|
||||||
|
} = start_download(params.url, false).await?;
|
||||||
|
|
||||||
|
let initial_capacity = usize::try_from(file_size).unwrap_or(0).min(MAX_INITIAL_ALLOCATION);
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity(initial_capacity);
|
||||||
|
let mut bytes_downloaded = 0_u64;
|
||||||
|
|
||||||
|
while let Ok(chunk) = chunks.recv().await {
|
||||||
|
bytes_downloaded += chunk.len() as u64;
|
||||||
|
data.extend_from_slice(&chunk);
|
||||||
|
|
||||||
|
if let Some(on_progress) = params.on_progress.as_mut() {
|
||||||
|
on_progress(ProgressFuncData {
|
||||||
|
bytes_downloaded,
|
||||||
|
file_size,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !resp.status().is_success() {
|
worker.await?;
|
||||||
// non-200 HTTP response
|
|
||||||
anyhow::bail!("non-200 HTTP response: {}", resp.status().as_str());
|
if bytes_downloaded != file_size {
|
||||||
|
anyhow::bail!(
|
||||||
|
"HTTP response size mismatch: expected {} bytes, received {}",
|
||||||
|
file_size,
|
||||||
|
bytes_downloaded,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bytes_downloaded: u64 = 0;
|
|
||||||
let mut file_size: u64 = 1;
|
|
||||||
|
|
||||||
let (parts, body) = resp.into_parts();
|
|
||||||
|
|
||||||
// that's a pretty interesting way to get file size :]
|
|
||||||
if let Some(val) = parts.headers.get("Content-Length")
|
|
||||||
&& let Ok(str) = val.to_str()
|
|
||||||
&& let Ok(s) = str.parse()
|
|
||||||
{
|
|
||||||
file_size = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = BodyStream::new(body)
|
|
||||||
.try_fold(Vec::new(), |mut body, chunk| {
|
|
||||||
if let Some(chunk) = chunk.data_ref() {
|
|
||||||
bytes_downloaded += chunk.len() as u64;
|
|
||||||
body.extend_from_slice(chunk);
|
|
||||||
|
|
||||||
if let Some(on_progress) = &mut on_progress {
|
|
||||||
on_progress(ProgressFuncData {
|
|
||||||
bytes_downloaded,
|
|
||||||
file_size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(body)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(HttpClientResponse { data })
|
Ok(HttpClientResponse { data })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uri_try_from_str(s: &str) -> anyhow::Result<hyper::http::uri::PathAndQuery> {
|
/// Downloads a response directly to a file.
|
||||||
use std::convert::TryInto;
|
///
|
||||||
let uri: hyper::Uri = s.try_into().context("invalid URI")?;
|
/// Unlike `get`, this permits responses without a Content-Length header. In
|
||||||
uri
|
/// that case, `ProgressFuncData::file_size` is zero.
|
||||||
.path_and_query()
|
///
|
||||||
.ok_or_else(|| anyhow::anyhow!("URI has no path and query component"))
|
/// An existing file is truncated. If the download fails after the file is
|
||||||
.cloned()
|
/// created, a partial file may remain at `path`.
|
||||||
}
|
pub async fn download_to_file(mut params: GetParams<'_>, path: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
|
let path = path.as_ref().to_owned();
|
||||||
|
|
||||||
async fn follow_single_redirect(
|
let DownloadStream {
|
||||||
req: &hyper::Request<Empty<&'static [u8]>>,
|
file_size,
|
||||||
resp: &hyper::Response<hyper::body::Incoming>,
|
chunks,
|
||||||
) -> anyhow::Result<hyper::Request<Empty<&'static [u8]>>> {
|
worker,
|
||||||
let location = resp
|
} = start_download(params.url, true).await?;
|
||||||
.headers()
|
|
||||||
.get(hyper::header::LOCATION)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("redirect response has no Location header"))?;
|
|
||||||
|
|
||||||
let location_str = location.to_str().context("invalid redirect location header")?;
|
let mut file = smol::fs::File::create(&path)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to create download file {:?}", path,))?;
|
||||||
|
|
||||||
// resolve relative urls against the original url.
|
let mut bytes_downloaded = 0_u64;
|
||||||
let original_uri = req.uri().clone();
|
|
||||||
let next_url: hyper::Uri = if location_str.starts_with("http://") || location_str.starts_with("https://") {
|
|
||||||
hyper::Uri::try_from(location_str).context("invalid redirect location")?
|
|
||||||
} else {
|
|
||||||
let mut parts = original_uri.into_parts();
|
|
||||||
parts.path_and_query = uri_try_from_str(location_str)
|
|
||||||
.context("invalid redirect location")?
|
|
||||||
.into();
|
|
||||||
hyper::Uri::from_parts(parts).context("failed to construct redirect URI")?
|
|
||||||
};
|
|
||||||
|
|
||||||
log::info!("redirecting to \"{}\"", next_url);
|
while let Ok(chunk) = chunks.recv().await {
|
||||||
|
file
|
||||||
|
.write_all(&chunk)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to write download file {:?}", path,))?;
|
||||||
|
|
||||||
let next_req = Request::builder()
|
bytes_downloaded += chunk.len() as u64;
|
||||||
.header(
|
|
||||||
hyper::header::HOST,
|
|
||||||
next_url.authority().context("invalid authority")?.as_str(),
|
|
||||||
)
|
|
||||||
.uri(next_url)
|
|
||||||
.body(Empty::new())?;
|
|
||||||
|
|
||||||
Ok(next_req)
|
if let Some(on_progress) = params.on_progress.as_mut() {
|
||||||
}
|
on_progress(ProgressFuncData {
|
||||||
|
bytes_downloaded,
|
||||||
pub async fn get_simple(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> {
|
file_size,
|
||||||
get(GetParams {
|
});
|
||||||
executor,
|
|
||||||
url,
|
|
||||||
on_progress: None,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch(
|
|
||||||
ex: &AsyncExecutor,
|
|
||||||
req: hyper::Request<http_body_util::Empty<&'static [u8]>>,
|
|
||||||
) -> anyhow::Result<hyper::Response<hyper::body::Incoming>> {
|
|
||||||
let io = {
|
|
||||||
let host = req.uri().host().context("cannot parse host")?;
|
|
||||||
|
|
||||||
match req.uri().scheme_str() {
|
|
||||||
Some("http") => {
|
|
||||||
let stream = {
|
|
||||||
let port = req.uri().port_u16().unwrap_or(80);
|
|
||||||
smol::net::TcpStream::connect((host, port)).await?
|
|
||||||
};
|
|
||||||
SmolStream::Plain(stream)
|
|
||||||
}
|
|
||||||
Some("https") => {
|
|
||||||
// In case of HTTPS, establish a secure TLS connection first.
|
|
||||||
let stream = {
|
|
||||||
let port = req.uri().port_u16().unwrap_or(443);
|
|
||||||
smol::net::TcpStream::connect((host, port)).await?
|
|
||||||
};
|
|
||||||
let stream = async_native_tls::connect(host, stream).await?;
|
|
||||||
SmolStream::Tls(stream)
|
|
||||||
}
|
|
||||||
scheme => anyhow::bail!("unsupported scheme: {:?}", scheme),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Spawn the HTTP/1 connection.
|
|
||||||
let (mut sender, conn) = hyper::client::conn::http1::handshake(smol_hyper::rt::FuturesIo::new(io)).await?;
|
|
||||||
ex.spawn(async move {
|
|
||||||
if let Err(e) = conn.await {
|
|
||||||
println!("Connection failed: {:?}", e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
// Get the result
|
|
||||||
let result = sender.send_request(req).await?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A TCP or TCP+TLS connection.
|
|
||||||
enum SmolStream {
|
|
||||||
/// A plain TCP connection.
|
|
||||||
Plain(TcpStream),
|
|
||||||
|
|
||||||
/// A TCP connection secured by TLS.
|
|
||||||
Tls(TlsStream<TcpStream>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncRead for SmolStream {
|
|
||||||
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<smol::io::Result<usize>> {
|
|
||||||
match &mut *self {
|
|
||||||
SmolStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
|
|
||||||
SmolStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncWrite for SmolStream {
|
|
||||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<smol::io::Result<usize>> {
|
|
||||||
match &mut *self {
|
|
||||||
SmolStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
|
|
||||||
SmolStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> {
|
worker.await?;
|
||||||
match &mut *self {
|
|
||||||
SmolStream::Plain(stream) => Pin::new(stream).poll_close(cx),
|
file
|
||||||
SmolStream::Tls(stream) => Pin::new(stream).poll_close(cx),
|
.flush()
|
||||||
}
|
.await
|
||||||
|
.with_context(|| format!("failed to flush download file {:?}", path,))?;
|
||||||
|
|
||||||
|
if file_size != 0 && bytes_downloaded != file_size {
|
||||||
|
anyhow::bail!(
|
||||||
|
"HTTP response size mismatch: expected {} bytes, received {}",
|
||||||
|
file_size,
|
||||||
|
bytes_downloaded,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> {
|
Ok(())
|
||||||
match &mut *self {
|
}
|
||||||
SmolStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
|
|
||||||
SmolStream::Tls(stream) => Pin::new(stream).poll_flush(cx),
|
pub async fn get_simple(url: &str) -> anyhow::Result<HttpClientResponse> {
|
||||||
}
|
get(GetParams { url, on_progress: None }).await
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use wgui::{globals::WguiGlobals, renderer_vk::text::custom_glyph::CustomGlyphData};
|
use wgui::{globals::WguiGlobals, renderer_vk::text::custom_glyph::CustomGlyphData};
|
||||||
use wlx_common::async_executor::AsyncExecutor;
|
|
||||||
|
|
||||||
use crate::util::networking::http_client;
|
use crate::util::networking::http_client;
|
||||||
|
|
||||||
pub async fn fetch_to_glyph_data(
|
pub async fn fetch_to_glyph_data(globals: &WguiGlobals, url: &str) -> anyhow::Result<(CustomGlyphData, Rc<Vec<u8>>)> {
|
||||||
globals: &WguiGlobals,
|
let res = http_client::get_simple(url).await?;
|
||||||
executor: &AsyncExecutor,
|
|
||||||
url: &str,
|
|
||||||
) -> anyhow::Result<(CustomGlyphData, Rc<Vec<u8>>)> {
|
|
||||||
let res = http_client::get_simple(executor, url).await?;
|
|
||||||
let glyph_data = CustomGlyphData::from_bytes_raster(globals, url, &res.data)?;
|
let glyph_data = CustomGlyphData::from_bytes_raster(globals, url, &res.data)?;
|
||||||
Ok((glyph_data, Rc::new(res.data)))
|
Ok((glyph_data, Rc::new(res.data)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client};
|
use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wlx_common::{async_executor::AsyncExecutor, config_io};
|
use wlx_common::config_io;
|
||||||
pub type SkymapUuid = uuid::Uuid;
|
pub type SkymapUuid = uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -160,10 +160,10 @@ impl SkymapCatalog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result<SkymapCatalog> {
|
pub async fn request_catalog() -> anyhow::Result<SkymapCatalog> {
|
||||||
log::info!("Fetching skymap list");
|
log::info!("Fetching skymap list");
|
||||||
|
|
||||||
let res = http_client::get_simple(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
|
let res = http_client::get_simple(&format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
|
||||||
let catalog = res.into_json::<SkymapCatalog>()?;
|
let catalog = res.into_json::<SkymapCatalog>()?;
|
||||||
catalog.validate()?;
|
catalog.validate()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,7 @@ impl ViewTrait for View {
|
||||||
if let Some(on_downloaded) = self.on_downloaded.take() {
|
if let Some(on_downloaded) = self.on_downloaded.take() {
|
||||||
self
|
self
|
||||||
.executor
|
.executor
|
||||||
.spawn(View::download(
|
.spawn(View::download(self.tasks.clone(), url, path, on_downloaded))
|
||||||
self.tasks.clone(),
|
|
||||||
self.executor.clone(),
|
|
||||||
url,
|
|
||||||
path,
|
|
||||||
on_downloaded,
|
|
||||||
))
|
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -189,37 +183,12 @@ impl View {
|
||||||
|
|
||||||
async fn download(
|
async fn download(
|
||||||
tasks: Tasks<Task>,
|
tasks: Tasks<Task>,
|
||||||
executor: AsyncExecutor,
|
|
||||||
url: String,
|
url: String,
|
||||||
target_path: PathBuf,
|
target_path: PathBuf,
|
||||||
on_downloaded: Box<dyn FnOnce()>,
|
on_downloaded: Box<dyn FnOnce()>,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
tasks.push(Task::SetStatusText(String::from("Connecting to the server...")));
|
tasks.push(Task::SetStatusText(String::from("Connecting to the server...")));
|
||||||
|
|
||||||
// start downloading from the server with progress reporting
|
|
||||||
let res = handle_async_result(
|
|
||||||
"Download failed",
|
|
||||||
&tasks,
|
|
||||||
http_client::get(http_client::GetParams {
|
|
||||||
executor: &executor,
|
|
||||||
url: &url,
|
|
||||||
on_progress: Some(Box::new({
|
|
||||||
let tasks = tasks.clone();
|
|
||||||
move |data: ProgressFuncData| {
|
|
||||||
tasks.push(Task::SetStatusText(format!(
|
|
||||||
"{}/{} KiB ({}%)",
|
|
||||||
data.bytes_downloaded / 1024,
|
|
||||||
data.file_size / 1024,
|
|
||||||
(data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
.await,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
tasks.push(Task::SetStatusText(String::from("Writing to file...")));
|
|
||||||
|
|
||||||
// create parent directory if it doesn't exist yet
|
// create parent directory if it doesn't exist yet
|
||||||
if let Some(parent) = target_path.parent() {
|
if let Some(parent) = target_path.parent() {
|
||||||
handle_async_result(
|
handle_async_result(
|
||||||
|
|
@ -229,10 +198,28 @@ impl View {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start downloading from the server with progress reporting
|
||||||
handle_async_result(
|
handle_async_result(
|
||||||
"File write failed",
|
"Download failed",
|
||||||
&tasks,
|
&tasks,
|
||||||
smol::fs::write(target_path, res.data).await,
|
http_client::download_to_file(
|
||||||
|
http_client::GetParams {
|
||||||
|
url: &url,
|
||||||
|
on_progress: Some(Box::new({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
move |data: ProgressFuncData| {
|
||||||
|
tasks.push(Task::SetStatusText(format!(
|
||||||
|
"{}/{} MiB ({}%)",
|
||||||
|
data.bytes_downloaded / 1024 / 1024,
|
||||||
|
data.file_size / 1024 / 1024,
|
||||||
|
(data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
&target_path,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
tasks.push(Task::SetStatusText(String::from("Download finished")));
|
tasks.push(Task::SetStatusText(String::from("Download finished")));
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,8 @@ const GAME_COVER_SIZE_X: f32 = 140.0;
|
||||||
const GAME_COVER_SIZE_Y: f32 = 210.0;
|
const GAME_COVER_SIZE_Y: f32 = 210.0;
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
async fn request_cover_image(
|
async fn request_cover_image(manifest: steam_utils::AppManifest, on_loaded: Box<dyn FnOnce(CoverArt)>) {
|
||||||
executor: AsyncExecutor,
|
let cover_art = match cached_fetcher::request_image(manifest.app_id.clone()).await {
|
||||||
manifest: steam_utils::AppManifest,
|
|
||||||
on_loaded: Box<dyn FnOnce(CoverArt)>,
|
|
||||||
) {
|
|
||||||
let cover_art = match cached_fetcher::request_image(executor, manifest.app_id.clone()).await {
|
|
||||||
Ok(cover_art) => cover_art,
|
Ok(cover_art) => cover_art,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("request_cover_image failed: {:?}", e);
|
log::error!("request_cover_image failed: {:?}", e);
|
||||||
|
|
@ -285,7 +281,6 @@ impl View {
|
||||||
params
|
params
|
||||||
.executor
|
.executor
|
||||||
.spawn(View::request_cover_image(
|
.spawn(View::request_cover_image(
|
||||||
params.executor.clone(),
|
|
||||||
params.manifest.clone(),
|
params.manifest.clone(),
|
||||||
Box::new(params.on_loaded),
|
Box::new(params.on_loaded),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ impl ViewTrait for View {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) {
|
async fn fetch_details(tasks: Tasks<Task>, app_id: AppID) {
|
||||||
let Some(details) = cached_fetcher::get_app_details_json(executor, app_id).await else {
|
let Some(details) = cached_fetcher::get_app_details_json(app_id).await else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ impl View {
|
||||||
let tasks = Tasks::new();
|
let tasks = Tasks::new();
|
||||||
|
|
||||||
// fetch details from the web
|
// fetch details from the web
|
||||||
let fut = View::fetch_details(params.executor.clone(), tasks.clone(), params.manifest.app_id.clone());
|
let fut = View::fetch_details(tasks.clone(), params.manifest.app_id.clone());
|
||||||
params.executor.spawn(fut).detach();
|
params.executor.spawn(fut).detach();
|
||||||
|
|
||||||
let id_cover_art_parent = state.get_widget_id("cover_art_parent")?;
|
let id_cover_art_parent = state.get_widget_id("cover_art_parent")?;
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,8 @@ impl ViewTrait for View {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) {
|
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>) {
|
||||||
let res = networking::skymap_catalog::request_catalog(&executor).await;
|
let res = networking::skymap_catalog::request_catalog().await;
|
||||||
tasks.push(Task::SetSkymapCatalog(Rc::new(res)))
|
tasks.push(Task::SetSkymapCatalog(Rc::new(res)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ impl View {
|
||||||
with_text: true,
|
with_text: true,
|
||||||
})?;
|
})?;
|
||||||
let tasks = Tasks::<Task>::new();
|
let tasks = Tasks::<Task>::new();
|
||||||
let fut = View::skymap_catalog_request_wrapper(tasks.clone(), par.executor.clone());
|
let fut = View::skymap_catalog_request_wrapper(tasks.clone());
|
||||||
par.executor.spawn(fut).detach();
|
par.executor.spawn(fut).detach();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id_parent: par.parent_id,
|
id_parent: par.parent_id,
|
||||||
|
|
@ -147,15 +147,10 @@ impl View {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request_skymap_preview(
|
async fn request_skymap_preview(globals: WguiGlobals, entry: SkymapCatalogEntry, tasks: Tasks<Task>) {
|
||||||
globals: WguiGlobals,
|
|
||||||
executor: AsyncExecutor,
|
|
||||||
entry: SkymapCatalogEntry,
|
|
||||||
tasks: Tasks<Task>,
|
|
||||||
) {
|
|
||||||
tasks.push(Task::SetSkymapPreview((
|
tasks.push(Task::SetSkymapPreview((
|
||||||
entry.uuid,
|
entry.uuid,
|
||||||
networking::image_fetch::fetch_to_glyph_data(&globals, &executor, &entry.files.get_url_preview())
|
networking::image_fetch::fetch_to_glyph_data(&globals, &entry.files.get_url_preview())
|
||||||
.await
|
.await
|
||||||
.ok(),
|
.ok(),
|
||||||
)));
|
)));
|
||||||
|
|
@ -192,12 +187,7 @@ impl View {
|
||||||
let id_list = parser_state.fetch_widget(&layout.state, "list")?.id;
|
let id_list = parser_state.fetch_widget(&layout.state, "list")?.id;
|
||||||
|
|
||||||
for entry in &catalog.entries {
|
for entry in &catalog.entries {
|
||||||
let task = View::request_skymap_preview(
|
let task = View::request_skymap_preview(self.globals.clone(), entry.clone(), self.tasks.clone());
|
||||||
self.globals.clone(),
|
|
||||||
self.executor.clone(),
|
|
||||||
entry.clone(),
|
|
||||||
self.tasks.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let skymap_uuid = entry.uuid;
|
let skymap_uuid = entry.uuid;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,4 @@ tracing-subscriber.workspace = true
|
||||||
vulkano.workspace = true
|
vulkano.workspace = true
|
||||||
vulkano-shaders.workspace = true
|
vulkano-shaders.workspace = true
|
||||||
winit.workspace = true
|
winit.workspace = true
|
||||||
|
ureq = "3.3.0"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue