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:
Wyatt Verchere
2023-08-14 13:23:42 -07:00
committed by GitHub
parent a1a5b8ed9c
commit d6ee1ff25a
33 changed files with 357 additions and 321 deletions

View File

@@ -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)

View File

@@ -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(())

View File

@@ -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?;
}

View File

@@ -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?;
}

View File

@@ -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(())

View File

@@ -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)
}

View File

@@ -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),
}
}
}

View File

@@ -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
}

View File

@@ -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,
}
}

View File

@@ -185,6 +185,10 @@ pub enum LoadingBarType {
ConfigChange {
new_path: PathBuf,
},
CopyProfile {
import_location: PathBuf,
profile_name: String,
},
}
#[derive(Serialize, Clone)]

View File

@@ -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 {

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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);

View File

@@ -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());
}