You've already forked AstralRinth
forked from didirus/AstralRinth
Beta bugs (#562)
* fixed bugs * added logging for atlauncher * draft: improving imports time * more improvements * more * prettier, etc * small changes * emma suggested change * rev * removed atlauncher debug
This commit is contained in:
@@ -7,10 +7,13 @@ use crate::ErrorKind;
|
||||
pub async fn authenticate_begin_flow(provider: &str) -> crate::Result<String> {
|
||||
let state = crate::State::get().await?;
|
||||
|
||||
// Don't start an uncompleteable new flow if there's an existing locked one
|
||||
let mut write: tokio::sync::RwLockWriteGuard<'_, Option<ModrinthAuthFlow>> =
|
||||
state.modrinth_auth_flow.write().await;
|
||||
|
||||
let mut flow = ModrinthAuthFlow::new(provider).await?;
|
||||
let url = flow.prepare_login_url().await?;
|
||||
|
||||
let mut write = state.modrinth_auth_flow.write().await;
|
||||
*write = Some(flow);
|
||||
|
||||
Ok(url)
|
||||
|
||||
@@ -3,13 +3,12 @@ use std::{collections::HashMap, path::PathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
event::LoadingBarId,
|
||||
pack::{
|
||||
self,
|
||||
import::{self, copy_dotminecraft},
|
||||
install_from::CreatePackDescription,
|
||||
},
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
prelude::{ModLoader, Profile, ProfilePathId},
|
||||
state::{LinkedData, ProfileInstallStage},
|
||||
util::io,
|
||||
State,
|
||||
@@ -33,8 +32,6 @@ pub struct ATLauncher {
|
||||
pub modrinth_project: Option<ATLauncherModrinthProject>,
|
||||
pub modrinth_version: Option<ATLauncherModrinthVersion>,
|
||||
pub modrinth_manifest: Option<pack::install_from::PackFormat>,
|
||||
|
||||
pub mods: Vec<ATLauncherMod>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -57,13 +54,9 @@ pub struct ATLauncherModrinthProject {
|
||||
pub slug: String,
|
||||
pub project_type: String,
|
||||
pub team: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub body: String,
|
||||
pub client_side: Option<String>,
|
||||
pub server_side: Option<String>,
|
||||
pub categories: Vec<String>,
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -110,7 +103,16 @@ pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
|
||||
.unwrap_or("".to_string());
|
||||
let instance: Result<ATInstance, serde_json::Error> =
|
||||
serde_json::from_str::<ATInstance>(&instance);
|
||||
instance.is_ok()
|
||||
if let Err(e) = instance {
|
||||
tracing::warn!(
|
||||
"Could not parse instance.json at {}: {}",
|
||||
instance_folder.display(),
|
||||
e
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -169,7 +171,6 @@ pub async fn import_atlauncher(
|
||||
backup_name,
|
||||
description,
|
||||
atinstance,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -181,7 +182,6 @@ async fn import_atlauncher_unmanaged(
|
||||
backup_name: String,
|
||||
description: CreatePackDescription,
|
||||
atinstance: ATInstance,
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
let mod_loader = format!(
|
||||
"\"{}\"",
|
||||
@@ -230,19 +230,28 @@ async fn import_atlauncher_unmanaged(
|
||||
|
||||
// Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc)
|
||||
let state = State::get().await?;
|
||||
copy_dotminecraft(
|
||||
let loading_bar = copy_dotminecraft(
|
||||
profile_path.clone(),
|
||||
minecraft_folder,
|
||||
&state.io_semaphore,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, existing_loading_bar)
|
||||
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
|
||||
.await?;
|
||||
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(
|
||||
&profile_val.get_profile_full_path().await?,
|
||||
&mut file_watcher,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::prelude::Profile;
|
||||
use crate::state::CredentialsStore;
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
@@ -187,18 +188,29 @@ pub async fn import_curseforge(
|
||||
|
||||
// Copy in contained folders as overrides
|
||||
let state = State::get().await?;
|
||||
copy_dotminecraft(
|
||||
let loading_bar = copy_dotminecraft(
|
||||
profile_path.clone(),
|
||||
curseforge_instance_folder,
|
||||
&state.io_semaphore,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, None).await?;
|
||||
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
|
||||
.await?;
|
||||
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(
|
||||
&profile_val.get_profile_full_path().await?,
|
||||
&mut file_watcher,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
prelude::{ModLoader, Profile, ProfilePathId},
|
||||
state::ProfileInstallStage,
|
||||
util::io,
|
||||
State,
|
||||
@@ -101,18 +101,28 @@ pub async fn import_gdlauncher(
|
||||
|
||||
// Copy in contained folders as overrides
|
||||
let state = State::get().await?;
|
||||
copy_dotminecraft(
|
||||
let loading_bar = copy_dotminecraft(
|
||||
profile_path.clone(),
|
||||
gdlauncher_instance_folder,
|
||||
&state.io_semaphore,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, None).await?;
|
||||
|
||||
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
|
||||
.await?;
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(
|
||||
&profile_val.get_profile_full_path().await?,
|
||||
&mut file_watcher,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
import::{self, copy_dotminecraft},
|
||||
install_from::{self, CreatePackDescription, PackDependency},
|
||||
},
|
||||
prelude::ProfilePathId,
|
||||
prelude::{Profile, ProfilePathId},
|
||||
util::io,
|
||||
State,
|
||||
};
|
||||
@@ -119,6 +119,26 @@ pub struct MMCComponentRequirement {
|
||||
pub suggests: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
#[serde(untagged)]
|
||||
enum MMCLauncherEnum {
|
||||
General(MMCLauncherGeneral),
|
||||
Instance(MMCLauncher),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct MMCLauncherGeneral {
|
||||
pub general: MMCLauncher,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct MMCLauncher {
|
||||
instance_dir: String,
|
||||
}
|
||||
|
||||
// Checks if if its a folder, and the folder contains instance.cfg and mmc-pack.json, and they both parse
|
||||
#[tracing::instrument]
|
||||
pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool {
|
||||
@@ -134,9 +154,19 @@ pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool {
|
||||
&& serde_json::from_str::<MMCPack>(&mmc_pack).is_ok()
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_instances_subpath(config: PathBuf) -> Option<String> {
|
||||
let launcher = io::read_to_string(&config).await.ok()?;
|
||||
let launcher: MMCLauncherEnum = serde_ini::from_str(&launcher).ok()?;
|
||||
match launcher {
|
||||
MMCLauncherEnum::General(p) => Some(p.general.instance_dir),
|
||||
MMCLauncherEnum::Instance(p) => Some(p.instance_dir),
|
||||
}
|
||||
}
|
||||
|
||||
// Loading the INI (instance.cfg) file
|
||||
async fn load_instance_cfg(file_path: &Path) -> crate::Result<MMCInstance> {
|
||||
let instance_cfg = io::read_to_string(file_path).await?;
|
||||
let instance_cfg: String = io::read_to_string(file_path).await?;
|
||||
let instance_cfg_enum: MMCInstanceEnum =
|
||||
serde_ini::from_str::<MMCInstanceEnum>(&instance_cfg)?;
|
||||
match instance_cfg_enum {
|
||||
@@ -281,18 +311,28 @@ async fn import_mmc_unmanaged(
|
||||
|
||||
// Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc)
|
||||
let state = State::get().await?;
|
||||
copy_dotminecraft(
|
||||
let loading_bar = copy_dotminecraft(
|
||||
profile_path.clone(),
|
||||
minecraft_folder,
|
||||
&state.io_semaphore,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, None).await?;
|
||||
|
||||
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
|
||||
.await?;
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(
|
||||
&profile_val.get_profile_full_path().await?,
|
||||
&mut file_watcher,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -7,6 +7,10 @@ use io::IOError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
event::{
|
||||
emit::{emit_loading, init_or_edit_loading},
|
||||
LoadingBarId,
|
||||
},
|
||||
prelude::ProfilePathId,
|
||||
state::Profiles,
|
||||
util::{
|
||||
@@ -51,11 +55,20 @@ pub async fn get_importable_instances(
|
||||
) -> crate::Result<Vec<String>> {
|
||||
// Some launchers have a different folder structure for instances
|
||||
let instances_subfolder = match launcher_type {
|
||||
ImportLauncherType::GDLauncher
|
||||
| ImportLauncherType::MultiMC
|
||||
| ImportLauncherType::PrismLauncher
|
||||
| ImportLauncherType::ATLauncher => "instances",
|
||||
ImportLauncherType::Curseforge => "Instances",
|
||||
ImportLauncherType::GDLauncher | ImportLauncherType::ATLauncher => {
|
||||
"instances".to_string()
|
||||
}
|
||||
ImportLauncherType::Curseforge => "Instances".to_string(),
|
||||
ImportLauncherType::MultiMC => {
|
||||
mmc::get_instances_subpath(base_path.clone().join("multimc.cfg"))
|
||||
.await
|
||||
.unwrap_or_else(|| "instances".to_string())
|
||||
}
|
||||
ImportLauncherType::PrismLauncher => mmc::get_instances_subpath(
|
||||
base_path.clone().join("prismlauncher.cfg"),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|| "instances".to_string()),
|
||||
ImportLauncherType::Unknown => {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Launcher type Unknown".to_string(),
|
||||
@@ -63,7 +76,8 @@ pub async fn get_importable_instances(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let instances_folder = base_path.join(instances_subfolder);
|
||||
|
||||
let instances_folder = base_path.join(&instances_subfolder);
|
||||
let mut instances = Vec::new();
|
||||
let mut dir = io::read_dir(&instances_folder).await.map_err(| _ | {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
@@ -238,55 +252,61 @@ pub async fn recache_icon(
|
||||
}
|
||||
|
||||
async fn copy_dotminecraft(
|
||||
profile_path: ProfilePathId,
|
||||
profile_path_id: ProfilePathId,
|
||||
dotminecraft: PathBuf,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<()> {
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
// Get full path to profile
|
||||
let profile_path = profile_path.get_full_path().await?;
|
||||
let profile_path = profile_path_id.get_full_path().await?;
|
||||
|
||||
// std fs copy every file in dotminecraft to profile_path
|
||||
let mut dir = io::read_dir(&dotminecraft).await?;
|
||||
while let Some(entry) = dir
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &dotminecraft))?
|
||||
{
|
||||
let path = entry.path();
|
||||
copy_dir_to(
|
||||
&path,
|
||||
&profile_path.join(path.file_name().ok_or_else(|| {
|
||||
// Gets all subfiles recursively in src
|
||||
let subfiles = get_all_subfiles(&dotminecraft).await?;
|
||||
let total_subfiles = subfiles.len() as u64;
|
||||
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
crate::LoadingBarType::CopyProfile {
|
||||
import_location: dotminecraft.clone(),
|
||||
profile_name: profile_path_id.to_string(),
|
||||
},
|
||||
total_subfiles as f64,
|
||||
"Copying files in profile",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Copy each file
|
||||
for src_child in subfiles {
|
||||
let dst_child =
|
||||
src_child.strip_prefix(&dotminecraft).map_err(|_| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Invalid file: {}",
|
||||
&path.display()
|
||||
&src_child.display()
|
||||
))
|
||||
})?),
|
||||
io_semaphore,
|
||||
)
|
||||
.await?;
|
||||
})?;
|
||||
let dst_child = profile_path.join(dst_child);
|
||||
|
||||
// sleep for cpu for 1 millisecond
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||
|
||||
fetch::copy(&src_child, &dst_child, io_semaphore).await?;
|
||||
|
||||
emit_loading(&loading_bar, 1.0, None).await?;
|
||||
}
|
||||
Ok(())
|
||||
Ok(loading_bar)
|
||||
}
|
||||
|
||||
/// Recursively fs::copy every file in src to dest
|
||||
/// Recursively get a list of all subfiles in src
|
||||
/// uses async recursion
|
||||
#[theseus_macros::debug_pin]
|
||||
#[async_recursion::async_recursion]
|
||||
#[tracing::instrument]
|
||||
async fn copy_dir_to(
|
||||
src: &Path,
|
||||
dst: &Path,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<()> {
|
||||
async fn get_all_subfiles(src: &Path) -> crate::Result<Vec<PathBuf>> {
|
||||
if !src.is_dir() {
|
||||
fetch::copy(src, dst, io_semaphore).await?;
|
||||
return Ok(());
|
||||
return Ok(vec![src.to_path_buf()]);
|
||||
}
|
||||
|
||||
// Create the destination directory
|
||||
io::create_dir_all(&dst).await?;
|
||||
|
||||
// Iterate over the directory
|
||||
let mut files = Vec::new();
|
||||
let mut dir = io::read_dir(&src).await?;
|
||||
while let Some(child) = dir
|
||||
.next_entry()
|
||||
@@ -294,21 +314,7 @@ async fn copy_dir_to(
|
||||
.map_err(|e| IOError::with_path(e, src))?
|
||||
{
|
||||
let src_child = child.path();
|
||||
let dst_child = dst.join(src_child.file_name().ok_or_else(|| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Invalid file: {}",
|
||||
&src_child.display()
|
||||
))
|
||||
})?);
|
||||
|
||||
if src_child.is_dir() {
|
||||
// Recurse into sub-directory
|
||||
copy_dir_to(&src_child, &dst_child, io_semaphore).await?;
|
||||
} else {
|
||||
// Copy file
|
||||
fetch::copy(&src_child, &dst_child, io_semaphore).await?;
|
||||
}
|
||||
files.append(&mut get_all_subfiles(&src_child).await?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ pub enum EnvType {
|
||||
Server,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PackDependency {
|
||||
Forge,
|
||||
@@ -101,6 +101,7 @@ pub struct CreatePackProfile {
|
||||
pub icon_url: Option<String>, // the URL icon for a profile (ONLY USED FOR TEMPORARY PROFILES)
|
||||
pub linked_data: Option<LinkedData>, // the linked project ID (mainly for modpacks)- used for updating
|
||||
pub skip_install_profile: Option<bool>,
|
||||
pub no_watch: Option<bool>,
|
||||
}
|
||||
|
||||
// default
|
||||
@@ -115,6 +116,7 @@ impl Default for CreatePackProfile {
|
||||
icon_url: None,
|
||||
linked_data: None,
|
||||
skip_install_profile: Some(true),
|
||||
no_watch: Some(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ pub async fn profile_create(
|
||||
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>,
|
||||
no_watch: Option<bool>,
|
||||
) -> crate::Result<ProfilePathId> {
|
||||
name = profile::sanitize_profile_name(&name);
|
||||
|
||||
@@ -112,7 +113,9 @@ pub async fn profile_create(
|
||||
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
profiles.insert(profile.clone()).await?;
|
||||
profiles
|
||||
.insert(profile.clone(), no_watch.unwrap_or_default())
|
||||
.await?;
|
||||
}
|
||||
|
||||
if !skip_install_profile.unwrap_or(false) {
|
||||
@@ -146,6 +149,7 @@ pub async fn profile_create_from_creator(
|
||||
profile.icon_url,
|
||||
profile.linked_data,
|
||||
profile.skip_install_profile,
|
||||
profile.no_watch,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -974,7 +974,7 @@ pub async fn create_mrpack_json(
|
||||
// But the values are sanitized to only include the version number
|
||||
let dependencies = dependencies
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, sanitize_loader_version_string(&v).to_string()))
|
||||
.map(|(k, v)| (k, sanitize_loader_version_string(&v, k).to_string()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let files: Result<Vec<PackFile>, crate::ErrorKind> = profile
|
||||
@@ -1043,18 +1043,26 @@ pub async fn create_mrpack_json(
|
||||
})
|
||||
}
|
||||
|
||||
fn sanitize_loader_version_string(s: &str) -> &str {
|
||||
// Split on '-'
|
||||
// If two or more, take the second
|
||||
// If one, take the first
|
||||
// If none, take the whole thing
|
||||
let mut split: std::str::Split<'_, char> = s.split('-');
|
||||
match split.next() {
|
||||
Some(first) => match split.next() {
|
||||
Some(second) => second,
|
||||
None => first,
|
||||
},
|
||||
None => s,
|
||||
fn sanitize_loader_version_string(s: &str, loader: PackDependency) -> &str {
|
||||
match loader {
|
||||
// Split on '-'
|
||||
// If two or more, take the second
|
||||
// If one, take the first
|
||||
// If none, take the whole thing
|
||||
PackDependency::Forge => {
|
||||
let mut split: std::str::Split<'_, char> = s.split('-');
|
||||
match split.next() {
|
||||
Some(first) => match split.next() {
|
||||
Some(second) => second,
|
||||
None => first,
|
||||
},
|
||||
None => s,
|
||||
}
|
||||
}
|
||||
// For quilt, etc we take the whole thing, as it functions like: 0.20.0-beta.11 (and should not be split here)
|
||||
PackDependency::QuiltLoader
|
||||
| PackDependency::FabricLoader
|
||||
| PackDependency::Minecraft => s,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,10 @@ pub enum LoadingBarType {
|
||||
ConfigChange {
|
||||
new_path: PathBuf,
|
||||
},
|
||||
CopyProfile {
|
||||
import_location: PathBuf,
|
||||
profile_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
|
||||
@@ -320,7 +320,7 @@ pub async fn download_libraries(
|
||||
let reader = std::io::Cursor::new(&data);
|
||||
if let Ok(mut archive) = zip::ZipArchive::new(reader) {
|
||||
match archive.extract(st.directories.version_natives_dir(version).await) {
|
||||
Ok(_) => tracing::info!("Fetched native {}", &library.name),
|
||||
Ok(_) => tracing::debug!("Fetched native {}", &library.name),
|
||||
Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -461,28 +461,28 @@ pub async fn launch_minecraft(
|
||||
// Uses 'a:b' syntax which is not quite yaml
|
||||
use regex::Regex;
|
||||
|
||||
let options_path = instance_path.join("options.txt");
|
||||
let mut options_string = String::new();
|
||||
|
||||
if options_path.exists() {
|
||||
options_string = io::read_to_string(&options_path).await?;
|
||||
}
|
||||
|
||||
for (key, value) in mc_set_options {
|
||||
let re = Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
|
||||
// check if the regex exists in the file
|
||||
if !re.is_match(&options_string) {
|
||||
// The key was not found in the file, so append it
|
||||
options_string.push_str(&format!("\n{}:{}", key, value));
|
||||
} else {
|
||||
let replaced_string = re
|
||||
.replace_all(&options_string, &format!("{}:{}", key, value))
|
||||
.to_string();
|
||||
options_string = replaced_string;
|
||||
if !mc_set_options.is_empty() {
|
||||
let options_path = instance_path.join("options.txt");
|
||||
let mut options_string = String::new();
|
||||
if options_path.exists() {
|
||||
options_string = io::read_to_string(&options_path).await?;
|
||||
}
|
||||
for (key, value) in mc_set_options {
|
||||
let re = Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
|
||||
// check if the regex exists in the file
|
||||
if !re.is_match(&options_string) {
|
||||
// The key was not found in the file, so append it
|
||||
options_string.push_str(&format!("\n{}:{}", key, value));
|
||||
} else {
|
||||
let replaced_string = re
|
||||
.replace_all(&options_string, &format!("{}:{}", key, value))
|
||||
.to_string();
|
||||
options_string = replaced_string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
io::write(&options_path, options_string).await?;
|
||||
io::write(&options_path, options_string).await?;
|
||||
}
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
|
||||
@@ -353,19 +353,17 @@ pub async fn init_watcher() -> crate::Result<Debouncer<RecommendedWatcher>> {
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let span = tracing::span!(tracing::Level::INFO, "init_watcher");
|
||||
tracing::info!(parent: &span, "Initting watcher");
|
||||
while let Some(res) = rx.next().await {
|
||||
let _span = span.enter();
|
||||
match res {
|
||||
Ok(mut events) => {
|
||||
let mut visited_paths = Vec::new();
|
||||
// sort events by e.path
|
||||
events.sort_by(|a, b| a.path.cmp(&b.path));
|
||||
events.iter().for_each(|e| {
|
||||
tracing::debug!(
|
||||
"File watcher event: {:?}",
|
||||
serde_json::to_string(&e.path).unwrap()
|
||||
);
|
||||
let mut new_path = PathBuf::new();
|
||||
let mut components_iterator = e.path.components();
|
||||
let mut found = false;
|
||||
|
||||
@@ -331,14 +331,13 @@ impl Profile {
|
||||
}
|
||||
|
||||
pub fn sync_projects_task(profile_path_id: ProfilePathId, force: bool) {
|
||||
let span = tracing::span!(
|
||||
tracing::Level::INFO,
|
||||
"sync_projects_task",
|
||||
?profile_path_id,
|
||||
?force
|
||||
);
|
||||
tokio::task::spawn(async move {
|
||||
let span =
|
||||
tracing::span!(tracing::Level::INFO, "sync_projects_task");
|
||||
tracing::debug!(
|
||||
parent: &span,
|
||||
"Syncing projects for profile {}",
|
||||
profile_path_id
|
||||
);
|
||||
let res = async {
|
||||
let _span = span.enter();
|
||||
let state = State::get().await?;
|
||||
@@ -840,12 +839,17 @@ impl Profiles {
|
||||
drop(creds);
|
||||
|
||||
// Versions are pre-sorted in labrinth (by versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));)
|
||||
// so we can just take the first one
|
||||
// so we can just take the first one for which the loader matches
|
||||
let mut new_profiles = state.profiles.write().await;
|
||||
if let Some(profile) =
|
||||
new_profiles.0.get_mut(&profile_path)
|
||||
{
|
||||
if let Some(recent_version) = versions.get(0) {
|
||||
let loader = profile.metadata.loader;
|
||||
let recent_version = versions.iter().find(|x| {
|
||||
x.loaders
|
||||
.contains(&loader.as_api_str().to_string())
|
||||
});
|
||||
if let Some(recent_version) = recent_version {
|
||||
profile.modrinth_update_version =
|
||||
Some(recent_version.id.clone());
|
||||
} else {
|
||||
@@ -879,7 +883,11 @@ impl Profiles {
|
||||
|
||||
#[tracing::instrument(skip(self, profile))]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
|
||||
pub async fn insert(
|
||||
&mut self,
|
||||
profile: Profile,
|
||||
no_watch: bool,
|
||||
) -> crate::Result<&Self> {
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
&profile.profile_id(),
|
||||
@@ -888,13 +896,15 @@ impl Profiles {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(
|
||||
&profile.get_profile_full_path().await?,
|
||||
&mut file_watcher,
|
||||
)
|
||||
.await?;
|
||||
if !no_watch {
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(
|
||||
&profile.get_profile_full_path().await?,
|
||||
&mut file_watcher,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let profile_name = profile.profile_id();
|
||||
profile_name.check_valid_utf()?;
|
||||
@@ -986,6 +996,7 @@ impl Profiles {
|
||||
dirs,
|
||||
)
|
||||
.await?,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
Profile::sync_projects_task(profile_path_id, false);
|
||||
|
||||
@@ -281,7 +281,6 @@ pub async fn infer_data_from_files(
|
||||
) -> crate::Result<HashMap<ProjectPathId, Project>> {
|
||||
let mut file_path_hashes = HashMap::new();
|
||||
|
||||
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
|
||||
for path in paths {
|
||||
if !path.exists() {
|
||||
continue;
|
||||
@@ -297,10 +296,19 @@ pub async fn infer_data_from_files(
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &path))?;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await.map_err(IOError::from)?;
|
||||
let mut buffer = [0u8; 4096]; // Buffer to read chunks
|
||||
let mut hasher = sha2::Sha512::new(); // Hasher
|
||||
|
||||
let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
|
||||
loop {
|
||||
let bytes_read =
|
||||
file.read(&mut buffer).await.map_err(IOError::from)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
hasher.update(&buffer[..bytes_read]);
|
||||
}
|
||||
|
||||
let hash = format!("{:x}", hasher.finalize());
|
||||
file_path_hashes.insert(hash, path.clone());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user