Monorepo missing features (#1273)

* fix tauri config

* fix package patch

* regen pnpm lock

* use new workflow

* New GH actions

* Update lockfile

* update scripts

* Fix build script

* Fix missing deps

* Fix assets eslint

* Update libraries lint

* Fix all lint configs

* update lockfile

* add fmt + clippy fails

* Separate App Tauri portion

* fix app features

* Fix lints

* install tauri cli

* update lockfile

* corepack, fix lints

* add store path

* fix unused import

* Fix tests

* Issue templates + port over tauri release

* fix actions

* fix before build command

* Add X86 target

* Update build matrix

* finalize actions

* make debug build smaller

* Use debug build to make cache smaller

* dummy commit

* change proj name

* update file name

* Use release builds for less space use

* Remove rust cache

* Readd for app build

* add merge queue trigger
This commit is contained in:
Geometrically
2024-07-09 15:17:38 -07:00
committed by GitHub
parent dab284f339
commit d1bc65c266
265 changed files with 1810 additions and 1871 deletions

106
apps/app/src/api/auth.rs Normal file
View 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?)
}

View 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/app/src/api/jre.rs Normal file
View 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?)
}

102
apps/app/src/api/logs.rs Normal file
View File

@@ -0,0 +1,102 @@
use crate::api::Result;
use theseus::logs::LogType;
use theseus::{
logs::{self, CensoredString, LatestLogCursor, Logs},
prelude::ProfilePathId,
};
/*
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?)
}

View 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/app/src/api/mod.rs Normal file
View 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,
}

View 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/app/src/api/pack.rs Normal file
View 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))
}

View 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/app/src/api/profile.rs Normal file
View 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.clone_from(&edit_profile.java);
prof.memory = edit_profile.memory;
prof.resolution = edit_profile.resolution;
prof.fullscreen = edit_profile.fullscreen;
prof.hooks.clone_from(&edit_profile.hooks);
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(())
}

View 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)
}

View 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/app/src/api/tags.rs Normal file
View 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/app/src/api/utils.rs Normal file
View 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)]
#[allow(clippy::enum_variant_names)]
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;
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 = path.clone();
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)
}