You've already forked AstralRinth
forked from didirus/AstralRinth
Direct World Joining (#3457)
* Begin work on worlds backend * Finish implementing get_profile_worlds and get_server_status (except pinning) * Create TS types and manually copy unparsed chat components * Clippy fix * Update types.d.ts * Initial worlds UI work * Fix api::get_profile_worlds to take in a relative path * sanitize & security update * Fix sanitizePotentialFileUrl * Fix sanitizePotentialFileUrl (for real) * Fix empty motd causing error * Finally actually fix world icons * Fix world icon not being visible on non-Windows * Use the correct generics to take in AppHandle * Implement start_join_singleplayer_world and start_join_server for modern versions * Don't error if server has no cached icon * Migrate to own server pinging * Ignore missing server hidden field and missing saves dir * Update world list frontend * More frontend work * Server status player sample can be absent * Fix refresh state * Add get_profile_protocol_version * Add protocol_version column to database * SQL INTEGER is i64 in sqlx * sqlx prepare * Cache protocol version in database * Continue worlds UI work * Fix motds being bold * Remove legacy pinging and add a 30-second timeout * Remove pinned for now and match world (and server) parsing closer to spec * Move type ServerStatus to worlds.ts * Implement add_server_to_profile * Fix pack_status being ignored when joining from launcher * Make World path field be relative * Implement rename_world and reset_world_icon * Clippy fix * Fix rename_world * UI enhancements * Implement backup_world, which returns the backup size in bytes * Clippy fix * Return index when adding servers to profile * Fix backup * Implement delete_world * Implement edit_server_in_profile and remove_server_from_profile * Clippy fix * Log server joins * Add edit and delete support * Fix ts errors * Fix minecraft font * Switch font out for non-monospaced. * Fix font proper * Some more world cleanup, handle play state, check quickplay compatibility * Clear the cached protocol version when a profile's game version is changed * Fix tint colors in navbar * Fix server protocol version pinging * UI fixes * Fix protocol version handler * Fix MOTD parsing * Add worlds_updated profile event * fix pkg * Functional home screen with worlds * lint * Fix incorrect folder creation * Make items clickable * Add locked field to SingleplayerWorld indicating whether the world is locked by the game * Implement locking frontend * Fix locking condition * Split worlds_updated profile event into servers_updated and world_updated * Fix compile error * Use port from resolve SRV record * Fix serialization of ProfilePayload and ProfilePayloadType * Individual singleplayer world refreshing * Log when worlds are perceived to be updated * Push logging + total refresh lock * Unlisten fixes * Highlight current world when clicked * Launcher logs refactor (#3444) * Switch live log to use STDOUT * fix clippy, legacy logs support * Fix lint * Handle non-XML log messages in XML logging, and don't escape log messages into XML --------- Co-authored-by: Josiah Glosson <soujournme@gmail.com> * Update incompatibility text * Home page fixes, and unlock after close * Remove logging * Add join log database migration * Switch server join timing to being in the database instead of in a separate log file * Create optimized get_recent_worlds function that takes in a limit * Update dependencies and fix Cargo.lock * temp disable overflow menus * revert home page changes * Enable overflow menus again * Remove list * Revert * Push dev tools * Remove default filter * Disable debug renderer * Fix random app errors * Refactor * Fix missing computed import * Fix light mode issues * Fix TS errors * Lint * Fix bad link in change modpack version modal * fix lint * fix intl --------- Co-authored-by: Josiah Glosson <soujournme@gmail.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,7 @@ pub mod utils;
|
||||
pub mod ads;
|
||||
pub mod cache;
|
||||
pub mod friends;
|
||||
pub mod worlds;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use theseus::prelude::*;
|
||||
use theseus::profile::QuickPlayType;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("profile")
|
||||
@@ -250,7 +251,7 @@ pub async fn profile_get_pack_export_candidates(
|
||||
// invoke('plugin:profile|profile_run', path)
|
||||
#[tauri::command]
|
||||
pub async fn profile_run(path: &str) -> Result<ProcessMetadata> {
|
||||
let process = profile::run(path).await?;
|
||||
let process = profile::run(path, &QuickPlayType::None).await?;
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
@@ -264,7 +265,9 @@ pub async fn profile_run_credentials(
|
||||
path: &str,
|
||||
credentials: Credentials,
|
||||
) -> Result<ProcessMetadata> {
|
||||
let process = profile::run_credentials(path, &credentials).await?;
|
||||
let process =
|
||||
profile::run_credentials(path, &credentials, &QuickPlayType::None)
|
||||
.await?;
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
@@ -347,6 +350,9 @@ pub async fn profile_edit(path: &str, edit_profile: EditProfile) -> Result<()> {
|
||||
prof.name = name;
|
||||
}
|
||||
if let Some(game_version) = edit_profile.game_version.clone() {
|
||||
if game_version != prof.game_version {
|
||||
prof.protocol_version = None;
|
||||
}
|
||||
prof.game_version = game_version;
|
||||
}
|
||||
if let Some(loader) = edit_profile.loader {
|
||||
|
||||
@@ -4,9 +4,11 @@ use theseus::{
|
||||
prelude::{CommandPayload, DirectoryInfo},
|
||||
};
|
||||
|
||||
use crate::api::Result;
|
||||
use crate::api::{Result, TheseusSerializableError};
|
||||
use dashmap::DashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use theseus::prelude::canonicalize;
|
||||
use url::Url;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("utils")
|
||||
@@ -140,3 +142,28 @@ pub async fn handle_command(command: String) -> Result<()> {
|
||||
tracing::info!("handle command: {command}");
|
||||
Ok(theseus::handler::parse_and_emit_command(&command).await?)
|
||||
}
|
||||
|
||||
// Remove when (and if) https://github.com/tauri-apps/tauri/issues/12022 is implemented
|
||||
pub(crate) fn tauri_convert_file_src(path: &Path) -> Result<Url> {
|
||||
#[cfg(any(windows, target_os = "android"))]
|
||||
const BASE: &str = "http://asset.localhost/";
|
||||
#[cfg(not(any(windows, target_os = "android")))]
|
||||
const BASE: &str = "asset://localhost/";
|
||||
|
||||
macro_rules! theseus_try {
|
||||
($test:expr) => {
|
||||
match $test {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
return Err(TheseusSerializableError::Theseus(e.into()))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let path = theseus_try!(canonicalize(path));
|
||||
let path = path.to_string_lossy();
|
||||
let encoded = urlencoding::encode(&path);
|
||||
|
||||
Ok(theseus_try!(Url::parse(&format!("{BASE}{encoded}"))))
|
||||
}
|
||||
|
||||
195
apps/app/src/api/worlds.rs
Normal file
195
apps/app/src/api/worlds.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use crate::api::Result;
|
||||
use either::Either;
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
use theseus::prelude::ProcessMetadata;
|
||||
use theseus::profile::{get_full_path, QuickPlayType};
|
||||
use theseus::worlds::{
|
||||
ServerPackStatus, ServerStatus, World, WorldWithProfile,
|
||||
};
|
||||
use theseus::{profile, worlds};
|
||||
|
||||
pub fn init<R: Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("worlds")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_recent_worlds,
|
||||
get_profile_worlds,
|
||||
get_singleplayer_world,
|
||||
rename_world,
|
||||
reset_world_icon,
|
||||
backup_world,
|
||||
delete_world,
|
||||
add_server_to_profile,
|
||||
edit_server_in_profile,
|
||||
remove_server_from_profile,
|
||||
get_profile_protocol_version,
|
||||
get_server_status,
|
||||
start_join_singleplayer_world,
|
||||
start_join_server,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_recent_worlds<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<WorldWithProfile>> {
|
||||
let mut result = worlds::get_recent_worlds(limit).await?;
|
||||
for world in result.iter_mut() {
|
||||
adapt_world_icon(&app_handle, &mut world.world);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_profile_worlds<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
path: &str,
|
||||
) -> Result<Vec<World>> {
|
||||
let mut result = worlds::get_profile_worlds(path).await?;
|
||||
for world in result.iter_mut() {
|
||||
adapt_world_icon(&app_handle, world);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_singleplayer_world<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
instance: &str,
|
||||
world: &str,
|
||||
) -> Result<World> {
|
||||
let instance = get_full_path(instance).await?;
|
||||
let mut world = worlds::get_singleplayer_world(&instance, world).await?;
|
||||
adapt_world_icon(&app_handle, &mut world);
|
||||
Ok(world)
|
||||
}
|
||||
|
||||
fn adapt_world_icon<R: Runtime>(app_handle: &AppHandle<R>, world: &mut World) {
|
||||
if let Some(Either::Left(icon_path)) = &world.icon {
|
||||
let icon_path = icon_path.clone();
|
||||
if let Ok(new_url) = super::utils::tauri_convert_file_src(&icon_path) {
|
||||
world.icon = Some(Either::Right(new_url));
|
||||
if let Err(e) =
|
||||
app_handle.asset_protocol_scope().allow_file(&icon_path)
|
||||
{
|
||||
tracing::warn!(
|
||||
"Failed to allow file access for icon {}: {}",
|
||||
icon_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Encountered invalid icon path for world {}: {}",
|
||||
world.name,
|
||||
icon_path.display()
|
||||
);
|
||||
world.icon = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn rename_world(
|
||||
instance: &str,
|
||||
world: &str,
|
||||
new_name: &str,
|
||||
) -> Result<()> {
|
||||
let instance = get_full_path(instance).await?;
|
||||
worlds::rename_world(&instance, world, new_name).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reset_world_icon(instance: &str, world: &str) -> Result<()> {
|
||||
let instance = get_full_path(instance).await?;
|
||||
worlds::reset_world_icon(&instance, world).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn backup_world(instance: &str, world: &str) -> Result<u64> {
|
||||
let instance = get_full_path(instance).await?;
|
||||
Ok(worlds::backup_world(&instance, world).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_world(instance: &str, world: &str) -> Result<()> {
|
||||
let instance = get_full_path(instance).await?;
|
||||
worlds::delete_world(&instance, world).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn add_server_to_profile(
|
||||
path: &str,
|
||||
name: String,
|
||||
address: String,
|
||||
pack_status: ServerPackStatus,
|
||||
) -> Result<usize> {
|
||||
let path = get_full_path(path).await?;
|
||||
Ok(
|
||||
worlds::add_server_to_profile(&path, name, address, pack_status)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn edit_server_in_profile(
|
||||
path: &str,
|
||||
index: usize,
|
||||
name: String,
|
||||
address: String,
|
||||
pack_status: ServerPackStatus,
|
||||
) -> Result<()> {
|
||||
let path = get_full_path(path).await?;
|
||||
worlds::edit_server_in_profile(&path, index, name, address, pack_status)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_server_from_profile(
|
||||
path: &str,
|
||||
index: usize,
|
||||
) -> Result<()> {
|
||||
let path = get_full_path(path).await?;
|
||||
worlds::remove_server_from_profile(&path, index).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_profile_protocol_version(path: &str) -> Result<Option<i32>> {
|
||||
Ok(worlds::get_profile_protocol_version(path).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_server_status(
|
||||
address: &str,
|
||||
protocol_version: Option<i32>,
|
||||
) -> Result<ServerStatus> {
|
||||
Ok(worlds::get_server_status(address, protocol_version).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_join_singleplayer_world(
|
||||
path: &str,
|
||||
world: String,
|
||||
) -> Result<ProcessMetadata> {
|
||||
let process =
|
||||
profile::run(path, &QuickPlayType::Singleplayer(world)).await?;
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_join_server(
|
||||
path: &str,
|
||||
address: &str,
|
||||
) -> Result<ProcessMetadata> {
|
||||
let process =
|
||||
profile::run(path, &QuickPlayType::Server(address.to_owned())).await?;
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
Reference in New Issue
Block a user