* fixes

* prettier

* more bugs

* changes

* more fixes

* prettier, fmt, clippy

* fix regressed error

* println, console.log

* fix imports

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
Wyatt Verchere
2023-08-04 20:33:50 -07:00
committed by GitHub
parent 6a76811bed
commit a35dd67b77
27 changed files with 357 additions and 100 deletions

View File

@@ -229,7 +229,13 @@ async fn import_atlauncher_unmanaged(
.await?;
// Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc)
copy_dotminecraft(profile_path.clone(), minecraft_folder).await?;
let state = State::get().await?;
copy_dotminecraft(
profile_path.clone(),
minecraft_folder,
&state.io_semaphore,
)
.await?;
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?

View File

@@ -5,11 +5,14 @@ use serde::{Deserialize, Serialize};
use crate::{
prelude::{ModLoader, ProfilePathId},
state::ProfileInstallStage,
util::io,
util::{
fetch::{fetch, write_cached_icon},
io,
},
State,
};
use super::copy_dotminecraft;
use super::{copy_dotminecraft, recache_icon};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -31,12 +34,20 @@ pub struct FlameModLoader {
pub primary: bool,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MinecraftInstance {
pub name: Option<String>,
pub profile_image_path: Option<PathBuf>,
pub installed_modpack: Option<InstalledModpack>,
pub game_version: String, // Minecraft game version. Non-prioritized, use this if Vanilla
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct InstalledModpack {
pub thumbnail_url: Option<String>,
}
// Check if folder has a minecraftinstance.json that parses
pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
@@ -53,9 +64,6 @@ pub async fn import_curseforge(
curseforge_instance_folder: PathBuf, // instance's folder
profile_path: ProfilePathId, // path to profile
) -> crate::Result<()> {
// TODO: recache curseforge instance icon
let icon: Option<PathBuf> = None;
// Load minecraftinstance.json
let minecraft_instance: String = io::read_to_string(
&curseforge_instance_folder.join("minecraftinstance.json"),
@@ -72,6 +80,32 @@ pub async fn import_curseforge(
.unwrap_or("Unknown".to_string())
);
let state = State::get().await?;
// Recache Curseforge Icon if it exists
let mut icon = None;
if let Some(icon_path) = minecraft_instance.profile_image_path.clone() {
icon = recache_icon(icon_path).await?;
} else if let Some(InstalledModpack {
thumbnail_url: Some(thumbnail_url),
}) = minecraft_instance.installed_modpack.clone()
{
let icon_bytes =
fetch(&thumbnail_url, None, &state.fetch_semaphore).await?;
let filename = thumbnail_url.rsplit('/').last();
if let Some(filename) = filename {
icon = Some(
write_cached_icon(
filename,
&state.directories.caches_dir(),
icon_bytes,
&state.io_semaphore,
)
.await?,
);
}
}
// Curseforge vanilla profile may not have a manifest.json, so we allow it to not exist
if curseforge_instance_folder.join("manifest.json").exists() {
// Load manifest.json
@@ -146,7 +180,13 @@ pub async fn import_curseforge(
}
// Copy in contained folders as overrides
copy_dotminecraft(profile_path.clone(), curseforge_instance_folder).await?;
let state = State::get().await?;
copy_dotminecraft(
profile_path.clone(),
curseforge_instance_folder,
&state.io_semaphore,
)
.await?;
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?

View File

@@ -100,7 +100,13 @@ pub async fn import_gdlauncher(
.await?;
// Copy in contained folders as overrides
copy_dotminecraft(profile_path.clone(), gdlauncher_instance_folder).await?;
let state = State::get().await?;
copy_dotminecraft(
profile_path.clone(),
gdlauncher_instance_folder,
&state.io_semaphore,
)
.await?;
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?

View File

@@ -280,7 +280,13 @@ async fn import_mmc_unmanaged(
.await?;
// Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc)
copy_dotminecraft(profile_path.clone(), minecraft_folder).await?;
let state = State::get().await?;
copy_dotminecraft(
profile_path.clone(),
minecraft_folder,
&state.io_semaphore,
)
.await?;
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?

View File

@@ -1,4 +1,7 @@
use std::path::{Path, PathBuf};
use std::{
fmt,
path::{Path, PathBuf},
};
use io::IOError;
use serde::{Deserialize, Serialize};
@@ -6,7 +9,10 @@ use serde::{Deserialize, Serialize};
use crate::{
prelude::ProfilePathId,
state::Profiles,
util::{fetch, io},
util::{
fetch::{self, IoSemaphore},
io,
},
};
pub mod atlauncher;
@@ -24,6 +30,19 @@ pub enum ImportLauncherType {
#[serde(other)]
Unknown,
}
// impl display
impl fmt::Display for ImportLauncherType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImportLauncherType::MultiMC => write!(f, "MultiMC"),
ImportLauncherType::PrismLauncher => write!(f, "PrismLauncher"),
ImportLauncherType::ATLauncher => write!(f, "ATLauncher"),
ImportLauncherType::GDLauncher => write!(f, "GDLauncher"),
ImportLauncherType::Curseforge => write!(f, "Curseforge"),
ImportLauncherType::Unknown => write!(f, "Unknown"),
}
}
}
// Return a list of importable instances from a launcher type and base path, by iterating through the folder and checking
pub async fn get_importable_instances(
@@ -31,12 +50,12 @@ pub async fn get_importable_instances(
base_path: PathBuf,
) -> crate::Result<Vec<String>> {
// Some launchers have a different folder structure for instances
let instances_folder = match launcher_type {
let instances_subfolder = match launcher_type {
ImportLauncherType::GDLauncher
| ImportLauncherType::MultiMC
| ImportLauncherType::PrismLauncher
| ImportLauncherType::ATLauncher => base_path.join("instances"),
ImportLauncherType::Curseforge => base_path.join("Instances"),
| ImportLauncherType::ATLauncher => "instances",
ImportLauncherType::Curseforge => "Instances",
ImportLauncherType::Unknown => {
return Err(crate::ErrorKind::InputError(
"Launcher type Unknown".to_string(),
@@ -44,8 +63,13 @@ pub async fn get_importable_instances(
.into())
}
};
let instances_folder = base_path.join(instances_subfolder);
let mut instances = Vec::new();
let mut dir = io::read_dir(&instances_folder).await?;
let mut dir = io::read_dir(&instances_folder).await.map_err(| _ | {
crate::ErrorKind::InputError(format!(
"Invalid {launcher_type} launcher path, could not find '{instances_subfolder}' subfolder."
))
})?;
while let Some(entry) = dir
.next_entry()
.await
@@ -216,6 +240,7 @@ pub async fn recache_icon(
async fn copy_dotminecraft(
profile_path: ProfilePathId,
dotminecraft: PathBuf,
io_semaphore: &IoSemaphore,
) -> crate::Result<()> {
// Get full path to profile
let profile_path = profile_path.get_full_path().await?;
@@ -236,6 +261,7 @@ async fn copy_dotminecraft(
&path.display()
))
})?),
io_semaphore,
)
.await?;
}
@@ -247,9 +273,13 @@ async fn copy_dotminecraft(
#[theseus_macros::debug_pin]
#[async_recursion::async_recursion]
#[tracing::instrument]
async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> {
async fn copy_dir_to(
src: &Path,
dst: &Path,
io_semaphore: &IoSemaphore,
) -> crate::Result<()> {
if !src.is_dir() {
io::copy(src, dst).await?;
fetch::copy(src, dst, io_semaphore).await?;
return Ok(());
}
@@ -273,10 +303,10 @@ async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> {
if src_child.is_dir() {
// Recurse into sub-directory
copy_dir_to(&src_child, &dst_child).await?;
copy_dir_to(&src_child, &dst_child, io_semaphore).await?;
} else {
// Copy file
io::copy(&src_child, &dst_child).await?;
fetch::copy(&src_child, &dst_child, io_semaphore).await?;
}
}

View File

@@ -9,7 +9,7 @@ use crate::prelude::ProfilePathId;
use crate::state::{ProfileInstallStage, Profiles, SideType};
use crate::util::fetch::{fetch_mirrors, write};
use crate::util::io;
use crate::State;
use crate::{profile, State};
use async_zip::tokio::read::seek::ZipFileReader;
use std::io::Cursor;
@@ -82,6 +82,7 @@ pub async fn install_zipped_mrpack_files(
let version_id = create_pack.description.version_id;
let existing_loading_bar = create_pack.description.existing_loading_bar;
let profile_path = create_pack.description.profile_path;
let icon_exists = icon.is_some();
let reader: Cursor<&bytes::Bytes> = Cursor::new(&file);
@@ -186,7 +187,7 @@ pub async fn install_zipped_mrpack_files(
let path = profile_path
.get_full_path()
.await?
.join(project.path);
.join(&project.path);
write(&path, &file, &state.io_semaphore)
.await?;
}
@@ -261,6 +262,14 @@ pub async fn install_zipped_mrpack_files(
}
}
// If the icon doesn't exist, we expect icon.png to be a potential icon.
// If it doesn't exist, and an override to icon.png exists, cache and use that
let potential_icon =
profile_path.get_full_path().await?.join("icon.png");
if !icon_exists && potential_icon.exists() {
profile::edit_icon(&profile_path, Some(&potential_icon)).await?;
}
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?
{

View File

@@ -104,7 +104,7 @@ pub async fn profile_create(
emit_profile(
uuid,
profile.get_profile_full_path().await?,
&profile.profile_id(),
&profile.metadata.name,
ProfilePayloadType::Created,
)

View File

@@ -45,7 +45,7 @@ pub async fn remove(path: &ProfilePathId) -> crate::Result<()> {
if let Some(profile) = profiles.remove(path).await? {
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
path,
&profile.metadata.name,
ProfilePayloadType::Removed,
)
@@ -124,7 +124,7 @@ where
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -162,7 +162,7 @@ pub async fn edit_icon(
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -285,7 +285,6 @@ pub async fn update_all_projects(
)
.await?;
let profile_base_path = profile.get_profile_full_path().await?;
let keys = profile
.projects
.into_iter()
@@ -331,7 +330,7 @@ pub async fn update_all_projects(
emit_profile(
profile.uuid,
profile_base_path,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -378,10 +377,12 @@ pub async fn update_project(
if let Some(mut project) = value {
if let ProjectMetadata::Modrinth {
ref mut version,
ref mut update_version,
..
} = project.metadata
{
*version = Box::new(new_version);
*update_version = None;
}
profile.projects.insert(path.clone(), project);
}
@@ -391,7 +392,7 @@ pub async fn update_project(
if !skip_send_event.unwrap_or(false) {
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -427,7 +428,7 @@ pub async fn add_project_from_version(
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -467,7 +468,7 @@ pub async fn add_project_from_path(
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -488,15 +489,15 @@ pub async fn add_project_from_path(
/// returns the new state, relative to the profile
#[tracing::instrument]
pub async fn toggle_disable_project(
profile: &ProfilePathId,
profile_path: &ProfilePathId,
project: &ProjectPathId,
) -> crate::Result<ProjectPathId> {
if let Some(profile) = get(profile, None).await? {
if let Some(profile) = get(profile_path, None).await? {
let res = profile.toggle_disable_project(project).await?;
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -505,8 +506,10 @@ pub async fn toggle_disable_project(
Ok(res)
} else {
Err(crate::ErrorKind::UnmanagedProfileError(profile.to_string())
.as_error())
Err(
crate::ErrorKind::UnmanagedProfileError(profile_path.to_string())
.as_error(),
)
}
}
@@ -514,15 +517,15 @@ pub async fn toggle_disable_project(
/// Uses and returns the relative path to the project
#[tracing::instrument]
pub async fn remove_project(
profile: &ProfilePathId,
profile_path: &ProfilePathId,
project: &ProjectPathId,
) -> crate::Result<()> {
if let Some(profile) = get(profile, None).await? {
if let Some(profile) = get(profile_path, None).await? {
profile.remove_project(project, None).await?;
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -531,8 +534,10 @@ pub async fn remove_project(
Ok(())
} else {
Err(crate::ErrorKind::UnmanagedProfileError(profile.to_string())
.as_error())
Err(
crate::ErrorKind::UnmanagedProfileError(profile_path.to_string())
.as_error(),
)
}
}
@@ -1032,5 +1037,5 @@ pub async fn build_folder(
}
pub fn sanitize_profile_name(input: &str) -> String {
input.replace(['/', '\\'], "_")
input.replace(['/', '\\', ':'], "_")
}

View File

@@ -57,7 +57,7 @@ pub async fn update_managed_modrinth(
emit_profile(
profile.uuid,
profile.path,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@@ -133,7 +133,7 @@ pub async fn repair_managed_modrinth(
emit_profile(
profile.uuid,
profile.path,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)

View File

@@ -4,10 +4,10 @@ use crate::{
CommandPayload, EventError, LoadingBar, LoadingBarType,
ProcessPayloadType, ProfilePayloadType,
},
prelude::ProfilePathId,
state::{ProcessType, SafeProcesses},
};
use futures::prelude::*;
use std::path::PathBuf;
#[cfg(feature = "tauri")]
use crate::event::{
@@ -298,12 +298,13 @@ pub async fn emit_process(
#[allow(unused_variables)]
pub async fn emit_profile(
uuid: Uuid,
path: PathBuf,
profile_path_id: &ProfilePathId,
name: &str,
event: ProfilePayloadType,
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let path = profile_path_id.get_full_path().await?;
let event_state = crate::EventState::get().await?;
event_state
.app
@@ -311,6 +312,7 @@ pub async fn emit_profile(
"profile",
ProfilePayload {
uuid,
profile_path_id: profile_path_id.clone(),
path,
name: name.to_string(),
event,

View File

@@ -5,6 +5,7 @@ use tokio::sync::OnceCell;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::prelude::ProfilePathId;
use crate::state::SafeProcesses;
pub mod emit;
@@ -240,6 +241,7 @@ pub enum ProcessPayloadType {
#[derive(Serialize, Clone)]
pub struct ProfilePayload {
pub uuid: Uuid,
pub profile_path_id: ProfilePathId,
pub path: PathBuf,
pub name: String,
pub event: ProfilePayloadType,

View File

@@ -303,7 +303,10 @@ impl Profile {
let profile = crate::api::profile::get(&path, None).await?;
if let Some(profile) = profile {
emit_warning(&format!("Profile {} has crashed! Visit the logs page to see a crash report.", profile.metadata.name)).await?;
// Hide warning if profile is not yet installed
if profile.install_stage == ProfileInstallStage::Installed {
emit_warning(&format!("Profile {} has crashed! Visit the logs page to see a crash report.", profile.metadata.name)).await?;
}
}
Ok::<(), crate::Error>(())
@@ -354,7 +357,7 @@ impl Profile {
}
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
&profile_path_id,
&profile.metadata.name,
ProfilePayloadType::Synced,
)
@@ -856,7 +859,7 @@ impl Profiles {
pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
&profile.profile_id(),
&profile.metadata.name,
ProfilePayloadType::Added,
)
@@ -943,7 +946,7 @@ impl Profiles {
// if path exists in the state but no longer in the filesystem, remove it from the state list
emit_profile(
profile.uuid,
profile.get_profile_full_path().await?,
&profile_path_id,
&profile.metadata.name,
ProfilePayloadType::Removed,
)

View File

@@ -231,6 +231,30 @@ pub async fn write<'a>(
Ok(())
}
pub async fn copy(
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
semaphore: &IoSemaphore,
) -> crate::Result<()> {
let src: &Path = src.as_ref();
let dest = dest.as_ref();
let io_semaphore = semaphore.0.read().await;
let _permit = io_semaphore.acquire().await?;
if let Some(parent) = dest.parent() {
io::create_dir_all(parent).await?;
}
io::copy(src, dest).await?;
tracing::trace!(
"Done copying file {} to {}",
src.display(),
dest.display()
);
Ok(())
}
// Writes a icon to the cache and returns the absolute path of the icon within the cache directory
#[tracing::instrument(skip(bytes, semaphore))]
pub async fn write_cached_icon(

View File

@@ -1,6 +1,8 @@
// IO error
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error.
use std::path::Path;
#[derive(Debug, thiserror::Error)]
pub enum IOError {
#[error("{source}, path: {path}")]
@@ -140,7 +142,7 @@ pub async fn copy(
from: impl AsRef<std::path::Path>,
to: impl AsRef<std::path::Path>,
) -> Result<u64, IOError> {
let from = from.as_ref();
let from: &Path = from.as_ref();
let to = to.as_ref();
tokio::fs::copy(from, to)
.await