parent
9702dae19d
commit
0d3f007dd4
@ -301,7 +301,7 @@ pub async fn copy_dotminecraft(
|
||||
#[theseus_macros::debug_pin]
|
||||
#[async_recursion::async_recursion]
|
||||
#[tracing::instrument]
|
||||
async fn get_all_subfiles(src: &Path) -> crate::Result<Vec<PathBuf>> {
|
||||
pub async fn get_all_subfiles(src: &Path) -> crate::Result<Vec<PathBuf>> {
|
||||
if !src.is_dir() {
|
||||
return Ok(vec![src.to_path_buf()]);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Theseus profile management interface
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::path::{PathBuf, Path};
|
||||
use tokio::fs;
|
||||
|
||||
use io::IOError;
|
||||
@ -10,7 +10,7 @@ use crate::{
|
||||
event::emit::{emit_loading, init_loading},
|
||||
prelude::DirectoryInfo,
|
||||
state::{self, Profiles},
|
||||
util::io,
|
||||
util::{io, fetch},
|
||||
};
|
||||
pub use crate::{
|
||||
state::{
|
||||
@ -77,6 +77,7 @@ pub async fn set(settings: Settings) -> crate::Result<()> {
|
||||
/// Sets the new config dir, the location of all Theseus data except for the settings.json and caches
|
||||
/// Takes control of the entire state and blocks until completion
|
||||
pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
||||
tracing::trace!("Changing config dir to: {}", new_config_dir.display());
|
||||
if !new_config_dir.is_dir() {
|
||||
return Err(crate::ErrorKind::FSError(format!(
|
||||
"New config dir is not a folder: {}",
|
||||
@ -85,6 +86,14 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
||||
.as_error());
|
||||
}
|
||||
|
||||
if !is_dir_writeable(new_config_dir.clone()).await? {
|
||||
return Err(crate::ErrorKind::FSError(format!(
|
||||
"New config dir is not writeable: {}",
|
||||
new_config_dir.display()
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
|
||||
let loading_bar = init_loading(
|
||||
crate::LoadingBarType::ConfigChange {
|
||||
new_path: new_config_dir.clone(),
|
||||
@ -100,6 +109,52 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
||||
let old_config_dir =
|
||||
state_write.directories.config_dir.read().await.clone();
|
||||
|
||||
|
||||
// Reset file watcher
|
||||
tracing::trace!("Reset file watcher");
|
||||
let file_watcher = state::init_watcher().await?;
|
||||
state_write.file_watcher = RwLock::new(file_watcher);
|
||||
|
||||
// Getting files to be moved
|
||||
let mut config_entries = io::read_dir(&old_config_dir).await?;
|
||||
let across_drives = is_different_drive(&old_config_dir, &new_config_dir);
|
||||
let mut entries = vec![];
|
||||
let mut deletable_entries = vec![];
|
||||
while let Some(entry) = config_entries
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &old_config_dir))?
|
||||
{
|
||||
|
||||
let entry_path = entry.path();
|
||||
if let Some(file_name) = entry_path.file_name() {
|
||||
// We are only moving the profiles and metadata folders
|
||||
if file_name == state::PROFILES_FOLDER_NAME || file_name == state::METADATA_FOLDER_NAME {
|
||||
if across_drives {
|
||||
entries.extend(crate::pack::import::get_all_subfiles(&entry_path).await?);
|
||||
deletable_entries.push(entry_path.clone());
|
||||
} else {
|
||||
entries.push(entry_path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!("Moving files");
|
||||
let semaphore = &state_write.io_semaphore;
|
||||
let num_entries = entries.len() as f64;
|
||||
for entry_path in entries {
|
||||
let relative_path = entry_path.strip_prefix(&old_config_dir)?;
|
||||
let new_path = new_config_dir.join(relative_path);
|
||||
if across_drives {
|
||||
fetch::copy(&entry_path, &new_path, semaphore).await?;
|
||||
} else {
|
||||
io::rename(entry_path.clone(), new_path.clone()).await?;
|
||||
}
|
||||
emit_loading(&loading_bar, 80.0 * (1.0 / num_entries), None)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tracing::trace!("Setting configuration setting");
|
||||
// Set load config dir setting
|
||||
let settings = {
|
||||
@ -132,41 +187,19 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
||||
tracing::trace!("Reinitializing directory");
|
||||
// Set new state information
|
||||
state_write.directories = DirectoryInfo::init(&settings)?;
|
||||
let total_entries = std::fs::read_dir(&old_config_dir)
|
||||
.map_err(|e| IOError::with_path(e, &old_config_dir))?
|
||||
.count() as f64;
|
||||
|
||||
// Move all files over from state_write.directories.config_dir to new_config_dir
|
||||
tracing::trace!("Renaming folder structure");
|
||||
let mut i = 0.0;
|
||||
let mut entries = io::read_dir(&old_config_dir).await?;
|
||||
while let Some(entry) = entries
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &old_config_dir))?
|
||||
{
|
||||
let entry_path = entry.path();
|
||||
if let Some(file_name) = entry_path.file_name() {
|
||||
// Ignore settings.json
|
||||
if file_name == state::SETTINGS_FILE_NAME {
|
||||
continue;
|
||||
}
|
||||
// Ignore caches folder
|
||||
if file_name == state::CACHES_FOLDER_NAME {
|
||||
continue;
|
||||
}
|
||||
// Ignore modrinth_logs folder
|
||||
if file_name == state::LAUNCHER_LOGS_FOLDER_NAME {
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_path = new_config_dir.join(file_name);
|
||||
io::rename(entry_path, new_path).await?;
|
||||
|
||||
i += 1.0;
|
||||
emit_loading(&loading_bar, 90.0 * (i / total_entries), None)
|
||||
.await?;
|
||||
// Delete entries that were from a different drive
|
||||
let deletable_entries_len = deletable_entries.len();
|
||||
if deletable_entries_len > 0 {
|
||||
tracing::trace!("Deleting old files");
|
||||
}
|
||||
for entry in deletable_entries {
|
||||
io::remove_dir_all(entry).await?;
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
10.0 * (1.0 / deletable_entries_len as f64),
|
||||
None,
|
||||
).await?;
|
||||
}
|
||||
|
||||
// Reset file watcher
|
||||
@ -181,15 +214,21 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
// TODO: need to be able to safely error out of this function, reverting the changes
|
||||
tracing::info!(
|
||||
"Successfully switched config folder to: {}",
|
||||
new_config_dir.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Function to check if two paths are on different drives/roots
|
||||
fn is_different_drive(path1: &Path, path2: &Path) -> bool {
|
||||
let root1 = path1.components().next();
|
||||
let root2 = path2.components().next();
|
||||
root1 != root2
|
||||
}
|
||||
|
||||
|
||||
pub async fn is_dir_writeable(new_config_dir: PathBuf) -> crate::Result<bool> {
|
||||
let temp_path = new_config_dir.join(".tmp");
|
||||
match fs::write(temp_path.clone(), "test").await {
|
||||
|
||||
@ -9,6 +9,8 @@ use super::{ProfilePathId, Settings};
|
||||
pub const SETTINGS_FILE_NAME: &str = "settings.json";
|
||||
pub const CACHES_FOLDER_NAME: &str = "caches";
|
||||
pub const LAUNCHER_LOGS_FOLDER_NAME: &str = "launcher_logs";
|
||||
pub const PROFILES_FOLDER_NAME: &str = "profiles";
|
||||
pub const METADATA_FOLDER_NAME: &str = "meta";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectoryInfo {
|
||||
@ -75,7 +77,7 @@ impl DirectoryInfo {
|
||||
/// Get the Minecraft instance metadata directory
|
||||
#[inline]
|
||||
pub async fn metadata_dir(&self) -> PathBuf {
|
||||
self.config_dir.read().await.join("meta")
|
||||
self.config_dir.read().await.join(METADATA_FOLDER_NAME)
|
||||
}
|
||||
|
||||
/// Get the Minecraft java versions metadata directory
|
||||
@ -153,7 +155,7 @@ impl DirectoryInfo {
|
||||
/// Get the profiles directory for created profiles
|
||||
#[inline]
|
||||
pub async fn profiles_dir(&self) -> PathBuf {
|
||||
self.config_dir.read().await.join("profiles")
|
||||
self.config_dir.read().await.join(PROFILES_FOLDER_NAME)
|
||||
}
|
||||
|
||||
/// Gets the logs dir for a given profile
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
// 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;
|
||||
use std::{path::Path, io::Write};
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
use tauri::async_runtime::spawn_blocking;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum IOError {
|
||||
@ -113,15 +116,39 @@ pub async fn write(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
data: impl AsRef<[u8]>,
|
||||
) -> Result<(), IOError> {
|
||||
let path = path.as_ref();
|
||||
tokio::fs::write(path, data)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
let path = path.as_ref().to_owned();
|
||||
let data = data.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
let cloned_path = path.clone();
|
||||
sync_write(data, path).map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
path: cloned_path.to_string_lossy().to_string(),
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|_| {
|
||||
std::io::Error::new(std::io::ErrorKind::Other, "background task failed")
|
||||
})??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_write(
|
||||
data: impl AsRef<[u8]>,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let mut tempfile = NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"could not get parent directory for temporary file",
|
||||
)
|
||||
})?)?;
|
||||
tempfile.write_all(data.as_ref())?;
|
||||
let tmp_path = tempfile.into_temp_path();
|
||||
let path = path.as_ref();
|
||||
tmp_path.persist(path)?;
|
||||
std::io::Result::Ok(())
|
||||
}
|
||||
// rename
|
||||
pub async fn rename(
|
||||
from: impl AsRef<std::path::Path>,
|
||||
|
||||
@ -139,7 +139,7 @@ async function findLauncherDir() {
|
||||
}
|
||||
|
||||
async function refreshDir() {
|
||||
await change_config_dir(settingsDir.value)
|
||||
await change_config_dir(settingsDir.value).catch(handleError)
|
||||
settings.value = await accessSettings().catch(handleError)
|
||||
settingsDir.value = settings.value.loaded_config_dir
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user