diff --git a/src-tauri/src/error/cache_error.rs b/src-tauri/src/error/cache_error.rs new file mode 100644 index 0000000..2495bc0 --- /dev/null +++ b/src-tauri/src/error/cache_error.rs @@ -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), + } + } +} + diff --git a/src-tauri/src/error/mod.rs b/src-tauri/src/error/mod.rs index 80bc585..874837f 100644 --- a/src-tauri/src/error/mod.rs +++ b/src-tauri/src/error/mod.rs @@ -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; \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 8061d3d..a19d5db 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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| { diff --git a/src-tauri/src/remote/cache.rs b/src-tauri/src/remote/cache.rs index c23be49..2bd0115 100644 --- a/src-tauri/src/remote/cache.rs +++ b/src-tauri/src/remote/cache.rs @@ -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>> for ObjectCache { - fn from(value: Response>) -> Self { - ObjectCache { +impl TryFrom>> for ObjectCache { + type Error = CacheError; + + fn try_from(value: Response>) -> Result { + 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 for Response> { diff --git a/src-tauri/src/remote/fetch_object.rs b/src-tauri/src/remote/fetch_object.rs index 4b5d7fb..4aa47cf 100644 --- a/src-tauri/src/remote/fetch_object.rs +++ b/src-tauri/src/remote/fetch_object.rs @@ -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>, responder: UriSchemeResponder) { +pub async fn fetch_object_wrapper(request: http::Request>, 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>) -> Result>, CacheError> +{ // Drop leading / let object_id = &request.uri().path()[1..]; @@ -17,8 +28,7 @@ pub async fn fetch_object(request: http::Request>, 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>, 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}"); + match response { + Ok(r) => { + let resp_builder = ResponseBuilder::new().header( + CONTENT_TYPE, + r.headers() + .get("Content-Type") + .expect("Failed get Content-Type header"), + ); + 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::(object_id, &resp.clone().try_into()?) + .expect("Failed to create cached object"); + } + + 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)) + } } } - return; } - let response = response.unwrap(); - - let resp_builder = ResponseBuilder::new().header( - CONTENT_TYPE, - response.headers().get("Content-Type").unwrap(), - ); - 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::(object_id, &resp.clone().into()).unwrap(); - } - - responder.respond(resp); } diff --git a/src-tauri/src/remote/utils.rs b/src-tauri/src/remote/utils.rs index 69a0ac8..7ecac5e 100644 --- a/src-tauri/src/remote/utils.rs +++ b/src-tauri/src/remote/utils.rs @@ -50,9 +50,10 @@ fn fetch_certificates() -> Vec { } } .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) {