error handling part 1

This commit is contained in:
Pauline 2024-03-23 04:15:11 +01:00
parent 15f3021701
commit e661921539
No known key found for this signature in database
GPG key ID: 722BB181973296AE
6 changed files with 87 additions and 33 deletions

View file

@ -27,6 +27,7 @@ zip = { workspace = true }
uuid = { workspace = true }
async-trait = { workspace = true }
prisma-client-rust = { workspace = true }
thiserror = { workspace = true }
[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!

View file

@ -10,7 +10,7 @@ use super::{Account, AuthenticationMethod};
pub(super) struct MicrosoftAuthenticationMethod;
impl AuthenticationMethod for MicrosoftAuthenticationMethod {
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> Result<Account, Box<dyn Error>>
async fn auth<R: Runtime, F>(handle: &AppHandle<R>, stage: F) -> <Account, Box<dyn Error>>
where F: Fn(String, u8, bool) -> () {
stage("Authenticating with Microsoft".into(), 0, false);

View file

@ -1,5 +1,9 @@
// Basic Metadata
pub const NAME: &str = "OneLauncher";
pub const VERSION: &str = "0.1.0";
// Authentication and HTTP (TODO: env?)
pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)";
pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56";
pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56";
// Java Downloading

View file

@ -1,12 +1,13 @@
use std::{collections::HashMap, env, error::Error, fs, path::PathBuf};
use std::{collections::HashMap, env, fs, path::PathBuf};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tauri::{AppHandle, Manager};
use thiserror::Error;
use uuid::Uuid;
use crate::utils::{file, http};
use crate::{utils::{file, http}, PolyError, PolyResult};
use super::{clients::vanilla::{vanilla_manifest, VanillaClient, VanillaManifest}, JavaVersion};
@ -61,8 +62,8 @@ pub fn get_game_client(handle: AppHandle, details: GameClientDetails) -> Box<dyn
pub trait GameClient: Send + Sync {
fn new(handle: AppHandle, details: GameClientDetails) -> Self where Self: Sized;
async fn get_all_version_urls() -> Result<HashMap<String, String>, Box<dyn Error>> where Self: Sized;
async fn get_minecraft_manifest(version: String) -> Result<vanilla_manifest::MinecraftManifest, Box<dyn Error>> where Self: Sized;
async fn get_all_version_urls() -> PolyResult<HashMap<String, String>> where Self: Sized;
async fn get_minecraft_manifest(version: String) -> PolyResult<vanilla_manifest::MinecraftManifest> where Self: Sized;
// async fn get_details_from_version(version: String) -> Result<GameClientDetails, Box<dyn Error>> where Self: Sized;
@ -72,13 +73,14 @@ pub trait GameClient: Send + Sync {
&self.get_details().client_type
}
async fn launch(&self) -> Result<(), Box<dyn Error>>;
async fn setup(&self) -> Result<(), Box<dyn Error>>;
async fn launch(&self) -> PolyResult<()>;
async fn setup(&self) -> PolyResult<()>;
async fn download_java(&self) -> Result<PathBuf, JavaDownloadError> {
async fn download_java(&self) -> PolyResult<PathBuf> {
let config_dir = match self.get_handle().path().app_config_dir() {
Ok(dir) => dir,
Err(_) => return Err(JavaDownloadError::UnsupportedOS)
// SURELY THERES A BETTER WAY TO DO THIS IDK LOLL
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(err.to_string())))
};
let java_dir = config_dir.join("java");
@ -86,7 +88,7 @@ pub trait GameClient: Send + Sync {
if !java_dir.exists() {
match fs::create_dir_all(&java_dir) {
Ok(_) => (),
Err(_) => return Err(JavaDownloadError::PermissionDenied)
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::PermissionDenied(err.to_string())))
};
}
@ -96,7 +98,7 @@ pub trait GameClient: Send + Sync {
"windows" => "zip",
"macos" => "tar.gz",
"linux" => "tar.gz",
_ => return Err(JavaDownloadError::UnsupportedOS)
_ => return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(os.to_string())))
};
let archive_name = format!("zulu-{}.{}", java_version.to_string(), archive_type);
@ -115,29 +117,35 @@ pub trait GameClient: Send + Sync {
if let Ok(mut files) = fs::read_dir(&dest) {
let file = match files.nth(0) {
Some(file) => file.unwrap().path(),
None => return Err(JavaDownloadError::NoJavaVersionFound)
None => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("unable to get the java executable file".to_string())))
};
return Ok(file.join("bin").join("java"));
}
Err(JavaDownloadError::NoJavaVersionFound)
Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("unable to download the java executable file".to_string())))
}
}
#[async_trait]
pub trait ModdedGameClient: GameClient {
async fn install_mods(&self) -> Result<(), Box<dyn Error>>;
async fn install_mods(&self) -> PolyResult<()>;
}
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum JavaDownloadError {
NoJavaVersionFound,
PermissionDenied,
UnsupportedOS,
UnsupportedArch,
ExtractError,
DownloadError,
#[error("failed to find a valid java version: {0}")]
NoJavaVersionFound(String),
#[error("failed creating a java directory (try running at an elevated permission): {0}")]
PermissionDenied(String),
#[error("unable to match your operating system ({0}) with a valid java version")]
UnsupportedOS(String),
#[error("unable to match your architecture ({0}) with a valid java version")]
UnsupportedArch(String),
#[error("failed to extract the java executable: {0}")]
ExtractError(String),
#[error("failed to download the java executable")]
DownloadError(String),
}
fn get_arch() -> String {
@ -151,7 +159,7 @@ fn get_arch() -> String {
}.to_string()
}
async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: &str) -> Result<Value, JavaDownloadError> {
async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: &str) -> PolyResult<Value> {
let url = format!("
https://api.azul.com/metadata/v1/zulu/packages/\
?java_version={}\
@ -168,38 +176,38 @@ async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: &
let response = match http::create_client().get(&url).send().await {
Ok(response) => match response.json::<serde_json::Value>().await {
Ok(json) => json,
Err(_) => return Err(JavaDownloadError::NoJavaVersionFound)
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(err.to_string())))
},
Err(_) => return Err(JavaDownloadError::NoJavaVersionFound)
Err(err) => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(err.to_string())))
};
if !response.is_array() || response.as_array().unwrap().is_empty() {
return Err(JavaDownloadError::NoJavaVersionFound);
return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("didn't get the expected java api result".to_string())));
}
Ok(response)
}
async fn download(java_version: &JavaVersion, os: &str, archive_type: &str, archive: &PathBuf) -> Result<(), JavaDownloadError> {
async fn download(java_version: &JavaVersion, os: &str, archive_type: &str, archive: &PathBuf) -> PolyResult<()> {
let response = get_java_versions(java_version, os, archive_type).await?;
let latest = response.as_array().unwrap().first().unwrap();
let download_url = latest.get("download_url").unwrap().as_str().unwrap();
if let Err(err) = http::download_file(download_url, archive.as_path()).await {
eprintln!("{}", err);
return Err(JavaDownloadError::DownloadError);
// eprintln!("{}", err);
return Err(PolyError::JavaError(JavaDownloadError::DownloadError(err.to_string())));
};
Ok(())
}
fn extract(archive: &PathBuf, dest: &PathBuf) -> Result<(), JavaDownloadError> {
fn extract(archive: &PathBuf, dest: &PathBuf) -> PolyResult<()> {
if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) {
eprintln!("{}", err);
// eprintln!("{}", err);
let _ = fs::remove_file(dest.as_path());
return Err(JavaDownloadError::ExtractError);
return Err(PolyError::JavaError(JavaDownloadError::ExtractError(err.to_string())));
}
let _ = fs::remove_file(archive.as_path());
Ok(())
}
}

View file

View file

@ -1,4 +1,45 @@
use thiserror::Error;
pub mod auth;
pub mod game;
pub mod utils;
pub mod constants;
/// A standardized [`Error`] type that is used across the launcher
#[derive(Debug, Error)]
pub enum PolyError {
#[error("a tauri runtime error occured (this should not happen): {0}")]
/// Wrapper around [`tauri::Error`] to handle expected Tauri errors.
TauriError(#[from] tauri::Error),
#[error("failed to manage a tokio semaphore: {0}")]
/// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors.
TokioError(#[from] tokio::sync::AcquireError),
#[error("failed to compress a file with flate: {0}")]
/// Wrapper around [`flate2::CompressError`] to handle flate compression errors.
FlateError(#[from] flate2::CompressError),
// im actually the funniest person alive dont @ me
#[error("failed to decompress a file with flate: {0}")]
/// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors.
DeflateError(#[from] flate2::DecompressError),
#[error("failed to parse uuids: {0}")]
/// Wrapper around [`uuid::Error`] to handle UUID parsing errors.
UUIDError(#[from] uuid::Error),
#[error("failed to manage serialization of json: {0}")]
/// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors.
SerdeError(#[from] serde_json::Error),
#[error("failed to manage writing and reading of files: {0}")]
/// Wrapper around [`std::io::Error`] only to be used if a more defined error isn't available (e.g. when using `zip`)
IOError(#[from] std::io::Error),
#[error("failed to query the database: {0}")]
/// Wrapper around [`prisma_client_rust::QueryError`] to handle DB query errors.
DBQueryError(#[from] prisma_client_rust::QueryError),
#[error("failed to establish a connection to the database: {0}")]
/// Wrapper around [`prisma_client_rust::NewClientError`] to handle errors occuring while programmatically connecting to Prisma.
DBConnectionError(#[from] prisma_client_rust::NewClientError),
#[error("failed to download java: {0}")]
/// Wrapper around [`game::client::JavaDownloadError`] to handle Java executable downloading.
JavaError(#[from] game::client::JavaDownloadError),
}
/// Alias for a [`Result`] with the error type [`PolyError`].
pub type PolyResult<T> = std::result::Result<T, PolyError>;