You've already forked AstralRinth
forked from didirus/AstralRinth
MR App 0.9.5 - Big bugfix update (#3585)
* Add launcher_feature_version to Profile * Misc fixes - Add typing to theme and settings stuff - Push instance route on creation from installing a modpack - Fixed servers not reloading properly when first added * Make old instances scan the logs folder for joined servers on launcher startup * Create AttachedWorldData * Change AttachedWorldData interface * Rename WorldType::World to WorldType::Singleplayer * Implement world display status system * Fix Minecraft font * Fix set_world_display_status Tauri error * Add 'Play instance' option * Add option to disable worlds showing in Home * Fixes - Fix available server filter only showing if there are some available - Fixed server and singleplayer filters sometimes showing when there are only servers or singleplayer worlds - Fixed new worlds not being automatically added when detected - Rephrased Jump back into worlds option description * Fixed sometimes more than 6 items showing up in Jump back in * Fix servers.dat issue with instances you haven't played before * Fix too large of bulk requests being made, limit max to 800 #3430 * Add hiding from home page, add types to Mods.vue * Make recent worlds go into grid when display is huge * Fix lint * Remove redundant media query * Fix protocol version on home page, and home page being blocked by pinging servers * Clippy fix * More Clippy fixes * Fix Prettier lints * Undo `from_string` changes --------- Co-authored-by: Josiah Glosson <soujournme@gmail.com> Co-authored-by: Alejandro González <me@alegon.dev>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
use crate::data::ModLoader;
|
||||
use crate::launcher::get_loader_version_from_profile;
|
||||
use crate::profile::get_full_path;
|
||||
use crate::state::{server_join_log, Profile, ProfileInstallStage};
|
||||
use crate::state::attached_world_data::AttachedWorldData;
|
||||
use crate::state::{
|
||||
attached_world_data, server_join_log, Profile, ProfileInstallStage,
|
||||
};
|
||||
pub use crate::util::server_ping::{
|
||||
ServerGameProfile, ServerPlayers, ServerStatus, ServerVersion,
|
||||
};
|
||||
@@ -11,6 +14,7 @@ use async_walkdir::WalkDir;
|
||||
use async_zip::{Compression, ZipEntryBuilder};
|
||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
||||
use either::Either;
|
||||
use enumset::{EnumSet, EnumSetType};
|
||||
use fs4::tokio::AsyncFileExt;
|
||||
use futures::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -42,10 +46,83 @@ pub struct World {
|
||||
with = "either::serde_untagged_optional"
|
||||
)]
|
||||
pub icon: Option<Either<PathBuf, Url>>,
|
||||
pub display_status: DisplayStatus,
|
||||
#[serde(flatten)]
|
||||
pub details: WorldDetails,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn world_type(&self) -> WorldType {
|
||||
match self.details {
|
||||
WorldDetails::Singleplayer { .. } => WorldType::Singleplayer,
|
||||
WorldDetails::Server { .. } => WorldType::Server,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn world_id(&self) -> &str {
|
||||
match &self.details {
|
||||
WorldDetails::Singleplayer { path, .. } => path,
|
||||
WorldDetails::Server { address, .. } => address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Default,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WorldType {
|
||||
#[default]
|
||||
Singleplayer,
|
||||
Server,
|
||||
}
|
||||
|
||||
impl WorldType {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Singleplayer => "singleplayer",
|
||||
Self::Server => "server",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(string: &str) -> Self {
|
||||
match string {
|
||||
"singleplayer" => Self::Singleplayer,
|
||||
"server" => Self::Server,
|
||||
_ => Self::Singleplayer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, EnumSetType, Debug, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[enumset(serialize_repr = "list")]
|
||||
pub enum DisplayStatus {
|
||||
#[default]
|
||||
Normal,
|
||||
Hidden,
|
||||
Favorite,
|
||||
}
|
||||
|
||||
impl DisplayStatus {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "normal",
|
||||
Self::Hidden => "hidden",
|
||||
Self::Favorite => "favorite",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(string: &str) -> Self {
|
||||
match string {
|
||||
"normal" => Self::Normal,
|
||||
"hidden" => Self::Hidden,
|
||||
"favorite" => Self::Favorite,
|
||||
_ => Self::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum WorldDetails {
|
||||
@@ -101,7 +178,10 @@ impl From<ServerPackStatus> for Option<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_recent_worlds(limit: usize) -> Result<Vec<WorldWithProfile>> {
|
||||
pub async fn get_recent_worlds(
|
||||
limit: usize,
|
||||
display_statuses: EnumSet<DisplayStatus>,
|
||||
) -> Result<Vec<WorldWithProfile>> {
|
||||
let state = State::get().await?;
|
||||
let profiles_dir = state.directories.profiles_dir();
|
||||
|
||||
@@ -133,6 +213,9 @@ pub async fn get_recent_worlds(limit: usize) -> Result<Vec<WorldWithProfile>> {
|
||||
if result.len() >= limit && is_older {
|
||||
continue;
|
||||
}
|
||||
if !display_statuses.contains(world.display_status) {
|
||||
continue;
|
||||
}
|
||||
if is_older {
|
||||
least_recent_time = world.last_played;
|
||||
}
|
||||
@@ -166,6 +249,21 @@ async fn get_all_worlds_in_profile(
|
||||
get_singleplayer_worlds_in_profile(profile_dir, &mut worlds).await?;
|
||||
get_server_worlds_in_profile(profile_path, profile_dir, &mut worlds)
|
||||
.await?;
|
||||
|
||||
let state = State::get().await?;
|
||||
let attached_data =
|
||||
AttachedWorldData::get_all_for_instance(profile_path, &state.pool)
|
||||
.await?;
|
||||
if !attached_data.is_empty() {
|
||||
for world in worlds.iter_mut() {
|
||||
if let Some(data) = attached_data
|
||||
.get(&(world.world_type(), world.world_id().to_owned()))
|
||||
{
|
||||
attach_world_data_to_world(world, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(worlds)
|
||||
}
|
||||
|
||||
@@ -193,10 +291,25 @@ async fn get_singleplayer_worlds_in_profile(
|
||||
}
|
||||
|
||||
pub async fn get_singleplayer_world(
|
||||
profile_path: &Path,
|
||||
instance: &str,
|
||||
world: &str,
|
||||
) -> Result<World> {
|
||||
read_singleplayer_world(get_world_dir(profile_path, world)).await
|
||||
let state = State::get().await?;
|
||||
let profile_path = state.directories.profiles_dir().join(instance);
|
||||
let mut world =
|
||||
read_singleplayer_world(get_world_dir(&profile_path, world)).await?;
|
||||
|
||||
if let Some(data) = AttachedWorldData::get_for_world(
|
||||
instance,
|
||||
world.world_type(),
|
||||
world.world_id(),
|
||||
&state.pool,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
attach_world_data_to_world(&mut world, &data);
|
||||
}
|
||||
Ok(world)
|
||||
}
|
||||
|
||||
async fn read_singleplayer_world(world_path: PathBuf) -> Result<World> {
|
||||
@@ -252,6 +365,7 @@ async fn read_singleplayer_world_maybe_locked(
|
||||
name: level_data.level_name,
|
||||
last_played: Utc.timestamp_millis_opt(level_data.last_played).single(),
|
||||
icon: icon.map(Either::Left),
|
||||
display_status: DisplayStatus::Normal,
|
||||
details: WorldDetails::Singleplayer {
|
||||
path: world_path
|
||||
.file_name()
|
||||
@@ -286,7 +400,7 @@ async fn get_server_worlds_in_profile(
|
||||
continue;
|
||||
}
|
||||
let icon = server.icon.and_then(|icon| {
|
||||
Url::parse(&format!("data:image/png;base64,{}", icon)).ok()
|
||||
Url::parse(&format!("data:image/png;base64,{icon}")).ok()
|
||||
});
|
||||
let last_played = join_log
|
||||
.as_ref()
|
||||
@@ -299,6 +413,7 @@ async fn get_server_worlds_in_profile(
|
||||
name: server.name,
|
||||
last_played,
|
||||
icon: icon.map(Either::Right),
|
||||
display_status: DisplayStatus::Normal,
|
||||
details: WorldDetails::Server {
|
||||
index,
|
||||
address: server.ip,
|
||||
@@ -311,6 +426,28 @@ async fn get_server_worlds_in_profile(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn attach_world_data_to_world(world: &mut World, data: &AttachedWorldData) {
|
||||
world.display_status = data.display_status;
|
||||
}
|
||||
|
||||
pub async fn set_world_display_status(
|
||||
instance: &str,
|
||||
world_type: WorldType,
|
||||
world_id: &str,
|
||||
display_status: DisplayStatus,
|
||||
) -> Result<()> {
|
||||
let state = State::get().await?;
|
||||
attached_world_data::set_display_status(
|
||||
instance,
|
||||
world_type,
|
||||
world_id,
|
||||
display_status,
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn rename_world(
|
||||
instance: &Path,
|
||||
world: &str,
|
||||
@@ -365,7 +502,7 @@ pub async fn backup_world(instance: &Path, world: &str) -> Result<u64> {
|
||||
let name_base = {
|
||||
let now = Local::now();
|
||||
let formatted_time = now.format("%Y-%m-%d_%H-%M-%S");
|
||||
format!("{}_{}", formatted_time, world)
|
||||
format!("{formatted_time}_{world}")
|
||||
};
|
||||
let output_path =
|
||||
backups_dir.join(find_available_name(&backups_dir, &name_base, ".zip"));
|
||||
@@ -671,8 +808,7 @@ pub async fn get_profile_protocol_version(
|
||||
) -> Result<Option<i32>> {
|
||||
let mut profile = super::profile::get(profile).await?.ok_or_else(|| {
|
||||
ErrorKind::UnmanagedProfileError(format!(
|
||||
"Could not find profile {}",
|
||||
profile
|
||||
"Could not find profile {profile}"
|
||||
))
|
||||
})?;
|
||||
if profile.install_stage != ProfileInstallStage::Installed {
|
||||
@@ -809,22 +945,21 @@ async fn resolve_server_address(
|
||||
return Ok((host.to_owned(), port));
|
||||
}
|
||||
let resolver = hickory_resolver::TokioResolver::builder_tokio()?.build();
|
||||
Ok(match resolver
|
||||
.srv_lookup(format!("_minecraft._tcp.{}", host))
|
||||
.await
|
||||
{
|
||||
Err(e)
|
||||
if e.proto()
|
||||
.filter(|x| x.kind().is_no_records_found())
|
||||
.is_some() =>
|
||||
{
|
||||
None
|
||||
Ok(
|
||||
match resolver.srv_lookup(format!("_minecraft._tcp.{host}")).await {
|
||||
Err(e)
|
||||
if e.proto()
|
||||
.filter(|x| x.kind().is_no_records_found())
|
||||
.is_some() =>
|
||||
{
|
||||
None
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(lookup) => lookup
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|r| (r.target().to_string(), r.port())),
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(lookup) => lookup
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|r| (r.target().to_string(), r.port())),
|
||||
}
|
||||
.unwrap_or_else(|| (host.to_owned(), port)))
|
||||
.unwrap_or_else(|| (host.to_owned(), port)),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user