aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src/main.rs')
-rw-r--r--src-tauri/src/main.rs255
1 files changed, 157 insertions, 98 deletions
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 0f1d7a1..33c94fe 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -6,7 +6,8 @@ use std::process::Stdio;
use std::sync::Mutex;
use tauri::{Emitter, Manager, State, Window}; // Added Emitter
use tokio::io::{AsyncBufReadExt, BufReader};
-use tokio::process::Command; // Added Serialize
+use tokio::process::Command;
+use ts_rs::TS; // Added Serialize
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
@@ -63,6 +64,7 @@ fn has_unresolved_placeholder(s: &str) -> bool {
}
#[tauri::command]
+#[dropout_macros::api]
async fn start_game(
window: Window,
auth_state: State<'_, core::auth::AccountState>,
@@ -198,92 +200,54 @@ async fn start_game(
None
};
- // Check if configured Java is compatible
+ // Resolve Java using priority-based resolution
+ // Priority: instance override > global config > user preference > auto-detect
+ // TODO: refactor into a separate function
let app_handle = window.app_handle();
- let mut java_path_to_use = config.java_path.clone();
- if !java_path_to_use.is_empty() && java_path_to_use != "java" {
- let is_compatible =
- core::java::is_java_compatible(&java_path_to_use, required_java_major, max_java_major);
-
- if !is_compatible {
- emit_log!(
- window,
- format!(
- "Configured Java version may not be compatible. Looking for compatible Java..."
- )
- );
-
- // Try to find a compatible Java version
- if let Some(compatible_java) =
- core::java::get_compatible_java(app_handle, required_java_major, max_java_major)
- {
- emit_log!(
- window,
- format!(
- "Found compatible Java {} at: {}",
- compatible_java.version, compatible_java.path
- )
- );
- java_path_to_use = compatible_java.path;
- } else {
- let version_constraint = if let Some(max) = max_java_major {
- if let Some(min) = required_java_major {
- if min == max as u64 {
- format!("Java {}", min)
- } else {
- format!("Java {} to {}", min, max)
- }
- } else {
- format!("Java {} (or lower)", max)
- }
- } else if let Some(min) = required_java_major {
- format!("Java {} or higher", min)
- } else {
- "any Java version".to_string()
- };
+ let instance = instance_state
+ .get_instance(&instance_id)
+ .ok_or_else(|| format!("Instance {} not found", instance_id))?;
- return Err(format!(
- "No compatible Java installation found. This version requires {}. Please install a compatible Java version in settings.",
- version_constraint
- ));
- }
- }
- } else {
- // No Java configured, try to find a compatible one
- if let Some(compatible_java) =
- core::java::get_compatible_java(app_handle, required_java_major, max_java_major)
- {
- emit_log!(
- window,
- format!(
- "Using Java {} at: {}",
- compatible_java.version, compatible_java.path
- )
- );
- java_path_to_use = compatible_java.path;
- } else {
- let version_constraint = if let Some(max) = max_java_major {
- if let Some(min) = required_java_major {
- if min == max as u64 {
- format!("Java {}", min)
- } else {
- format!("Java {} to {}", min, max)
- }
+ let java_installation = core::java::priority::resolve_java_for_launch(
+ app_handle,
+ instance.java_path_override.as_deref(),
+ Some(&config.java_path),
+ required_java_major,
+ max_java_major,
+ )
+ .await
+ .ok_or_else(|| {
+ let version_constraint = if let Some(max) = max_java_major {
+ if let Some(min) = required_java_major {
+ if min == max as u64 {
+ format!("Java {}", min)
} else {
- format!("Java {} (or lower)", max)
+ format!("Java {} to {}", min, max)
}
- } else if let Some(min) = required_java_major {
- format!("Java {} or higher", min)
} else {
- "any Java version".to_string()
- };
+ format!("Java {} (or lower)", max)
+ }
+ } else if let Some(min) = required_java_major {
+ format!("Java {} or higher", min)
+ } else {
+ "any Java version".to_string()
+ };
- return Err(format!(
- "No compatible Java installation found. This version requires {}. Please install a compatible Java version in settings.",
- version_constraint
- ));
- }
- }
+ format!(
+ "No compatible Java installation found. This version requires {}. Please install a compatible Java version in settings.",
+ version_constraint
+ )
+ })?;
+
+ emit_log!(
+ window,
+ format!(
+ "Using Java {} at: {}",
+ java_installation.version, java_installation.path
+ )
+ );
+
+ let java_path_to_use = java_installation.path;
// 2. Prepare download tasks
emit_log!(window, "Preparing download tasks...".to_string());
@@ -934,7 +898,18 @@ fn parse_jvm_arguments(
}
#[tauri::command]
-async fn get_versions(
+#[dropout_macros::api]
+async fn get_versions() -> Result<Vec<core::manifest::Version>, String> {
+ core::manifest::fetch_version_manifest()
+ .await
+ .map(|m| m.versions)
+ .map_err(|e| e.to_string())
+}
+
+/// Get all available versions from Mojang's version manifest
+#[tauri::command]
+#[dropout_macros::api]
+async fn get_versions_of_instance(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
instance_id: String,
@@ -978,6 +953,7 @@ async fn get_versions(
/// Check if a version is installed (has client.jar)
#[tauri::command]
+#[dropout_macros::api]
async fn check_version_installed(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1017,6 +993,7 @@ async fn check_version_installed(
/// Install a version (download client, libraries, assets) without launching
#[tauri::command]
+#[dropout_macros::api]
async fn install_version(
window: Window,
config_state: State<'_, core::config::ConfigState>,
@@ -1340,6 +1317,7 @@ async fn install_version(
}
#[tauri::command]
+#[dropout_macros::api]
async fn login_offline(
window: Window,
state: State<'_, core::auth::AccountState>,
@@ -1363,6 +1341,7 @@ async fn login_offline(
}
#[tauri::command]
+#[dropout_macros::api]
async fn get_active_account(
state: State<'_, core::auth::AccountState>,
) -> Result<Option<core::auth::Account>, String> {
@@ -1370,6 +1349,7 @@ async fn get_active_account(
}
#[tauri::command]
+#[dropout_macros::api]
async fn logout(window: Window, state: State<'_, core::auth::AccountState>) -> Result<(), String> {
// Get current account UUID before clearing
let uuid = state
@@ -1396,6 +1376,7 @@ async fn logout(window: Window, state: State<'_, core::auth::AccountState>) -> R
}
#[tauri::command]
+#[dropout_macros::api]
async fn get_settings(
state: State<'_, core::config::ConfigState>,
) -> Result<core::config::LauncherConfig, String> {
@@ -1403,6 +1384,7 @@ async fn get_settings(
}
#[tauri::command]
+#[dropout_macros::api]
async fn save_settings(
state: State<'_, core::config::ConfigState>,
config: core::config::LauncherConfig,
@@ -1413,11 +1395,13 @@ async fn save_settings(
}
#[tauri::command]
+#[dropout_macros::api]
async fn get_config_path(state: State<'_, core::config::ConfigState>) -> Result<String, String> {
Ok(state.file_path.to_string_lossy().to_string())
}
#[tauri::command]
+#[dropout_macros::api]
async fn read_raw_config(state: State<'_, core::config::ConfigState>) -> Result<String, String> {
tokio::fs::read_to_string(&state.file_path)
.await
@@ -1425,6 +1409,7 @@ async fn read_raw_config(state: State<'_, core::config::ConfigState>) -> Result<
}
#[tauri::command]
+#[dropout_macros::api]
async fn save_raw_config(
state: State<'_, core::config::ConfigState>,
content: String,
@@ -1445,11 +1430,13 @@ async fn save_raw_config(
}
#[tauri::command]
+#[dropout_macros::api]
async fn start_microsoft_login() -> Result<core::auth::DeviceCodeResponse, String> {
core::auth::start_device_flow().await
}
#[tauri::command]
+#[dropout_macros::api]
async fn complete_microsoft_login(
window: Window,
state: State<'_, core::auth::AccountState>,
@@ -1520,6 +1507,7 @@ async fn complete_microsoft_login(
/// Refresh token for current Microsoft account
#[tauri::command]
+#[dropout_macros::api]
async fn refresh_account(
window: Window,
state: State<'_, core::auth::AccountState>,
@@ -1555,22 +1543,34 @@ async fn refresh_account(
/// Detect Java installations on the system
#[tauri::command]
+#[dropout_macros::api]
+async fn detect_all_java_installations(
+ app_handle: tauri::AppHandle,
+) -> Result<Vec<core::java::JavaInstallation>, String> {
+ Ok(core::java::detect_all_java_installations(&app_handle).await)
+}
+
+/// Alias for detect_all_java_installations (for backward compatibility)
+#[tauri::command]
+#[dropout_macros::api]
async fn detect_java(
app_handle: tauri::AppHandle,
) -> Result<Vec<core::java::JavaInstallation>, String> {
- Ok(core::java::detect_all_java_installations(&app_handle))
+ Ok(core::java::detect_all_java_installations(&app_handle).await)
}
/// Get recommended Java for a specific Minecraft version
#[tauri::command]
+#[dropout_macros::api]
async fn get_recommended_java(
required_major_version: Option<u64>,
) -> Result<Option<core::java::JavaInstallation>, String> {
- Ok(core::java::get_recommended_java(required_major_version))
+ Ok(core::java::get_recommended_java(required_major_version).await)
}
/// Get Adoptium Java download info
#[tauri::command]
+#[dropout_macros::api]
async fn fetch_adoptium_java(
major_version: u32,
image_type: String,
@@ -1579,11 +1579,14 @@ async fn fetch_adoptium_java(
"jdk" => core::java::ImageType::Jdk,
_ => core::java::ImageType::Jre,
};
- core::java::fetch_java_release(major_version, img_type).await
+ core::java::fetch_java_release(major_version, img_type)
+ .await
+ .map_err(|e| e.to_string())
}
/// Download and install Adoptium Java
#[tauri::command]
+#[dropout_macros::api]
async fn download_adoptium_java(
app_handle: tauri::AppHandle,
major_version: u32,
@@ -1595,33 +1598,45 @@ async fn download_adoptium_java(
_ => core::java::ImageType::Jre,
};
let path = custom_path.map(std::path::PathBuf::from);
- core::java::download_and_install_java(&app_handle, major_version, img_type, path).await
+ core::java::download_and_install_java(&app_handle, major_version, img_type, path)
+ .await
+ .map_err(|e| e.to_string())
}
/// Get available Adoptium Java versions
#[tauri::command]
+#[dropout_macros::api]
async fn fetch_available_java_versions() -> Result<Vec<u32>, String> {
- core::java::fetch_available_versions().await
+ core::java::fetch_available_versions()
+ .await
+ .map_err(|e| e.to_string())
}
/// Fetch Java catalog with platform availability (uses cache)
#[tauri::command]
+#[dropout_macros::api]
async fn fetch_java_catalog(
app_handle: tauri::AppHandle,
) -> Result<core::java::JavaCatalog, String> {
- core::java::fetch_java_catalog(&app_handle, false).await
+ core::java::fetch_java_catalog(&app_handle, false)
+ .await
+ .map_err(|e| e.to_string())
}
/// Refresh Java catalog (bypass cache)
#[tauri::command]
+#[dropout_macros::api]
async fn refresh_java_catalog(
app_handle: tauri::AppHandle,
) -> Result<core::java::JavaCatalog, String> {
- core::java::fetch_java_catalog(&app_handle, true).await
+ core::java::fetch_java_catalog(&app_handle, true)
+ .await
+ .map_err(|e| e.to_string())
}
/// Cancel current Java download
#[tauri::command]
+#[dropout_macros::api]
async fn cancel_java_download() -> Result<(), String> {
core::java::cancel_current_download();
Ok(())
@@ -1629,6 +1644,7 @@ async fn cancel_java_download() -> Result<(), String> {
/// Get pending Java downloads
#[tauri::command]
+#[dropout_macros::api]
async fn get_pending_java_downloads(
app_handle: tauri::AppHandle,
) -> Result<Vec<core::downloader::PendingJavaDownload>, String> {
@@ -1637,6 +1653,7 @@ async fn get_pending_java_downloads(
/// Resume pending Java downloads
#[tauri::command]
+#[dropout_macros::api]
async fn resume_java_downloads(
app_handle: tauri::AppHandle,
) -> Result<Vec<core::java::JavaInstallation>, String> {
@@ -1645,6 +1662,7 @@ async fn resume_java_downloads(
/// Get Minecraft versions supported by Fabric
#[tauri::command]
+#[dropout_macros::api]
async fn get_fabric_game_versions() -> Result<Vec<core::fabric::FabricGameVersion>, String> {
core::fabric::fetch_supported_game_versions()
.await
@@ -1653,6 +1671,7 @@ async fn get_fabric_game_versions() -> Result<Vec<core::fabric::FabricGameVersio
/// Get available Fabric loader versions
#[tauri::command]
+#[dropout_macros::api]
async fn get_fabric_loader_versions() -> Result<Vec<core::fabric::FabricLoaderVersion>, String> {
core::fabric::fetch_loader_versions()
.await
@@ -1661,6 +1680,7 @@ async fn get_fabric_loader_versions() -> Result<Vec<core::fabric::FabricLoaderVe
/// Get Fabric loaders available for a specific Minecraft version
#[tauri::command]
+#[dropout_macros::api]
async fn get_fabric_loaders_for_version(
game_version: String,
) -> Result<Vec<core::fabric::FabricLoaderEntry>, String> {
@@ -1671,6 +1691,7 @@ async fn get_fabric_loaders_for_version(
/// Install Fabric loader for a specific Minecraft version
#[tauri::command]
+#[dropout_macros::api]
async fn install_fabric(
window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1715,6 +1736,7 @@ async fn install_fabric(
/// List installed Fabric versions
#[tauri::command]
+#[dropout_macros::api]
async fn list_installed_fabric_versions(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1731,6 +1753,7 @@ async fn list_installed_fabric_versions(
/// Get Java version requirement for a specific version
#[tauri::command]
+#[dropout_macros::api]
async fn get_version_java_version(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1749,17 +1772,18 @@ async fn get_version_java_version(
}
/// Version metadata for display in the UI
-#[derive(serde::Serialize)]
+#[derive(serde::Serialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(export, export_to = "core.ts")]
struct VersionMetadata {
id: String,
- #[serde(rename = "javaVersion")]
java_version: Option<u64>,
- #[serde(rename = "isInstalled")]
is_installed: bool,
}
/// Delete a version (remove version directory)
#[tauri::command]
+#[dropout_macros::api]
async fn delete_version(
window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1814,6 +1838,7 @@ async fn delete_version(
/// Get detailed metadata for a specific version
#[tauri::command]
+#[dropout_macros::api]
async fn get_version_metadata(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1899,7 +1924,9 @@ async fn get_version_metadata(
}
/// Installed version info
-#[derive(serde::Serialize)]
+#[derive(serde::Serialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(export, export_to = "core.ts")]
struct InstalledVersion {
id: String,
#[serde(rename = "type")]
@@ -1909,6 +1936,7 @@ struct InstalledVersion {
/// List all installed versions from the data directory
/// Simply lists all folders in the versions directory without validation
#[tauri::command]
+#[dropout_macros::api]
async fn list_installed_versions(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -1995,6 +2023,7 @@ async fn list_installed_versions(
/// Check if Fabric is installed for a specific version
#[tauri::command]
+#[dropout_macros::api]
async fn is_fabric_installed(
_window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -2015,6 +2044,7 @@ async fn is_fabric_installed(
/// Get Minecraft versions supported by Forge
#[tauri::command]
+#[dropout_macros::api]
async fn get_forge_game_versions() -> Result<Vec<String>, String> {
core::forge::fetch_supported_game_versions()
.await
@@ -2023,6 +2053,7 @@ async fn get_forge_game_versions() -> Result<Vec<String>, String> {
/// Get available Forge versions for a specific Minecraft version
#[tauri::command]
+#[dropout_macros::api]
async fn get_forge_versions_for_game(
game_version: String,
) -> Result<Vec<core::forge::ForgeVersion>, String> {
@@ -2033,6 +2064,7 @@ async fn get_forge_versions_for_game(
/// Install Forge for a specific Minecraft version
#[tauri::command]
+#[dropout_macros::api]
async fn install_forge(
window: Window,
config_state: State<'_, core::config::ConfigState>,
@@ -2060,7 +2092,7 @@ async fn install_forge(
config.java_path.clone()
} else {
// Try to find a suitable Java installation
- let javas = core::java::detect_all_java_installations(app_handle);
+ let javas = core::java::detect_all_java_installations(app_handle).await;
if let Some(java) = javas.first() {
java.path.clone()
} else {
@@ -2128,7 +2160,9 @@ async fn install_forge(
Ok(result)
}
-#[derive(serde::Serialize)]
+#[derive(serde::Serialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(export, export_to = "core.ts")]
struct GithubRelease {
tag_name: String,
name: String,
@@ -2138,6 +2172,7 @@ struct GithubRelease {
}
#[tauri::command]
+#[dropout_macros::api]
async fn get_github_releases() -> Result<Vec<GithubRelease>, String> {
let client = reqwest::Client::new();
let res = client
@@ -2174,12 +2209,14 @@ async fn get_github_releases() -> Result<Vec<GithubRelease>, String> {
Ok(result)
}
-#[derive(Serialize)]
+#[derive(Serialize, TS)]
+#[ts(export, export_to = "core.ts")]
struct PastebinResponse {
url: String,
}
#[tauri::command]
+#[dropout_macros::api]
async fn upload_to_pastebin(
state: State<'_, core::config::ConfigState>,
content: String,
@@ -2249,6 +2286,7 @@ async fn upload_to_pastebin(
}
#[tauri::command]
+#[dropout_macros::api]
async fn assistant_check_health(
assistant_state: State<'_, core::assistant::AssistantState>,
config_state: State<'_, core::config::ConfigState>,
@@ -2259,6 +2297,7 @@ async fn assistant_check_health(
}
#[tauri::command]
+#[dropout_macros::api]
async fn assistant_chat(
assistant_state: State<'_, core::assistant::AssistantState>,
config_state: State<'_, core::config::ConfigState>,
@@ -2270,6 +2309,7 @@ async fn assistant_chat(
}
#[tauri::command]
+#[dropout_macros::api]
async fn list_ollama_models(
assistant_state: State<'_, core::assistant::AssistantState>,
endpoint: String,
@@ -2279,6 +2319,7 @@ async fn list_ollama_models(
}
#[tauri::command]
+#[dropout_macros::api]
async fn list_openai_models(
assistant_state: State<'_, core::assistant::AssistantState>,
config_state: State<'_, core::config::ConfigState>,
@@ -2292,6 +2333,7 @@ async fn list_openai_models(
/// Create a new instance
#[tauri::command]
+#[dropout_macros::api]
async fn create_instance(
window: Window,
state: State<'_, core::instance::InstanceState>,
@@ -2303,6 +2345,7 @@ async fn create_instance(
/// Delete an instance
#[tauri::command]
+#[dropout_macros::api]
async fn delete_instance(
state: State<'_, core::instance::InstanceState>,
instance_id: String,
@@ -2312,6 +2355,7 @@ async fn delete_instance(
/// Update an instance
#[tauri::command]
+#[dropout_macros::api]
async fn update_instance(
state: State<'_, core::instance::InstanceState>,
instance: core::instance::Instance,
@@ -2321,6 +2365,7 @@ async fn update_instance(
/// Get all instances
#[tauri::command]
+#[dropout_macros::api]
async fn list_instances(
state: State<'_, core::instance::InstanceState>,
) -> Result<Vec<core::instance::Instance>, String> {
@@ -2329,6 +2374,7 @@ async fn list_instances(
/// Get a single instance by ID
#[tauri::command]
+#[dropout_macros::api]
async fn get_instance(
state: State<'_, core::instance::InstanceState>,
instance_id: String,
@@ -2340,6 +2386,7 @@ async fn get_instance(
/// Set the active instance
#[tauri::command]
+#[dropout_macros::api]
async fn set_active_instance(
state: State<'_, core::instance::InstanceState>,
instance_id: String,
@@ -2349,6 +2396,7 @@ async fn set_active_instance(
/// Get the active instance
#[tauri::command]
+#[dropout_macros::api]
async fn get_active_instance(
state: State<'_, core::instance::InstanceState>,
) -> Result<Option<core::instance::Instance>, String> {
@@ -2357,6 +2405,7 @@ async fn get_active_instance(
/// Duplicate an instance
#[tauri::command]
+#[dropout_macros::api]
async fn duplicate_instance(
window: Window,
state: State<'_, core::instance::InstanceState>,
@@ -2368,6 +2417,7 @@ async fn duplicate_instance(
}
#[tauri::command]
+#[dropout_macros::api]
async fn assistant_chat_stream(
window: tauri::Window,
assistant_state: State<'_, core::assistant::AssistantState>,
@@ -2382,7 +2432,9 @@ async fn assistant_chat_stream(
}
/// Migrate instance caches to shared global caches
-#[derive(Serialize)]
+#[derive(Serialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(export, export_to = "core.ts")]
struct MigrationResult {
moved_files: usize,
hardlinks: usize,
@@ -2392,6 +2444,7 @@ struct MigrationResult {
}
#[tauri::command]
+#[dropout_macros::api]
async fn migrate_shared_caches(
window: Window,
instance_state: State<'_, core::instance::InstanceState>,
@@ -2431,7 +2484,9 @@ async fn migrate_shared_caches(
}
/// File information for instance file browser
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(export, export_to = "core.ts")]
struct FileInfo {
name: String,
path: String,
@@ -2442,6 +2497,7 @@ struct FileInfo {
/// List files in an instance subdirectory (mods, resourcepacks, shaderpacks, saves, screenshots)
#[tauri::command]
+#[dropout_macros::api]
async fn list_instance_directory(
instance_state: State<'_, core::instance::InstanceState>,
instance_id: String,
@@ -2493,6 +2549,7 @@ async fn list_instance_directory(
/// Delete a file in an instance directory
#[tauri::command]
+#[dropout_macros::api]
async fn delete_instance_file(path: String) -> Result<(), String> {
let path_buf = std::path::PathBuf::from(&path);
if path_buf.is_dir() {
@@ -2509,6 +2566,7 @@ async fn delete_instance_file(path: String) -> Result<(), String> {
/// Open instance directory in system file explorer
#[tauri::command]
+#[dropout_macros::api]
async fn open_file_explorer(path: String) -> Result<(), String> {
#[cfg(target_os = "windows")]
{
@@ -2586,6 +2644,7 @@ fn main() {
.invoke_handler(tauri::generate_handler![
start_game,
get_versions,
+ get_versions_of_instance,
check_version_installed,
install_version,
list_installed_versions,