You've already forked AstralRinth
forked from didirus/AstralRinth
Upgrading (#354)
* fixed no download bug * draft * Working version * minor improvements * cicd fix * merge conflicts * fixed major merge confusion * more conflicts, reformatting * fixed random bugs found * added second repair option to avoid confusion
This commit is contained in:
240
theseus/src/api/profile/create.rs
Normal file
240
theseus/src/api/profile/create.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::pack::install_from::CreatePackProfile;
|
||||
use crate::prelude::ProfilePathId;
|
||||
use crate::state::LinkedData;
|
||||
use crate::util::io::{self, canonicalize};
|
||||
use crate::{
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
prelude::ModLoader,
|
||||
};
|
||||
pub use crate::{
|
||||
state::{JavaSettings, Profile},
|
||||
State,
|
||||
};
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tracing::{info, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
// Creates a profile of a given name and adds it to the in-memory state
|
||||
// Returns relative filepath as ProfilePathId which can be used to access it in the State
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn profile_create(
|
||||
mut 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. defaults to latest
|
||||
icon: Option<PathBuf>, // the icon for the profile
|
||||
icon_url: Option<String>, // the URL icon for a profile (ONLY USED FOR TEMPORARY PROFILES)
|
||||
linked_data: Option<LinkedData>, // the linked project ID (mainly for modpacks)- used for updating
|
||||
skip_install_profile: Option<bool>,
|
||||
) -> crate::Result<ProfilePathId> {
|
||||
trace!("Creating new profile. {}", name);
|
||||
let state = State::get().await?;
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
let mut path = state.directories.profiles_dir().await.join(&name);
|
||||
if path.exists() {
|
||||
let mut new_name;
|
||||
let mut new_path;
|
||||
let mut which = 1;
|
||||
loop {
|
||||
new_name = format!("{name} ({which})");
|
||||
new_path = state.directories.profiles_dir().await.join(&new_name);
|
||||
if !new_path.exists() {
|
||||
break;
|
||||
}
|
||||
which += 1;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"Folder collision: {}, renaming to: {}",
|
||||
path.display(),
|
||||
new_path.display()
|
||||
);
|
||||
path = new_path;
|
||||
name = new_name;
|
||||
}
|
||||
io::create_dir_all(&path).await?;
|
||||
|
||||
info!(
|
||||
"Creating profile at path {}",
|
||||
&canonicalize(&path)?.display()
|
||||
);
|
||||
let loader = if modloader != ModLoader::Vanilla {
|
||||
get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
modloader,
|
||||
loader_version,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut profile = Profile::new(uuid, name, game_version).await?;
|
||||
let result = async {
|
||||
if let Some(ref icon) = icon {
|
||||
let bytes =
|
||||
io::read(state.directories.caches_dir().join(icon)).await?;
|
||||
profile
|
||||
.set_icon(
|
||||
&state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
bytes::Bytes::from(bytes),
|
||||
&icon.to_string_lossy(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
profile.metadata.icon_url = icon_url;
|
||||
if let Some(loader_version) = loader {
|
||||
profile.metadata.loader = modloader;
|
||||
profile.metadata.loader_version = Some(loader_version);
|
||||
}
|
||||
|
||||
profile.metadata.linked_data = linked_data;
|
||||
|
||||
emit_profile(
|
||||
uuid,
|
||||
profile.get_profile_full_path().await?,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Created,
|
||||
)
|
||||
.await?;
|
||||
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
profiles.insert(profile.clone()).await?;
|
||||
}
|
||||
|
||||
if !skip_install_profile.unwrap_or(false) {
|
||||
crate::launcher::install_minecraft(&profile, None).await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
|
||||
Ok(profile.profile_id())
|
||||
}
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile.profile_id()).await;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn profile_create_from_creator(
|
||||
profile: CreatePackProfile,
|
||||
) -> crate::Result<ProfilePathId> {
|
||||
profile_create(
|
||||
profile.name,
|
||||
profile.game_version,
|
||||
profile.modloader,
|
||||
profile.loader_version,
|
||||
profile.icon,
|
||||
profile.icon_url,
|
||||
profile.linked_data,
|
||||
profile.skip_install_profile,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub(crate) async fn get_loader_version_from_loader(
|
||||
game_version: String,
|
||||
loader: ModLoader,
|
||||
loader_version: Option<String>,
|
||||
) -> crate::Result<Option<LoaderVersion>> {
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
let version = loader_version.unwrap_or_else(|| "latest".to_string());
|
||||
|
||||
let filter = |it: &LoaderVersion| match version.as_str() {
|
||||
"latest" => true,
|
||||
"stable" => it.stable,
|
||||
id => {
|
||||
it.id == *id
|
||||
|| format!("{}-{}", game_version, id) == it.id
|
||||
|| format!("{}-{}-{}", game_version, id, game_version) == it.id
|
||||
}
|
||||
};
|
||||
|
||||
let loader_data = match loader {
|
||||
ModLoader::Forge => &metadata.forge,
|
||||
ModLoader::Fabric => &metadata.fabric,
|
||||
ModLoader::Quilt => &metadata.quilt,
|
||||
_ => {
|
||||
return Err(
|
||||
ProfileCreationError::NoManifest(loader.to_string()).into()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let loaders = &loader_data
|
||||
.game_versions
|
||||
.iter()
|
||||
.find(|it| {
|
||||
it.id
|
||||
.replace(daedalus::modded::DUMMY_REPLACE_STRING, &game_version)
|
||||
== game_version
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ProfileCreationError::ModloaderUnsupported(
|
||||
loader.to_string(),
|
||||
game_version.clone(),
|
||||
)
|
||||
})?
|
||||
.loaders;
|
||||
|
||||
let loader_version = loaders
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(filter)
|
||||
.or(
|
||||
// If stable was searched for but not found, return latest by default
|
||||
if version == "stable" {
|
||||
loaders.iter().next().cloned()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
ProfileCreationError::InvalidVersionModloader(
|
||||
version,
|
||||
loader.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Some(loader_version))
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ProfileCreationError {
|
||||
#[error("Profile .json exists: {0}")]
|
||||
ProfileExistsError(PathBuf),
|
||||
#[error("Modloader {0} unsupported for Minecraft version {1}")]
|
||||
ModloaderUnsupported(String, String),
|
||||
#[error("Invalid version {0} for modloader {1}")]
|
||||
InvalidVersionModloader(String, String),
|
||||
#[error("Could not get manifest for loader {0}. This is a bug in the GUI")]
|
||||
NoManifest(String),
|
||||
#[error("Could not get State.")]
|
||||
NoState,
|
||||
|
||||
#[error("Attempted to create project in something other than a folder.")]
|
||||
NotFolder,
|
||||
#[error("You are trying to create a profile in a non-empty directory")]
|
||||
NotEmptyFolder,
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
}
|
||||
1015
theseus/src/api/profile/mod.rs
Normal file
1015
theseus/src/api/profile/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
203
theseus/src/api/profile/update.rs
Normal file
203
theseus/src/api/profile/update.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use crate::{
|
||||
event::{
|
||||
emit::{emit_profile, loading_try_for_each_concurrent},
|
||||
ProfilePayloadType,
|
||||
},
|
||||
pack::{self, install_from::generate_pack_from_version_id},
|
||||
prelude::{ProfilePathId, ProjectPathId},
|
||||
profile::get,
|
||||
state::Project,
|
||||
State,
|
||||
};
|
||||
use futures::try_join;
|
||||
|
||||
/// Updates a managed modrinth pack to the cached latest version found in 'modrinth_update_version'
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn update_managed_modrinth(
|
||||
profile_path: &ProfilePathId,
|
||||
) -> crate::Result<()> {
|
||||
let profile = get(profile_path, None).await?.ok_or_else(|| {
|
||||
crate::ErrorKind::UnmanagedProfileError(profile_path.to_string())
|
||||
.as_error()
|
||||
})?;
|
||||
|
||||
let unmanaged_err = || {
|
||||
crate::ErrorKind::InputError(
|
||||
format!("Profile at {} is not a managed modrinth pack, or has been disconnected.", profile_path),
|
||||
)
|
||||
};
|
||||
|
||||
// Extract modrinth pack information, if appropriate
|
||||
let linked_data = profile
|
||||
.metadata
|
||||
.linked_data
|
||||
.as_ref()
|
||||
.ok_or_else(unmanaged_err)?;
|
||||
let project_id: &String =
|
||||
linked_data.project_id.as_ref().ok_or_else(unmanaged_err)?;
|
||||
let version_id =
|
||||
linked_data.version_id.as_ref().ok_or_else(unmanaged_err)?;
|
||||
|
||||
// extract modrinth_update_version, returning Ok(()) if it is none
|
||||
let modrinth_update_version = match profile.modrinth_update_version {
|
||||
Some(ref x) if x != version_id => x,
|
||||
_ => return Ok(()), // No update version, or no update needed, return Ok(())
|
||||
};
|
||||
|
||||
// Replace the pack with the new version
|
||||
replace_managed_modrinth(
|
||||
profile_path,
|
||||
&profile,
|
||||
project_id,
|
||||
version_id,
|
||||
Some(modrinth_update_version),
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
State::sync().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Repair a managed modrinth pack by 'updating' it to the current version
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn repair_managed_modrinth(
|
||||
profile_path: &ProfilePathId,
|
||||
) -> crate::Result<()> {
|
||||
let profile = get(profile_path, None).await?.ok_or_else(|| {
|
||||
crate::ErrorKind::UnmanagedProfileError(profile_path.to_string())
|
||||
.as_error()
|
||||
})?;
|
||||
|
||||
let unmanaged_err = || {
|
||||
crate::ErrorKind::InputError(
|
||||
format!("Profile at {} is not a managed modrinth pack, or has been disconnected.", profile_path),
|
||||
)
|
||||
};
|
||||
|
||||
// For repairing specifically, first we remove all installed projects (to ensure we do remove ones that aren't in the pack)
|
||||
// We do a project removal followed by removing everything in the .mrpack, to ensure we only
|
||||
// remove relevant projects and not things like save files
|
||||
let projects_map = profile.projects.clone();
|
||||
let stream = futures::stream::iter(
|
||||
projects_map
|
||||
.into_iter()
|
||||
.map(Ok::<(ProjectPathId, Project), crate::Error>),
|
||||
);
|
||||
loading_try_for_each_concurrent(
|
||||
stream,
|
||||
None,
|
||||
None,
|
||||
0.0,
|
||||
0,
|
||||
None,
|
||||
|(project_id, _)| {
|
||||
let profile = profile.clone();
|
||||
async move {
|
||||
profile.remove_project(&project_id, Some(true)).await?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Extract modrinth pack information, if appropriate
|
||||
let linked_data = profile
|
||||
.metadata
|
||||
.linked_data
|
||||
.as_ref()
|
||||
.ok_or_else(unmanaged_err)?;
|
||||
let project_id: &String =
|
||||
linked_data.project_id.as_ref().ok_or_else(unmanaged_err)?;
|
||||
let version_id =
|
||||
linked_data.version_id.as_ref().ok_or_else(unmanaged_err)?;
|
||||
|
||||
// Replace the pack with the same version
|
||||
replace_managed_modrinth(
|
||||
profile_path,
|
||||
&profile,
|
||||
project_id,
|
||||
version_id,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
State::sync().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replace a managed modrinth pack with a new version
|
||||
/// If new_version_id is None, the pack is 'reinstalled' in-place
|
||||
#[tracing::instrument(skip(profile))]
|
||||
#[theseus_macros::debug_pin]
|
||||
async fn replace_managed_modrinth(
|
||||
profile_path: &ProfilePathId,
|
||||
profile: &crate::state::Profile,
|
||||
project_id: &String,
|
||||
version_id: &String,
|
||||
new_version_id: Option<&String>,
|
||||
) -> crate::Result<()> {
|
||||
// Fetch .mrpacks for both old and new versions
|
||||
// TODO: this will need to be updated if we revert the hacky pack method we needed for compiler speed
|
||||
let old_pack_creator = generate_pack_from_version_id(
|
||||
project_id.clone(),
|
||||
version_id.clone(),
|
||||
profile.metadata.name.clone(),
|
||||
None,
|
||||
profile_path.clone(),
|
||||
);
|
||||
|
||||
// download in parallel, then join. If new_version_id is None, we don't need to download the new pack, so we clone the old one
|
||||
let (old_pack_creator, new_pack_creator) =
|
||||
if let Some(new_version_id) = new_version_id {
|
||||
try_join!(
|
||||
old_pack_creator,
|
||||
generate_pack_from_version_id(
|
||||
project_id.clone(),
|
||||
new_version_id.clone(),
|
||||
profile.metadata.name.clone(),
|
||||
None,
|
||||
profile_path.clone()
|
||||
)
|
||||
)?
|
||||
} else {
|
||||
let mut old_pack_creator = old_pack_creator.await?;
|
||||
old_pack_creator.description.existing_loading_bar = None;
|
||||
(old_pack_creator.clone(), old_pack_creator)
|
||||
};
|
||||
|
||||
// Removal - remove all files that were added by the old pack
|
||||
// - remove all installed projects
|
||||
// - remove all overrides
|
||||
pack::install_mrpack::remove_all_related_files(
|
||||
profile_path.clone(),
|
||||
old_pack_creator.file,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Reinstallation - install all files that are added by the new pack
|
||||
// - install all projects
|
||||
// - install all overrides
|
||||
// - edits the profile to update the new data
|
||||
// - (functionals almost identically to rteinstalling the pack 'in-place')
|
||||
pack::install_mrpack::install_zipped_mrpack_files(new_pack_creator).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user