forked from didirus/AstralRinth
Move files in preparation for monorepo migration
This commit is contained in:
106
apps/theseus_gui/src-tauri/src/api/auth.rs
Normal file
106
apps/theseus_gui/src-tauri/src/api/auth.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use crate::api::Result;
|
||||
use chrono::{Duration, Utc};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Manager, UserAttentionType};
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("auth")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
auth_get_default_user,
|
||||
auth_set_default_user,
|
||||
auth_remove_user,
|
||||
auth_users,
|
||||
auth_get_user,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Authenticate a user with Hydra - part 1
|
||||
/// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at)
|
||||
#[tauri::command]
|
||||
pub async fn auth_login(app: tauri::AppHandle) -> Result<Option<Credentials>> {
|
||||
let flow = minecraft_auth::begin_login().await?;
|
||||
|
||||
let start = Utc::now();
|
||||
|
||||
if let Some(window) = app.get_window("signin") {
|
||||
window.close()?;
|
||||
}
|
||||
|
||||
let window = tauri::WindowBuilder::new(
|
||||
&app,
|
||||
"signin",
|
||||
tauri::WindowUrl::External(flow.redirect_uri.parse().map_err(
|
||||
|_| {
|
||||
theseus::ErrorKind::OtherError(
|
||||
"Error parsing auth redirect URL".to_string(),
|
||||
)
|
||||
.as_error()
|
||||
},
|
||||
)?),
|
||||
)
|
||||
.title("Sign into Modrinth")
|
||||
.always_on_top(true)
|
||||
.center()
|
||||
.build()?;
|
||||
|
||||
window.request_user_attention(Some(UserAttentionType::Critical))?;
|
||||
|
||||
while (Utc::now() - start) < Duration::minutes(10) {
|
||||
if window.title().is_err() {
|
||||
// user closed window, cancelling flow
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if window
|
||||
.url()
|
||||
.as_str()
|
||||
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
||||
{
|
||||
if let Some((_, code)) =
|
||||
window.url().query_pairs().find(|x| x.0 == "code")
|
||||
{
|
||||
window.close()?;
|
||||
let val =
|
||||
minecraft_auth::finish_login(&code.clone(), flow).await?;
|
||||
|
||||
return Ok(Some(val));
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
window.close()?;
|
||||
Ok(None)
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> {
|
||||
Ok(minecraft_auth::remove_user(user).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn auth_get_default_user() -> Result<Option<uuid::Uuid>> {
|
||||
Ok(minecraft_auth::get_default_user().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn auth_set_default_user(user: uuid::Uuid) -> Result<()> {
|
||||
Ok(minecraft_auth::set_default_user(user).await?)
|
||||
}
|
||||
|
||||
/// Get a copy of the list of all user credentials
|
||||
// invoke('plugin:auth|auth_users',user)
|
||||
#[tauri::command]
|
||||
pub async fn auth_users() -> Result<Vec<Credentials>> {
|
||||
Ok(minecraft_auth::users().await?)
|
||||
}
|
||||
|
||||
/// Get a user from the UUID
|
||||
/// Prefer to use refresh instead, as it will refresh the credentials as well
|
||||
// invoke('plugin:auth|auth_users',user)
|
||||
#[tauri::command]
|
||||
pub async fn auth_get_user(user: uuid::Uuid) -> Result<Credentials> {
|
||||
Ok(minecraft_auth::get_user(user).await?)
|
||||
}
|
||||
71
apps/theseus_gui/src-tauri/src/api/import.rs
Normal file
71
apps/theseus_gui/src-tauri/src/api/import.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::api::Result;
|
||||
use theseus::pack::import::ImportLauncherType;
|
||||
|
||||
use theseus::pack::import;
|
||||
use theseus::prelude::ProfilePathId;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("import")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
import_get_importable_instances,
|
||||
import_import_instance,
|
||||
import_is_valid_importable_instance,
|
||||
import_get_default_launcher_path,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Gets a list of importable instances from a launcher type and base path
|
||||
/// eg: get_importable_instances(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"))
|
||||
/// returns ["Instance 1", "Instance 2"]
|
||||
#[tauri::command]
|
||||
pub async fn import_get_importable_instances(
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
) -> Result<Vec<String>> {
|
||||
Ok(import::get_importable_instances(launcher_type, base_path).await?)
|
||||
}
|
||||
|
||||
/// Import an instance from a launcher type and base path
|
||||
/// profile_path should be a blank profile for this purpose- if the function fails, it will be deleted
|
||||
/// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1")
|
||||
#[tauri::command]
|
||||
pub async fn import_import_instance(
|
||||
profile_path: ProfilePathId,
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
instance_folder: String,
|
||||
) -> Result<()> {
|
||||
import::import_instance(
|
||||
profile_path,
|
||||
launcher_type,
|
||||
base_path,
|
||||
instance_folder,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if this instance is valid for importing, given a certain launcher type
|
||||
/// eg: is_valid_importable_instance(PathBuf::from("C:/MultiMC/Instance 1"), ImportLauncherType::MultiMC)
|
||||
#[tauri::command]
|
||||
pub async fn import_is_valid_importable_instance(
|
||||
instance_folder: PathBuf,
|
||||
launcher_type: ImportLauncherType,
|
||||
) -> Result<bool> {
|
||||
Ok(
|
||||
import::is_valid_importable_instance(instance_folder, launcher_type)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the default path for the given launcher type
|
||||
/// None if it can't be found or doesn't exist
|
||||
#[tauri::command]
|
||||
pub async fn import_get_default_launcher_path(
|
||||
launcher_type: ImportLauncherType,
|
||||
) -> Result<Option<PathBuf>> {
|
||||
Ok(import::get_default_launcher_path(launcher_type))
|
||||
}
|
||||
51
apps/theseus_gui/src-tauri/src/api/jre.rs
Normal file
51
apps/theseus_gui/src-tauri/src/api/jre.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::api::Result;
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use theseus::prelude::JavaVersion;
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("jre")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
jre_find_filtered_jres,
|
||||
jre_get_jre,
|
||||
jre_test_jre,
|
||||
jre_auto_install_java,
|
||||
jre_get_max_memory,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
// Finds the installation of Java 8, if it exists
|
||||
#[tauri::command]
|
||||
pub async fn jre_find_filtered_jres(
|
||||
version: Option<u32>,
|
||||
) -> Result<Vec<JavaVersion>> {
|
||||
Ok(jre::find_filtered_jres(version).await?)
|
||||
}
|
||||
|
||||
// Validates JRE at a given path
|
||||
// Returns None if the path is not a valid JRE
|
||||
#[tauri::command]
|
||||
pub async fn jre_get_jre(path: PathBuf) -> Result<Option<JavaVersion>> {
|
||||
jre::check_jre(path).await.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
// Tests JRE of a certain version
|
||||
#[tauri::command]
|
||||
pub async fn jre_test_jre(path: PathBuf, major_version: u32) -> Result<bool> {
|
||||
Ok(jre::test_jre(path, major_version).await?)
|
||||
}
|
||||
|
||||
// Auto installs java for the given java version
|
||||
#[tauri::command]
|
||||
pub async fn jre_auto_install_java(java_version: u32) -> Result<PathBuf> {
|
||||
Ok(jre::auto_install_java(java_version).await?)
|
||||
}
|
||||
|
||||
// Gets the maximum memory a system has available.
|
||||
#[tauri::command]
|
||||
pub async fn jre_get_max_memory() -> Result<u64> {
|
||||
Ok(jre::get_max_memory().await?)
|
||||
}
|
||||
96
apps/theseus_gui/src-tauri/src/api/logs.rs
Normal file
96
apps/theseus_gui/src-tauri/src/api/logs.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use crate::api::Result;
|
||||
use theseus::{
|
||||
logs::{self, CensoredString, LatestLogCursor, Logs},
|
||||
prelude::ProfilePathId,
|
||||
};
|
||||
use theseus::logs::LogType;
|
||||
|
||||
/*
|
||||
A log is a struct containing the filename string, stdout, and stderr, as follows:
|
||||
|
||||
pub struct Logs {
|
||||
pub filename: String,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("logs")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
logs_get_logs,
|
||||
logs_get_logs_by_filename,
|
||||
logs_get_output_by_filename,
|
||||
logs_delete_logs,
|
||||
logs_delete_logs_by_filename,
|
||||
logs_get_latest_log_cursor,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Get all Logs for a profile, sorted by filename
|
||||
#[tauri::command]
|
||||
pub async fn logs_get_logs(
|
||||
profile_path: ProfilePathId,
|
||||
clear_contents: Option<bool>,
|
||||
) -> Result<Vec<Logs>> {
|
||||
let val = logs::get_logs(profile_path, clear_contents).await?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Get a Log struct for a profile by profile id and filename string
|
||||
#[tauri::command]
|
||||
pub async fn logs_get_logs_by_filename(
|
||||
profile_path: ProfilePathId,
|
||||
log_type: LogType,
|
||||
filename: String,
|
||||
) -> Result<Logs> {
|
||||
Ok(logs::get_logs_by_filename(profile_path, log_type, filename).await?)
|
||||
}
|
||||
|
||||
/// Get the stdout for a profile by profile id and filename string
|
||||
#[tauri::command]
|
||||
pub async fn logs_get_output_by_filename(
|
||||
profile_path: ProfilePathId,
|
||||
log_type: LogType,
|
||||
filename: String,
|
||||
) -> Result<CensoredString> {
|
||||
let profile_path = if let Some(p) =
|
||||
crate::profile::get(&profile_path, None).await?
|
||||
{
|
||||
p.profile_id()
|
||||
} else {
|
||||
return Err(theseus::Error::from(
|
||||
theseus::ErrorKind::UnmanagedProfileError(profile_path.to_string()),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
Ok(logs::get_output_by_filename(&profile_path, log_type, &filename).await?)
|
||||
}
|
||||
|
||||
/// Delete all logs for a profile by profile id
|
||||
#[tauri::command]
|
||||
pub async fn logs_delete_logs(profile_path: ProfilePathId) -> Result<()> {
|
||||
Ok(logs::delete_logs(profile_path).await?)
|
||||
}
|
||||
|
||||
/// Delete a log for a profile by profile id and filename string
|
||||
#[tauri::command]
|
||||
pub async fn logs_delete_logs_by_filename(
|
||||
profile_path: ProfilePathId,
|
||||
log_type: LogType,
|
||||
filename: String,
|
||||
) -> Result<()> {
|
||||
Ok(logs::delete_logs_by_filename(profile_path, log_type, &filename).await?)
|
||||
}
|
||||
|
||||
/// Get live log from a cursor
|
||||
#[tauri::command]
|
||||
pub async fn logs_get_latest_log_cursor(
|
||||
profile_path: ProfilePathId,
|
||||
cursor: u64, // 0 to start at beginning of file
|
||||
) -> Result<LatestLogCursor> {
|
||||
Ok(logs::get_latest_log_cursor(profile_path, cursor).await?)
|
||||
}
|
||||
45
apps/theseus_gui/src-tauri/src/api/metadata.rs
Normal file
45
apps/theseus_gui/src-tauri/src/api/metadata.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::api::Result;
|
||||
use daedalus::minecraft::VersionManifest;
|
||||
use daedalus::modded::Manifest;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("metadata")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
metadata_get_game_versions,
|
||||
metadata_get_fabric_versions,
|
||||
metadata_get_forge_versions,
|
||||
metadata_get_quilt_versions,
|
||||
metadata_get_neoforge_versions,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Gets the game versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_game_versions() -> Result<VersionManifest> {
|
||||
Ok(theseus::metadata::get_minecraft_versions().await?)
|
||||
}
|
||||
|
||||
/// Gets the fabric versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_fabric_versions() -> Result<Manifest> {
|
||||
Ok(theseus::metadata::get_fabric_versions().await?)
|
||||
}
|
||||
|
||||
/// Gets the forge versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_forge_versions() -> Result<Manifest> {
|
||||
Ok(theseus::metadata::get_forge_versions().await?)
|
||||
}
|
||||
|
||||
/// Gets the quilt versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_quilt_versions() -> Result<Manifest> {
|
||||
Ok(theseus::metadata::get_quilt_versions().await?)
|
||||
}
|
||||
|
||||
/// Gets the quilt versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_neoforge_versions() -> Result<Manifest> {
|
||||
Ok(theseus::metadata::get_neoforge_versions().await?)
|
||||
}
|
||||
102
apps/theseus_gui/src-tauri/src/api/mod.rs
Normal file
102
apps/theseus_gui/src-tauri/src/api/mod.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod auth;
|
||||
pub mod import;
|
||||
pub mod jre;
|
||||
pub mod logs;
|
||||
pub mod metadata;
|
||||
pub mod mr_auth;
|
||||
pub mod pack;
|
||||
pub mod process;
|
||||
pub mod profile;
|
||||
pub mod profile_create;
|
||||
pub mod settings;
|
||||
pub mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||
|
||||
// // Main returnable Theseus GUI error
|
||||
// // Needs to be Serializable to be returned to the JavaScript side
|
||||
// #[derive(Error, Debug, Serialize)]
|
||||
// pub enum TheseusGuiError {
|
||||
// #[error(transparent)]
|
||||
// Serializable(),
|
||||
// }
|
||||
|
||||
// Serializable error intermediary, so TheseusGuiError can be Serializable (eg: so that we can return theseus::Errors in Tauri directly)
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TheseusSerializableError {
|
||||
#[error("{0}")]
|
||||
Theseus(#[from] theseus::Error),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("Tauri error: {0}")]
|
||||
Tauri(#[from] tauri::Error),
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error("Callback error: {0}")]
|
||||
Callback(String),
|
||||
}
|
||||
|
||||
// Generic implementation of From<T> for ErrorTypeA
|
||||
// impl<T> From<T> for TheseusGuiError
|
||||
// where
|
||||
// TheseusSerializableError: From<T>,
|
||||
// {
|
||||
// fn from(error: T) -> Self {
|
||||
// TheseusGuiError::Serializable(TheseusSerializableError::from(error))
|
||||
// }
|
||||
// }
|
||||
|
||||
// This is a very simple macro that implements a very basic Serializable for each variant of TheseusSerializableError,
|
||||
// where the field is the string. (This allows easy extension to errors without many match arms)
|
||||
macro_rules! impl_serialize {
|
||||
($($variant:ident),* $(,)?) => {
|
||||
impl Serialize for TheseusSerializableError {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
// For the Theseus variant, we add a special display for the error,
|
||||
// to view the spans if subscribed to them (which is information that is lost when serializing)
|
||||
TheseusSerializableError::Theseus(theseus_error) => {
|
||||
$crate::error::display_tracing_error(theseus_error);
|
||||
|
||||
let mut state = serializer.serialize_struct("Theseus", 2)?;
|
||||
state.serialize_field("field_name", "Theseus")?;
|
||||
state.serialize_field("message", &theseus_error.to_string())?;
|
||||
state.end()
|
||||
}
|
||||
$(
|
||||
TheseusSerializableError::$variant(message) => {
|
||||
let mut state = serializer.serialize_struct(stringify!($variant), 2)?;
|
||||
state.serialize_field("field_name", stringify!($variant))?;
|
||||
state.serialize_field("message", &message.to_string())?;
|
||||
state.end()
|
||||
},
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Use the macro to implement Serialize for TheseusSerializableError
|
||||
#[cfg(target_os = "macos")]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
Tauri,
|
||||
Callback
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
Tauri,
|
||||
}
|
||||
82
apps/theseus_gui/src-tauri/src/api/mr_auth.rs
Normal file
82
apps/theseus_gui/src-tauri/src/api/mr_auth.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use crate::api::Result;
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("mr_auth")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
authenticate_begin_flow,
|
||||
authenticate_await_completion,
|
||||
cancel_flow,
|
||||
login_pass,
|
||||
login_2fa,
|
||||
create_account,
|
||||
refresh,
|
||||
logout,
|
||||
get,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn authenticate_begin_flow(provider: &str) -> Result<String> {
|
||||
Ok(theseus::mr_auth::authenticate_begin_flow(provider).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn authenticate_await_completion() -> Result<ModrinthCredentialsResult>
|
||||
{
|
||||
Ok(theseus::mr_auth::authenticate_await_complete_flow().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn cancel_flow() -> Result<()> {
|
||||
Ok(theseus::mr_auth::cancel_flow().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn login_pass(
|
||||
username: &str,
|
||||
password: &str,
|
||||
challenge: &str,
|
||||
) -> Result<ModrinthCredentialsResult> {
|
||||
Ok(theseus::mr_auth::login_password(username, password, challenge).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn login_2fa(code: &str, flow: &str) -> Result<ModrinthCredentials> {
|
||||
Ok(theseus::mr_auth::login_2fa(code, flow).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_account(
|
||||
username: &str,
|
||||
email: &str,
|
||||
password: &str,
|
||||
challenge: &str,
|
||||
sign_up_newsletter: bool,
|
||||
) -> Result<ModrinthCredentials> {
|
||||
Ok(theseus::mr_auth::create_account(
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
challenge,
|
||||
sign_up_newsletter,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn refresh() -> Result<()> {
|
||||
Ok(theseus::mr_auth::refresh().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn logout() -> Result<()> {
|
||||
Ok(theseus::mr_auth::logout().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get() -> Result<Option<ModrinthCredentials>> {
|
||||
Ok(theseus::mr_auth::get_credentials().await?)
|
||||
}
|
||||
33
apps/theseus_gui/src-tauri/src/api/pack.rs
Normal file
33
apps/theseus_gui/src-tauri/src/api/pack.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::api::Result;
|
||||
|
||||
use theseus::{
|
||||
pack::{
|
||||
install_from::{CreatePackLocation, CreatePackProfile},
|
||||
install_mrpack::install_zipped_mrpack,
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("pack")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
pack_install,
|
||||
pack_get_profile_from_pack,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pack_install(
|
||||
location: CreatePackLocation,
|
||||
profile: ProfilePathId,
|
||||
) -> Result<ProfilePathId> {
|
||||
Ok(install_zipped_mrpack(location, profile).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn pack_get_profile_from_pack(
|
||||
location: CreatePackLocation,
|
||||
) -> Result<CreatePackProfile> {
|
||||
Ok(pack::install_from::get_profile_from_pack(location))
|
||||
}
|
||||
78
apps/theseus_gui/src-tauri/src/api/process.rs
Normal file
78
apps/theseus_gui/src-tauri/src/api/process.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use crate::api::Result;
|
||||
use theseus::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("process")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
process_has_finished_by_uuid,
|
||||
process_get_exit_status_by_uuid,
|
||||
process_get_all_uuids,
|
||||
process_get_all_running_uuids,
|
||||
process_get_uuids_by_profile_path,
|
||||
process_get_all_running_profile_paths,
|
||||
process_get_all_running_profiles,
|
||||
process_kill_by_uuid,
|
||||
process_wait_for_by_uuid,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
// Checks if a process has finished by process UUID
|
||||
#[tauri::command]
|
||||
pub async fn process_has_finished_by_uuid(uuid: Uuid) -> Result<bool> {
|
||||
Ok(process::has_finished_by_uuid(uuid).await?)
|
||||
}
|
||||
|
||||
// Gets process exit status by process UUID
|
||||
#[tauri::command]
|
||||
pub async fn process_get_exit_status_by_uuid(
|
||||
uuid: Uuid,
|
||||
) -> Result<Option<i32>> {
|
||||
Ok(process::get_exit_status_by_uuid(uuid).await?)
|
||||
}
|
||||
|
||||
// Gets all process UUIDs
|
||||
#[tauri::command]
|
||||
pub async fn process_get_all_uuids() -> Result<Vec<Uuid>> {
|
||||
Ok(process::get_all_uuids().await?)
|
||||
}
|
||||
|
||||
// Gets all running process UUIDs
|
||||
#[tauri::command]
|
||||
pub async fn process_get_all_running_uuids() -> Result<Vec<Uuid>> {
|
||||
Ok(process::get_all_running_uuids().await?)
|
||||
}
|
||||
|
||||
// Gets all process UUIDs by profile path
|
||||
#[tauri::command]
|
||||
pub async fn process_get_uuids_by_profile_path(
|
||||
profile_path: ProfilePathId,
|
||||
) -> Result<Vec<Uuid>> {
|
||||
Ok(process::get_uuids_by_profile_path(profile_path).await?)
|
||||
}
|
||||
|
||||
// Gets the Profile paths of each *running* stored process in the state
|
||||
#[tauri::command]
|
||||
pub async fn process_get_all_running_profile_paths(
|
||||
) -> Result<Vec<ProfilePathId>> {
|
||||
Ok(process::get_all_running_profile_paths().await?)
|
||||
}
|
||||
|
||||
// Gets the Profiles (cloned) of each *running* stored process in the state
|
||||
#[tauri::command]
|
||||
pub async fn process_get_all_running_profiles() -> Result<Vec<Profile>> {
|
||||
Ok(process::get_all_running_profiles().await?)
|
||||
}
|
||||
|
||||
// Kill a process by process UUID
|
||||
#[tauri::command]
|
||||
pub async fn process_kill_by_uuid(uuid: Uuid) -> Result<()> {
|
||||
Ok(process::kill_by_uuid(uuid).await?)
|
||||
}
|
||||
|
||||
// Wait for a process to finish by process UUID
|
||||
#[tauri::command]
|
||||
pub async fn process_wait_for_by_uuid(uuid: Uuid) -> Result<()> {
|
||||
Ok(process::wait_for_by_uuid(uuid).await?)
|
||||
}
|
||||
358
apps/theseus_gui/src-tauri/src/api/profile.rs
Normal file
358
apps/theseus_gui/src-tauri/src/api/profile.rs
Normal file
@@ -0,0 +1,358 @@
|
||||
use crate::api::Result;
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use theseus::{prelude::*, InnerProjectPathUnix};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("profile")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
profile_remove,
|
||||
profile_get,
|
||||
profile_get_optimal_jre_key,
|
||||
profile_get_full_path,
|
||||
profile_get_mod_full_path,
|
||||
profile_list,
|
||||
profile_check_installed,
|
||||
profile_install,
|
||||
profile_update_all,
|
||||
profile_update_project,
|
||||
profile_add_project_from_version,
|
||||
profile_add_project_from_path,
|
||||
profile_toggle_disable_project,
|
||||
profile_remove_project,
|
||||
profile_update_managed_modrinth_version,
|
||||
profile_repair_managed_modrinth,
|
||||
profile_run,
|
||||
profile_run_wait,
|
||||
profile_run_credentials,
|
||||
profile_run_wait_credentials,
|
||||
profile_edit,
|
||||
profile_edit_icon,
|
||||
profile_export_mrpack,
|
||||
profile_get_pack_export_candidates,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
// Remove a profile
|
||||
// invoke('plugin:profile|profile_add_path',path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_remove(path: ProfilePathId) -> Result<()> {
|
||||
profile::remove(&path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get a profile by path
|
||||
// invoke('plugin:profile|profile_add_path',path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_get(
|
||||
path: ProfilePathId,
|
||||
clear_projects: Option<bool>,
|
||||
) -> Result<Option<Profile>> {
|
||||
let res = profile::get(&path, clear_projects).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Get a profile's full path
|
||||
// invoke('plugin:profile|profile_get_full_path',path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_full_path(path: ProfilePathId) -> Result<PathBuf> {
|
||||
let res = profile::get_full_path(&path).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Get's a mod's full path
|
||||
// invoke('plugin:profile|profile_get_mod_full_path',path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_mod_full_path(
|
||||
path: ProfilePathId,
|
||||
project_path: ProjectPathId,
|
||||
) -> Result<PathBuf> {
|
||||
let res = profile::get_mod_full_path(&path, &project_path).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Get optimal java version from profile
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_optimal_jre_key(
|
||||
path: ProfilePathId,
|
||||
) -> Result<Option<JavaVersion>> {
|
||||
let res = profile::get_optimal_jre_key(&path).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Get a copy of the profile set
|
||||
// invoke('plugin:profile|profile_list')
|
||||
#[tauri::command]
|
||||
pub async fn profile_list(
|
||||
clear_projects: Option<bool>,
|
||||
) -> Result<HashMap<ProfilePathId, Profile>> {
|
||||
let res = profile::list(clear_projects).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn profile_check_installed(
|
||||
path: ProfilePathId,
|
||||
project_id: String,
|
||||
) -> Result<bool> {
|
||||
let profile = profile_get(path, None).await?;
|
||||
if let Some(profile) = profile {
|
||||
Ok(profile.projects.into_iter().any(|(_, project)| {
|
||||
if let ProjectMetadata::Modrinth { project, .. } = &project.metadata
|
||||
{
|
||||
project.id == project_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Installs/Repairs a profile
|
||||
/// invoke('plugin:profile|profile_install')
|
||||
#[tauri::command]
|
||||
pub async fn profile_install(path: ProfilePathId, force: bool) -> Result<()> {
|
||||
profile::install(&path, force).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates all of the profile's projects
|
||||
/// invoke('plugin:profile|profile_update_all')
|
||||
#[tauri::command]
|
||||
pub async fn profile_update_all(
|
||||
path: ProfilePathId,
|
||||
) -> Result<HashMap<ProjectPathId, ProjectPathId>> {
|
||||
Ok(profile::update_all_projects(&path).await?)
|
||||
}
|
||||
|
||||
/// Updates a specified project
|
||||
/// invoke('plugin:profile|profile_update_project')
|
||||
#[tauri::command]
|
||||
pub async fn profile_update_project(
|
||||
path: ProfilePathId,
|
||||
project_path: ProjectPathId,
|
||||
) -> Result<ProjectPathId> {
|
||||
Ok(profile::update_project(&path, &project_path, None).await?)
|
||||
}
|
||||
|
||||
// Adds a project to a profile from a version ID
|
||||
// invoke('plugin:profile|profile_add_project_from_version')
|
||||
#[tauri::command]
|
||||
pub async fn profile_add_project_from_version(
|
||||
path: ProfilePathId,
|
||||
version_id: String,
|
||||
) -> Result<ProjectPathId> {
|
||||
Ok(profile::add_project_from_version(&path, version_id).await?)
|
||||
}
|
||||
|
||||
// Adds a project to a profile from a path
|
||||
// invoke('plugin:profile|profile_add_project_from_path')
|
||||
#[tauri::command]
|
||||
pub async fn profile_add_project_from_path(
|
||||
path: ProfilePathId,
|
||||
project_path: &Path,
|
||||
project_type: Option<String>,
|
||||
) -> Result<ProjectPathId> {
|
||||
let res = profile::add_project_from_path(&path, project_path, project_type)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Toggles disabling a project from its path
|
||||
// invoke('plugin:profile|profile_toggle_disable_project')
|
||||
#[tauri::command]
|
||||
pub async fn profile_toggle_disable_project(
|
||||
path: ProfilePathId,
|
||||
project_path: ProjectPathId,
|
||||
) -> Result<ProjectPathId> {
|
||||
Ok(profile::toggle_disable_project(&path, &project_path).await?)
|
||||
}
|
||||
|
||||
// Removes a project from a profile
|
||||
// invoke('plugin:profile|profile_remove_project')
|
||||
#[tauri::command]
|
||||
pub async fn profile_remove_project(
|
||||
path: ProfilePathId,
|
||||
project_path: ProjectPathId,
|
||||
) -> Result<()> {
|
||||
profile::remove_project(&path, &project_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Updates a managed Modrinth profile to a version of version_id
|
||||
#[tauri::command]
|
||||
pub async fn profile_update_managed_modrinth_version(
|
||||
path: ProfilePathId,
|
||||
version_id: String,
|
||||
) -> Result<()> {
|
||||
Ok(
|
||||
profile::update::update_managed_modrinth_version(&path, &version_id)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
// Repairs a managed Modrinth profile by updating it to the current version
|
||||
#[tauri::command]
|
||||
pub async fn profile_repair_managed_modrinth(
|
||||
path: ProfilePathId,
|
||||
) -> Result<()> {
|
||||
Ok(profile::update::repair_managed_modrinth(&path).await?)
|
||||
}
|
||||
|
||||
// Exports a profile to a .mrpack file (export_location should end in .mrpack)
|
||||
// invoke('profile_export_mrpack')
|
||||
#[tauri::command]
|
||||
pub async fn profile_export_mrpack(
|
||||
path: ProfilePathId,
|
||||
export_location: PathBuf,
|
||||
included_overrides: Vec<String>,
|
||||
version_id: Option<String>,
|
||||
description: Option<String>,
|
||||
name: Option<String>, // only used to cache
|
||||
) -> Result<()> {
|
||||
profile::export_mrpack(
|
||||
&path,
|
||||
export_location,
|
||||
included_overrides,
|
||||
version_id,
|
||||
description,
|
||||
name,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See [`profile::get_pack_export_candidates`]
|
||||
#[tauri::command]
|
||||
pub async fn profile_get_pack_export_candidates(
|
||||
profile_path: ProfilePathId,
|
||||
) -> Result<Vec<InnerProjectPathUnix>> {
|
||||
let candidates = profile::get_pack_export_candidates(&profile_path).await?;
|
||||
Ok(candidates)
|
||||
}
|
||||
|
||||
// Run minecraft using a profile using the default credentials
|
||||
// Returns the UUID, which can be used to poll
|
||||
// for the actual Child in the state.
|
||||
// invoke('plugin:profile|profile_run', path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_run(path: ProfilePathId) -> Result<Uuid> {
|
||||
let minecraft_child = profile::run(&path).await?;
|
||||
let uuid = minecraft_child.read().await.uuid;
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
// Run Minecraft using a profile using the default credentials, and wait for the result
|
||||
// invoke('plugin:profile|profile_run_wait', path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_run_wait(path: ProfilePathId) -> Result<()> {
|
||||
let proc_lock = profile::run(&path).await?;
|
||||
let mut proc = proc_lock.write().await;
|
||||
Ok(process::wait_for(&mut proc).await?)
|
||||
}
|
||||
|
||||
// Run Minecraft using a profile using chosen credentials
|
||||
// Returns the UUID, which can be used to poll
|
||||
// for the actual Child in the state.
|
||||
// invoke('plugin:profile|profile_run_credentials', {path, credentials})')
|
||||
#[tauri::command]
|
||||
pub async fn profile_run_credentials(
|
||||
path: ProfilePathId,
|
||||
credentials: Credentials,
|
||||
) -> Result<Uuid> {
|
||||
let minecraft_child = profile::run_credentials(&path, &credentials).await?;
|
||||
let uuid = minecraft_child.read().await.uuid;
|
||||
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
// Run Minecraft using a profile using the chosen credentials, and wait for the result
|
||||
// invoke('plugin:profile|profile_run_wait', {path, credentials)
|
||||
#[tauri::command]
|
||||
pub async fn profile_run_wait_credentials(
|
||||
path: ProfilePathId,
|
||||
credentials: Credentials,
|
||||
) -> Result<()> {
|
||||
let proc_lock = profile::run_credentials(&path, &credentials).await?;
|
||||
let mut proc = proc_lock.write().await;
|
||||
Ok(process::wait_for(&mut proc).await?)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct EditProfile {
|
||||
pub metadata: Option<EditProfileMetadata>,
|
||||
pub java: Option<JavaSettings>,
|
||||
pub memory: Option<MemorySettings>,
|
||||
pub resolution: Option<WindowSize>,
|
||||
pub hooks: Option<Hooks>,
|
||||
pub fullscreen: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct EditProfileMetadata {
|
||||
pub name: Option<String>,
|
||||
pub game_version: Option<String>,
|
||||
pub loader: Option<ModLoader>,
|
||||
pub loader_version: Option<LoaderVersion>,
|
||||
pub linked_data: Option<LinkedData>,
|
||||
pub groups: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// Edits a profile
|
||||
// invoke('plugin:profile|profile_edit', {path, editProfile})
|
||||
#[tauri::command]
|
||||
pub async fn profile_edit(
|
||||
path: ProfilePathId,
|
||||
edit_profile: EditProfile,
|
||||
) -> Result<()> {
|
||||
profile::edit(&path, |prof| {
|
||||
if let Some(metadata) = edit_profile.metadata.clone() {
|
||||
if let Some(name) = metadata.name {
|
||||
prof.metadata.name = name;
|
||||
}
|
||||
if let Some(game_version) = metadata.game_version {
|
||||
prof.metadata.game_version = game_version;
|
||||
}
|
||||
if let Some(loader) = metadata.loader {
|
||||
prof.metadata.loader = loader;
|
||||
}
|
||||
prof.metadata.loader_version = metadata.loader_version;
|
||||
prof.metadata.linked_data = metadata.linked_data;
|
||||
|
||||
if let Some(groups) = metadata.groups {
|
||||
prof.metadata.groups = groups;
|
||||
}
|
||||
}
|
||||
|
||||
prof.java = edit_profile.java.clone();
|
||||
prof.memory = edit_profile.memory;
|
||||
prof.resolution = edit_profile.resolution;
|
||||
prof.fullscreen = edit_profile.fullscreen;
|
||||
prof.hooks = edit_profile.hooks.clone();
|
||||
|
||||
prof.metadata.date_modified = chrono::Utc::now();
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
State::sync().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Edits a profile's icon
|
||||
// invoke('plugin:profile|profile_edit_icon')
|
||||
#[tauri::command]
|
||||
pub async fn profile_edit_icon(
|
||||
path: ProfilePathId,
|
||||
icon_path: Option<&Path>,
|
||||
) -> Result<()> {
|
||||
profile::edit_icon(&path, icon_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
46
apps/theseus_gui/src-tauri/src/api/profile_create.rs
Normal file
46
apps/theseus_gui/src-tauri/src/api/profile_create.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::api::Result;
|
||||
use std::path::PathBuf;
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("profile_create")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
profile_create,
|
||||
profile_duplicate
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
// Creates a profile at the given filepath and adds it to the in-memory state
|
||||
// invoke('plugin:profile_create|profile_add',profile)
|
||||
#[tauri::command]
|
||||
pub async fn profile_create(
|
||||
name: String, // the name of the profile, and relative path
|
||||
game_version: String, // the game version of the profile
|
||||
modloader: ModLoader, // the modloader to use
|
||||
loader_version: Option<String>, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader
|
||||
icon: Option<PathBuf>, // the icon for the profile
|
||||
no_watch: Option<bool>,
|
||||
) -> Result<ProfilePathId> {
|
||||
let res = profile::create::profile_create(
|
||||
name,
|
||||
game_version,
|
||||
modloader,
|
||||
loader_version,
|
||||
icon,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
no_watch,
|
||||
)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Creates a profile from a duplicate
|
||||
// invoke('plugin:profile_create|profile_duplicate',profile)
|
||||
#[tauri::command]
|
||||
pub async fn profile_duplicate(path: ProfilePathId) -> Result<ProfilePathId> {
|
||||
let res = profile::create::profile_create_from_duplicate(path).await?;
|
||||
Ok(res)
|
||||
}
|
||||
48
apps/theseus_gui/src-tauri/src/api/settings.rs
Normal file
48
apps/theseus_gui/src-tauri/src/api/settings.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::api::Result;
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("settings")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
settings_get,
|
||||
settings_set,
|
||||
settings_change_config_dir,
|
||||
settings_is_dir_writeable
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
// Get full settings
|
||||
// invoke('plugin:settings|settings_get')
|
||||
#[tauri::command]
|
||||
pub async fn settings_get() -> Result<Settings> {
|
||||
let res = settings::get().await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Set full settings
|
||||
// invoke('plugin:settings|settings_set', settings)
|
||||
#[tauri::command]
|
||||
pub async fn settings_set(settings: Settings) -> Result<()> {
|
||||
settings::set(settings).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Change config directory
|
||||
// Seizes the entire State to do it
|
||||
// invoke('plugin:settings|settings_change_config_dir', new_dir)
|
||||
#[tauri::command]
|
||||
pub async fn settings_change_config_dir(new_config_dir: PathBuf) -> Result<()> {
|
||||
settings::set_config_dir(new_config_dir).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn settings_is_dir_writeable(
|
||||
new_config_dir: PathBuf,
|
||||
) -> Result<bool> {
|
||||
let res = settings::is_dir_writeable(new_config_dir).await?;
|
||||
Ok(res)
|
||||
}
|
||||
51
apps/theseus_gui/src-tauri/src/api/tags.rs
Normal file
51
apps/theseus_gui/src-tauri/src/api/tags.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use crate::api::Result;
|
||||
use theseus::tags::{Category, DonationPlatform, GameVersion, Loader, Tags};
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("tags")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
tags_get_categories,
|
||||
tags_get_report_types,
|
||||
tags_get_loaders,
|
||||
tags_get_game_versions,
|
||||
tags_get_donation_platforms,
|
||||
tags_get_tag_bundle,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Gets cached category tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_categories() -> Result<Vec<Category>> {
|
||||
Ok(theseus::tags::get_category_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached report type tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_report_types() -> Result<Vec<String>> {
|
||||
Ok(theseus::tags::get_report_type_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached loader tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_loaders() -> Result<Vec<Loader>> {
|
||||
Ok(theseus::tags::get_loader_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached game version tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_game_versions() -> Result<Vec<GameVersion>> {
|
||||
Ok(theseus::tags::get_game_version_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached donation platform tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_donation_platforms() -> Result<Vec<DonationPlatform>> {
|
||||
Ok(theseus::tags::get_donation_platform_tags().await?)
|
||||
}
|
||||
|
||||
/// Gets cached tag bundle from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_tag_bundle() -> Result<Tags> {
|
||||
Ok(theseus::tags::get_tag_bundle().await?)
|
||||
}
|
||||
191
apps/theseus_gui/src-tauri/src/api/utils.rs
Normal file
191
apps/theseus_gui/src-tauri/src/api/utils.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use theseus::{
|
||||
handler,
|
||||
prelude::{CommandPayload, DirectoryInfo},
|
||||
State,
|
||||
};
|
||||
|
||||
use crate::api::Result;
|
||||
use std::{env, path::PathBuf, process::Command};
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("utils")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_os,
|
||||
should_disable_mouseover,
|
||||
show_in_folder,
|
||||
show_launcher_logs_folder,
|
||||
progress_bars_list,
|
||||
safety_check_safe_loading_bars,
|
||||
get_opening_command,
|
||||
await_sync,
|
||||
is_offline,
|
||||
refresh_offline
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Gets OS
|
||||
#[tauri::command]
|
||||
pub fn get_os() -> OS {
|
||||
#[cfg(target_os = "windows")]
|
||||
let os = OS::Windows;
|
||||
#[cfg(target_os = "linux")]
|
||||
let os = OS::Linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
let os = OS::MacOS;
|
||||
os
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum OS {
|
||||
Windows,
|
||||
Linux,
|
||||
MacOS,
|
||||
}
|
||||
|
||||
// Lists active progress bars
|
||||
// Create a new HashMap with the same keys
|
||||
// Values provided should not be used directly, as they are not guaranteed to be up-to-date
|
||||
#[tauri::command]
|
||||
pub async fn progress_bars_list(
|
||||
) -> Result<std::collections::HashMap<uuid::Uuid, theseus::LoadingBar>> {
|
||||
let res = theseus::EventState::list_progress_bars().await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Check if there are any safe loading bars running
|
||||
#[tauri::command]
|
||||
pub async fn safety_check_safe_loading_bars() -> Result<bool> {
|
||||
Ok(theseus::safety::check_safe_loading_bars().await?)
|
||||
}
|
||||
|
||||
// cfg only on mac os
|
||||
// disables mouseover and fixes a random crash error only fixed by recent versions of macos
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tauri::command]
|
||||
pub async fn should_disable_mouseover() -> bool {
|
||||
// We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety
|
||||
let os = os_info::get();
|
||||
if let os_info::Version::Semantic(major, minor, _) = os.version() {
|
||||
if *major >= 12 && *minor >= 3 {
|
||||
// Mac os version is 12.3 or higher, we allow mouseover
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[tauri::command]
|
||||
pub async fn should_disable_mouseover() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn show_in_folder(path: PathBuf) -> Result<()> {
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if path.is_dir() {
|
||||
Command::new("explorer")
|
||||
.args([&path]) // The comma after select is not a typo
|
||||
.spawn()?;
|
||||
} else {
|
||||
Command::new("explorer")
|
||||
.args(["/select,", &path.to_string_lossy()]) // The comma after select is not a typo
|
||||
.spawn()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::fs::metadata;
|
||||
use std::path::PathBuf;
|
||||
|
||||
if path.to_string_lossy().to_string().contains(',') {
|
||||
// see https://gitlab.freedesktop.org/dbus/dbus/-/issues/76
|
||||
let new_path = match metadata(&path)?.is_dir() {
|
||||
true => path,
|
||||
false => {
|
||||
let mut path2 = PathBuf::from(path);
|
||||
path2.pop();
|
||||
path2
|
||||
}
|
||||
};
|
||||
Command::new("xdg-open").arg(&new_path).spawn()?;
|
||||
} else {
|
||||
Command::new("xdg-open").arg(&path).spawn()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if path.is_dir() {
|
||||
Command::new("open").args([&path]).spawn()?;
|
||||
} else {
|
||||
Command::new("open")
|
||||
.args(["-R", &path.as_os_str().to_string_lossy()])
|
||||
.spawn()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), theseus::Error>(())
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn show_launcher_logs_folder() -> Result<()> {
|
||||
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
|
||||
// failure to get folder just opens filesystem
|
||||
// (ie: if in debug mode only and launcher_logs never created)
|
||||
show_in_folder(path)
|
||||
}
|
||||
|
||||
// Get opening command
|
||||
// For example, if a user clicks on an .mrpack to open the app.
|
||||
// This should be called once and only when the app is done booting up and ready to receive a command
|
||||
// Returns a Command struct- see events.js
|
||||
#[tauri::command]
|
||||
pub async fn get_opening_command() -> Result<Option<CommandPayload>> {
|
||||
// Tauri is not CLI, we use arguments as path to file to call
|
||||
let cmd_arg = env::args_os().nth(1);
|
||||
|
||||
let cmd_arg = cmd_arg.map(|path| path.to_string_lossy().to_string());
|
||||
if let Some(cmd) = cmd_arg {
|
||||
tracing::debug!("Opening command: {:?}", cmd);
|
||||
return Ok(Some(handler::parse_command(&cmd).await?));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// helper function called when redirected by a weblink (ie: modrith://do-something) or when redirected by a .mrpack file (in which case its a filepath)
|
||||
// We hijack the deep link library (which also contains functionality for instance-checking)
|
||||
pub async fn handle_command(command: String) -> Result<()> {
|
||||
Ok(theseus::handler::parse_and_emit_command(&command).await?)
|
||||
}
|
||||
|
||||
// Waits for state to be synced
|
||||
#[tauri::command]
|
||||
pub async fn await_sync() -> Result<()> {
|
||||
State::sync().await?;
|
||||
tracing::debug!("State synced");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if theseus is currently in offline mode, without a refresh attempt
|
||||
#[tauri::command]
|
||||
pub async fn is_offline() -> Result<bool> {
|
||||
let state = State::get().await?;
|
||||
let offline = *state.offline.read().await;
|
||||
Ok(offline)
|
||||
}
|
||||
|
||||
/// Refreshes whether or not theseus is in offline mode, and returns the new value
|
||||
#[tauri::command]
|
||||
pub async fn refresh_offline() -> Result<bool> {
|
||||
let state = State::get().await?;
|
||||
state.refresh_offline().await?;
|
||||
let offline = *state.offline.read().await;
|
||||
Ok(offline)
|
||||
}
|
||||
18
apps/theseus_gui/src-tauri/src/error.rs
Normal file
18
apps/theseus_gui/src-tauri/src/error.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use tracing_error::ExtractSpanTrace;
|
||||
|
||||
pub fn display_tracing_error(err: &theseus::Error) {
|
||||
match get_span_trace(err) {
|
||||
Some(span_trace) => {
|
||||
tracing::error!(error = %err, span_trace = %span_trace);
|
||||
}
|
||||
None => {
|
||||
tracing::error!(error = %err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_span_trace<'a>(
|
||||
error: &'a (dyn std::error::Error + 'static),
|
||||
) -> Option<&'a tracing_error::SpanTrace> {
|
||||
error.source().and_then(|e| e.span_trace())
|
||||
}
|
||||
98
apps/theseus_gui/src-tauri/src/macos/delegate.rs
Normal file
98
apps/theseus_gui/src-tauri/src/macos/delegate.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::NSAutoreleasePool,
|
||||
};
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::api::TheseusSerializableError;
|
||||
|
||||
type Callback = OnceCell<Box<dyn Fn(String) + Send + Sync + 'static>>;
|
||||
|
||||
static CALLBACK: Callback = OnceCell::new();
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
unsafe impl Sync for AppDelegateClass {}
|
||||
|
||||
// Obj C class for the app delegate
|
||||
// This inherits from the TaoAppDelegate (used by tauri) so we do not accidentally override any functionality
|
||||
// The application_open_file method is the only method we override, as it is currently unimplemented in tauri
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref THESEUS_APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
|
||||
let superclass = class!(TaoAppDelegate);
|
||||
let mut decl = ClassDecl::new("TheseusAppDelegate", superclass).unwrap();
|
||||
|
||||
// Add the method to the class
|
||||
decl.add_method(
|
||||
sel!(application:openFile:),
|
||||
application_open_file as extern "C" fn(&Object, Sel, id, id) -> bool,
|
||||
);
|
||||
|
||||
// Other methods are inherited
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn application_open_file(
|
||||
_: &Object,
|
||||
_: Sel,
|
||||
_: id,
|
||||
file: id,
|
||||
) -> bool {
|
||||
let file = nsstring_to_string(file);
|
||||
callback(file)
|
||||
}
|
||||
|
||||
pub fn callback(file: String) -> bool {
|
||||
if let Some(callback) = CALLBACK.get() {
|
||||
callback(file);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_open_file<T>(
|
||||
callback: T,
|
||||
) -> Result<(), TheseusSerializableError>
|
||||
where
|
||||
T: Fn(String) + Send + Sync + 'static,
|
||||
{
|
||||
unsafe {
|
||||
// Modified from tao: https://github.com/tauri-apps/tao
|
||||
// sets the current app delegate to be the inherited app delegate rather than the default tauri/tao one
|
||||
let app: id = msg_send![class!(TaoApp), sharedApplication];
|
||||
|
||||
let delegate: id = msg_send![THESEUS_APP_DELEGATE_CLASS.0, new];
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
let _: () = msg_send![app, setDelegate: delegate];
|
||||
let _: () = msg_send![pool, drain];
|
||||
}
|
||||
CALLBACK.set(Box::new(callback)).map_err(|_| {
|
||||
TheseusSerializableError::Callback("Callback already set".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert an NSString to a Rust `String`
|
||||
/// From 'fruitbasket' https://github.com/mrmekon/fruitbasket/
|
||||
#[allow(clippy::cmp_null)]
|
||||
pub fn nsstring_to_string(nsstring: *mut Object) -> String {
|
||||
unsafe {
|
||||
let cstr: *const i8 = msg_send![nsstring, UTF8String];
|
||||
if cstr != std::ptr::null() {
|
||||
std::ffi::CStr::from_ptr(cstr)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
2
apps/theseus_gui/src-tauri/src/macos/mod.rs
Normal file
2
apps/theseus_gui/src-tauri/src/macos/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod delegate;
|
||||
pub mod window_ext;
|
||||
70
apps/theseus_gui/src-tauri/src/macos/window_ext.rs
Normal file
70
apps/theseus_gui/src-tauri/src/macos/window_ext.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
/// from: https://github.com/tauri-apps/tauri/issues/4789, full credit to haasal
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::{Runtime, Window};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub trait WindowExt {
|
||||
fn set_transparent_titlebar(&self, transparent: bool);
|
||||
fn position_traffic_lights(&self, x: f64, y: f64);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl<R: Runtime> WindowExt for Window<R> {
|
||||
fn set_transparent_titlebar(&self, transparent: bool) {
|
||||
use cocoa::appkit::{NSWindow, NSWindowTitleVisibility};
|
||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
||||
|
||||
unsafe {
|
||||
window.setTitleVisibility_(
|
||||
NSWindowTitleVisibility::NSWindowTitleHidden,
|
||||
);
|
||||
|
||||
if transparent {
|
||||
window.setTitlebarAppearsTransparent_(cocoa::base::YES);
|
||||
} else {
|
||||
window.setTitlebarAppearsTransparent_(cocoa::base::NO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn position_traffic_lights(&self, x: f64, y: f64) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
||||
|
||||
unsafe {
|
||||
let close = window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize = window.standardWindowButton_(
|
||||
NSWindowButton::NSWindowMiniaturizeButton,
|
||||
);
|
||||
let zoom = window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y =
|
||||
NSView::frame(window).size.height - title_bar_frame_height;
|
||||
let _: () =
|
||||
msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between = NSView::frame(miniaturize).origin.x
|
||||
- NSView::frame(close).origin.x;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
155
apps/theseus_gui/src-tauri/src/main.rs
Normal file
155
apps/theseus_gui/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use tauri::Manager;
|
||||
use theseus::prelude::*;
|
||||
|
||||
mod api;
|
||||
mod error;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
// Should be called in launcher initialization
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
theseus::EventState::init(app).await?;
|
||||
let s = State::get().await?;
|
||||
State::update();
|
||||
|
||||
s.children.write().await.rescue_cache().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_dev() -> bool {
|
||||
cfg!(debug_assertions)
|
||||
}
|
||||
|
||||
// Toggles decorations
|
||||
#[tauri::command]
|
||||
async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
|
||||
window.set_decorations(b).map_err(|e| {
|
||||
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
|
||||
"Failed to toggle decorations: {}",
|
||||
e
|
||||
)))
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct Payload {
|
||||
args: Vec<String>,
|
||||
cwd: String,
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
tauri_plugin_deep_link::prepare("com.modrinth.theseus");
|
||||
|
||||
/*
|
||||
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
|
||||
ERROR > WARN > INFO > DEBUG > TRACE
|
||||
eg. RUST_LOG=info will show info, warn, and error logs
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
|
||||
Error messages returned to Tauri will display as traced error logs if they return an error.
|
||||
This will also include an attached span trace if the error is from a tracing error, and the level is set to info, debug, or trace
|
||||
|
||||
on unix:
|
||||
RUST_LOG="theseus=trace" {run command}
|
||||
|
||||
*/
|
||||
let _log_guard = theseus::start_logger();
|
||||
|
||||
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
app.emit_all("single-instance", Payload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.setup(|app| {
|
||||
// Register deep link handler, allowing reading of modrinth:// links
|
||||
if let Err(e) = tauri_plugin_deep_link::register(
|
||||
"modrinth",
|
||||
|request: String| {
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
request,
|
||||
));
|
||||
},
|
||||
) {
|
||||
// Allow it to fail- see https://github.com/FabianLars/tauri-plugin-deep-link/issues/19
|
||||
tracing::error!("Error registering deep link handler: {}", e);
|
||||
}
|
||||
|
||||
let win = app.get_window("main").unwrap();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
use window_shadows::set_shadow;
|
||||
set_shadow(&win, true).unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use macos::window_ext::WindowExt;
|
||||
win.set_transparent_titlebar(true);
|
||||
win.position_traffic_lights(9.0, 16.0);
|
||||
|
||||
macos::delegate::register_open_file(|filename| {
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
filename,
|
||||
));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Show app now that we are setup
|
||||
win.show().unwrap();
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use tauri::WindowEvent;
|
||||
builder = builder.on_window_event(|e| {
|
||||
use macos::window_ext::WindowExt;
|
||||
if let WindowEvent::Resized(..) = e.event() {
|
||||
let win = e.window();
|
||||
win.position_traffic_lights(9.0, 16.0);
|
||||
}
|
||||
})
|
||||
}
|
||||
let builder = builder
|
||||
.plugin(api::auth::init())
|
||||
.plugin(api::mr_auth::init())
|
||||
.plugin(api::import::init())
|
||||
.plugin(api::logs::init())
|
||||
.plugin(api::jre::init())
|
||||
.plugin(api::metadata::init())
|
||||
.plugin(api::pack::init())
|
||||
.plugin(api::process::init())
|
||||
.plugin(api::profile::init())
|
||||
.plugin(api::profile_create::init())
|
||||
.plugin(api::settings::init())
|
||||
.plugin(api::tags::init())
|
||||
.plugin(api::utils::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
initialize_state,
|
||||
is_dev,
|
||||
toggle_decorations,
|
||||
api::auth::auth_login,
|
||||
]);
|
||||
|
||||
builder
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
Reference in New Issue
Block a user