chore: Add CacheError and remove unwraps from fetch_object

Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
quexeky
2025-09-05 17:57:31 +10:00
parent ea6fa551a2
commit be5500d29f
6 changed files with 94 additions and 40 deletions

View File

@ -0,0 +1,24 @@
use std::fmt::Display;
use http::{header::ToStrError, HeaderName};
use serde_with::SerializeDisplay;
use crate::error::remote_access_error::RemoteAccessError;
#[derive(Debug, SerializeDisplay)]
pub enum CacheError {
HeaderNotFound(HeaderName),
ParseError(ToStrError),
Remote(RemoteAccessError)
}
impl Display for CacheError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CacheError::HeaderNotFound(header_name) => write!(f, "Could not find header {} in cache", header_name),
CacheError::ParseError(to_str_error) => write!(f, "Could not parse cache with error {}", to_str_error),
CacheError::Remote(remote_access_error) => write!(f, "Cache got remote access error: {}", remote_access_error),
}
}
}

View File

@ -4,3 +4,4 @@ pub mod drop_server_error;
pub mod library_error;
pub mod process_error;
pub mod remote_access_error;
pub mod cache_error;

View File

@ -20,6 +20,7 @@ use crate::database::scan::scan_install_dirs;
use crate::process::commands::open_process_logs;
use crate::process::process_handlers::UMU_LAUNCHER_EXECUTABLE;
use crate::remote::commands::auth_initiate_code;
use crate::remote::fetch_object::fetch_object_wrapper;
use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download};
use bitcode::{Decode, Encode};
use client::commands::fetch_state;
@ -47,7 +48,7 @@ use games::commands::{
};
use games::downloads::commands::download_game;
use games::library::{Game, update_game_configuration};
use log::{LevelFilter, debug, info, warn, error};
use log::{LevelFilter, debug, info, warn};
use log4rs::Config;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
@ -60,7 +61,6 @@ use remote::commands::{
auth_initiate, fetch_drop_object, gen_drop_url, manual_recieve_handshake, retry_connect,
sign_out, use_remote,
};
use remote::fetch_object::fetch_object;
use remote::server_proto::{handle_server_proto, handle_server_proto_offline};
use serde::{Deserialize, Serialize};
use std::fs::File;
@ -462,7 +462,7 @@ pub fn run() {
})
.register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
tauri::async_runtime::spawn(async move {
fetch_object(request, responder).await;
fetch_object_wrapper(request, responder).await;
});
})
.register_asynchronous_uri_scheme_protocol("server", |ctx, request, responder| {

View File

@ -7,16 +7,16 @@ use std::{
use crate::{
database::{db::borrow_db_checked, models::data::Database},
error::remote_access_error::RemoteAccessError,
error::{cache_error::CacheError, remote_access_error::RemoteAccessError},
};
use bitcode::{Decode, DecodeOwned, Encode};
use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder};
use http::{header::{ToStrError, CONTENT_TYPE}, response::Builder as ResponseBuilder, Response};
#[macro_export]
macro_rules! offline {
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
async move { if $crate::borrow_db_checked().settings.force_offline || $var.lock().unwrap().status == $crate::AppStatus::Offline {
async move { if $crate::borrow_db_checked().settings.force_offline || $crate::state_lock!($var).status == $crate::AppStatus::Offline {
$func2( $( $arg ), *).await
} else {
$func1( $( $arg ), *).await
@ -87,19 +87,22 @@ impl ObjectCache {
}
}
impl From<Response<Vec<u8>>> for ObjectCache {
fn from(value: Response<Vec<u8>>) -> Self {
ObjectCache {
impl TryFrom<Response<Vec<u8>>> for ObjectCache {
type Error = CacheError;
fn try_from(value: Response<Vec<u8>>) -> Result<Self, Self::Error> {
Ok(ObjectCache {
content_type: value
.headers()
.get(CONTENT_TYPE)
.unwrap()
.ok_or(CacheError::HeaderNotFound(CONTENT_TYPE))?
.to_str()
.unwrap()
.map_err(CacheError::ParseError)?
.to_owned(),
body: value.body().clone(),
expiry: get_sys_time_in_secs() + 60 * 60 * 24,
}
})
}
}
impl From<ObjectCache> for Response<Vec<u8>> {

View File

@ -1,15 +1,26 @@
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder};
use log::warn;
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder, Response};
use log::{debug, warn};
use tauri::UriSchemeResponder;
use crate::{database::db::DatabaseImpls, remote::utils::DROP_CLIENT_ASYNC, DB};
use crate::{database::db::DatabaseImpls, error::cache_error::CacheError, remote::utils::DROP_CLIENT_ASYNC, DB};
use super::{
auth::generate_authorization_header,
cache::{ObjectCache, cache_object, get_cached_object},
};
pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
pub async fn fetch_object_wrapper(request: http::Request<Vec<u8>>, responder: UriSchemeResponder) {
match fetch_object(request).await {
Ok(r) => responder.respond(r),
Err(e) => {
warn!("Cache error: {}", e);
responder.respond(Response::new(Vec::new()));
}
};
}
pub async fn fetch_object(request: http::Request<Vec<u8>>) -> Result<Response<Vec<u8>>, CacheError>
{
// Drop leading /
let object_id = &request.uri().path()[1..];
@ -17,8 +28,7 @@ pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeR
if let Ok(cache_result) = &cache_result
&& !cache_result.has_expired()
{
responder.respond(cache_result.into());
return;
return Ok(cache_result.into());
}
let header = generate_authorization_header();
@ -26,26 +36,41 @@ pub async fn fetch_object(request: http::Request<Vec<u8>>, responder: UriSchemeR
let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url());
let response = client.get(url).header("Authorization", header).send().await;
if response.is_err() {
match cache_result {
Ok(cache_result) => responder.respond(cache_result.into()),
Err(e) => {
warn!("{e}");
}
}
return;
}
let response = response.unwrap();
match response {
Ok(r) => {
let resp_builder = ResponseBuilder::new().header(
CONTENT_TYPE,
response.headers().get("Content-Type").unwrap(),
r.headers()
.get("Content-Type")
.expect("Failed get Content-Type header"),
);
let data = Vec::from(response.bytes().await.unwrap());
let resp = resp_builder.body(data).unwrap();
if cache_result.is_err() || cache_result.unwrap().has_expired() {
cache_object::<ObjectCache>(object_id, &resp.clone().into()).unwrap();
let data = match r.bytes().await {
Ok(data) => Vec::from(data),
Err(e) => {
warn!(
"Could not get data from cache object {} with error {}",
object_id, e
);
Vec::new()
}
};
let resp = resp_builder.body(data).expect("Failed to build object cache response body");
if cache_result.map_or(true, |x| x.has_expired()) {
cache_object::<ObjectCache>(object_id, &resp.clone().try_into()?)
.expect("Failed to create cached object");
}
responder.respond(resp);
return Ok(resp.into());
}
Err(e) => {
debug!("Object fetch failed with error {}. Attempting to download from cache", e);
return match cache_result {
Ok(cache_result) => Ok(cache_result.into()),
Err(e) => {
warn!("{e}");
Err(CacheError::Remote(e))
}
}
}
}
}

View File

@ -50,9 +50,10 @@ fn fetch_certificates() -> Vec<Certificate> {
}
}
.read_to_end(&mut buf)
.expect(&format!(
"Failed to read to end of certificate file {}",
c.path().display()
.unwrap_or_else(|e| panic!(
"Failed to read to end of certificate file {} with error {}",
c.path().display(),
e
));
match Certificate::from_pem_bundle(&buf) {