feat: improve errors and include installed games in library

This commit is contained in:
DecDuck
2025-04-05 15:36:53 +11:00
parent 1fdf569278
commit 3e074abc0a
5 changed files with 38 additions and 15 deletions

View File

@ -85,5 +85,6 @@ const message =
props.error?.statusMessage || props.error?.statusMessage ||
props.error?.message || props.error?.message ||
"An unknown error occurred."; "An unknown error occurred.";
const showSignIn = statusCode ? statusCode == 403 || statusCode == 401 : false;
console.error(props.error);
</script> </script>

View File

@ -29,6 +29,9 @@
Drop encountered an error that it couldn't handle. Please Drop encountered an error that it couldn't handle. Please
restart the application and file a bug report. restart the application and file a bug report.
</p> </p>
<p class="mt-3 text-sm font-monospace text-zinc-500">
Error: {{ error }}
</p>
</div> </div>
</main> </main>
<footer <footer

View File

@ -16,7 +16,7 @@ pub enum RemoteAccessError {
ParsingError(ParseError), ParsingError(ParseError),
InvalidEndpoint, InvalidEndpoint,
HandshakeFailed(String), HandshakeFailed(String),
GameNotFound, GameNotFound(String),
InvalidResponse(DropServerError), InvalidResponse(DropServerError),
InvalidRedirect, InvalidRedirect,
ManifestDownloadFailed(StatusCode, String), ManifestDownloadFailed(StatusCode, String),
@ -43,7 +43,7 @@ impl Display for RemoteAccessError {
} }
RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"), RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {}", message), RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {}", message),
RemoteAccessError::GameNotFound => write!(f, "could not find game on server"), RemoteAccessError::GameNotFound(id) => write!(f, "could not find game on server: {}", id),
RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {} {}", error.status_code, error.status_message), RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {} {}", error.status_code, error.status_message),
RemoteAccessError::InvalidRedirect => write!(f, "server redirect was invalid"), RemoteAccessError::InvalidRedirect => write!(f, "server redirect was invalid"),
RemoteAccessError::ManifestDownloadFailed(status, response) => write!( RemoteAccessError::ManifestDownloadFailed(status, response) => write!(

View File

@ -14,7 +14,7 @@ use crate::download_manager::downloadable_metadata::DownloadableMetadata;
use crate::error::remote_access_error::RemoteAccessError; use crate::error::remote_access_error::RemoteAccessError;
use crate::games::state::{GameStatusManager, GameStatusWithTransient}; use crate::games::state::{GameStatusManager, GameStatusWithTransient};
use crate::remote::auth::generate_authorization_header; use crate::remote::auth::generate_authorization_header;
use crate::remote::cache::{cache_object, get_cached_object}; use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
use crate::remote::requests::make_request; use crate::remote::requests::make_request;
use crate::AppState; use crate::AppState;
@ -85,7 +85,7 @@ pub fn fetch_library_logic(
return Err(RemoteAccessError::InvalidResponse(err)); return Err(RemoteAccessError::InvalidResponse(err));
} }
let games: Vec<Game> = response.json()?; let mut games: Vec<Game> = response.json()?;
let mut handle = state.lock().unwrap(); let mut handle = state.lock().unwrap();
@ -100,6 +100,18 @@ pub fn fetch_library_logic(
.insert(game.id.clone(), GameDownloadStatus::Remote {}); .insert(game.id.clone(), GameDownloadStatus::Remote {});
} }
} }
// Add games that are installed but no longer in library
for (_, meta) in &db_handle.applications.installed_game_version {
if games.iter().find(|e| e.id == meta.id).is_some() {
continue;
}
// We should always have a cache of the object
// Pass db_handle because otherwise we get a gridlock
let game = get_cached_object_db::<String, Game>(meta.id.clone(), &db_handle)?;
games.push(game);
}
drop(handle); drop(handle);
drop(db_handle); drop(db_handle);
cache_object("library", &games)?; cache_object("library", &games)?;
@ -142,13 +154,13 @@ pub fn fetch_game_logic(
return Ok(data); return Ok(data);
} }
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
let response = make_request(&client, &["/api/v1/game/", &id], &[], |r| { let response = make_request(&client, &["/api/v1/client/game/", &id], &[], |r| {
r.header("Authorization", generate_authorization_header()) r.header("Authorization", generate_authorization_header())
})? })?
.send()?; .send()?;
if response.status() == 404 { if response.status() == 404 {
return Err(RemoteAccessError::GameNotFound); return Err(RemoteAccessError::GameNotFound(id));
} }
if response.status() != 200 { if response.status() != 200 {
let err = response.json().unwrap(); let err = response.json().unwrap();
@ -312,7 +324,7 @@ pub fn on_game_complete(
) -> Result<(), RemoteAccessError> { ) -> Result<(), RemoteAccessError> {
// Fetch game version information from remote // Fetch game version information from remote
if meta.version.is_none() { if meta.version.is_none() {
return Err(RemoteAccessError::GameNotFound); return Err(RemoteAccessError::GameNotFound(meta.id.clone()));
} }
let header = generate_authorization_header(); let header = generate_authorization_header();
@ -320,7 +332,7 @@ pub fn on_game_complete(
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
let response = make_request( let response = make_request(
&client, &client,
&["/api/v1/client/metadata/version"], &["/api/v1/client/game/version"],
&[ &[
("id", &meta.id), ("id", &meta.id),
("version", meta.version.as_ref().unwrap()), ("version", meta.version.as_ref().unwrap()),

View File

@ -1,11 +1,14 @@
use crate::{database::db::borrow_db_checked, error::remote_access_error::RemoteAccessError}; use std::sync::RwLockReadGuard;
use crate::{
database::db::{borrow_db_checked, Database},
error::remote_access_error::RemoteAccessError,
};
use cacache::Integrity; use cacache::Integrity;
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder, Response}; use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder, Response};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_binary::binary_stream::Endian; use serde_binary::binary_stream::Endian;
use tauri::{UriSchemeContext, UriSchemeResponder};
use super::{auth::generate_authorization_header, requests::make_request};
#[macro_export] #[macro_export]
macro_rules! offline { macro_rules! offline {
@ -30,12 +33,16 @@ pub fn cache_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
pub fn get_cached_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>( pub fn get_cached_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
key: K, key: K,
) -> Result<D, RemoteAccessError> { ) -> Result<D, RemoteAccessError> {
let bytes = cacache::read_sync(&borrow_db_checked().cache_dir, key) get_cached_object_db::<K, D>(key, &borrow_db_checked())
.map_err(|e| RemoteAccessError::Cache(e))?; }
pub fn get_cached_object_db<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
key: K,
db: &Database,
) -> Result<D, RemoteAccessError> {
let bytes = cacache::read_sync(&db.cache_dir, key).map_err(|e| RemoteAccessError::Cache(e))?;
let data = serde_binary::from_slice::<D>(&bytes, Endian::Little).unwrap(); let data = serde_binary::from_slice::<D>(&bytes, Endian::Little).unwrap();
Ok(data) Ok(data)
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ObjectCache { pub struct ObjectCache {
content_type: String, content_type: String,