parent
9702dae19d
commit
0d3f007dd4
@ -301,7 +301,7 @@ pub async fn copy_dotminecraft(
|
|||||||
#[theseus_macros::debug_pin]
|
#[theseus_macros::debug_pin]
|
||||||
#[async_recursion::async_recursion]
|
#[async_recursion::async_recursion]
|
||||||
#[tracing::instrument]
|
#[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() {
|
if !src.is_dir() {
|
||||||
return Ok(vec![src.to_path_buf()]);
|
return Ok(vec![src.to_path_buf()]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Theseus profile management interface
|
//! Theseus profile management interface
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::{PathBuf, Path};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use io::IOError;
|
use io::IOError;
|
||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
event::emit::{emit_loading, init_loading},
|
event::emit::{emit_loading, init_loading},
|
||||||
prelude::DirectoryInfo,
|
prelude::DirectoryInfo,
|
||||||
state::{self, Profiles},
|
state::{self, Profiles},
|
||||||
util::io,
|
util::{io, fetch},
|
||||||
};
|
};
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
state::{
|
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
|
/// 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
|
/// Takes control of the entire state and blocks until completion
|
||||||
pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
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() {
|
if !new_config_dir.is_dir() {
|
||||||
return Err(crate::ErrorKind::FSError(format!(
|
return Err(crate::ErrorKind::FSError(format!(
|
||||||
"New config dir is not a folder: {}",
|
"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());
|
.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(
|
let loading_bar = init_loading(
|
||||||
crate::LoadingBarType::ConfigChange {
|
crate::LoadingBarType::ConfigChange {
|
||||||
new_path: new_config_dir.clone(),
|
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 =
|
let old_config_dir =
|
||||||
state_write.directories.config_dir.read().await.clone();
|
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");
|
tracing::trace!("Setting configuration setting");
|
||||||
// Set load config dir setting
|
// Set load config dir setting
|
||||||
let settings = {
|
let settings = {
|
||||||
@ -132,41 +187,19 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
|
|||||||
tracing::trace!("Reinitializing directory");
|
tracing::trace!("Reinitializing directory");
|
||||||
// Set new state information
|
// Set new state information
|
||||||
state_write.directories = DirectoryInfo::init(&settings)?;
|
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
|
// Delete entries that were from a different drive
|
||||||
tracing::trace!("Renaming folder structure");
|
let deletable_entries_len = deletable_entries.len();
|
||||||
let mut i = 0.0;
|
if deletable_entries_len > 0 {
|
||||||
let mut entries = io::read_dir(&old_config_dir).await?;
|
tracing::trace!("Deleting old files");
|
||||||
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?;
|
|
||||||
}
|
}
|
||||||
|
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
|
// 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?;
|
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!(
|
tracing::info!(
|
||||||
"Successfully switched config folder to: {}",
|
"Successfully switched config folder to: {}",
|
||||||
new_config_dir.display()
|
new_config_dir.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
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> {
|
pub async fn is_dir_writeable(new_config_dir: PathBuf) -> crate::Result<bool> {
|
||||||
let temp_path = new_config_dir.join(".tmp");
|
let temp_path = new_config_dir.join(".tmp");
|
||||||
match fs::write(temp_path.clone(), "test").await {
|
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 SETTINGS_FILE_NAME: &str = "settings.json";
|
||||||
pub const CACHES_FOLDER_NAME: &str = "caches";
|
pub const CACHES_FOLDER_NAME: &str = "caches";
|
||||||
pub const LAUNCHER_LOGS_FOLDER_NAME: &str = "launcher_logs";
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct DirectoryInfo {
|
pub struct DirectoryInfo {
|
||||||
@ -75,7 +77,7 @@ impl DirectoryInfo {
|
|||||||
/// Get the Minecraft instance metadata directory
|
/// Get the Minecraft instance metadata directory
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn metadata_dir(&self) -> PathBuf {
|
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
|
/// Get the Minecraft java versions metadata directory
|
||||||
@ -153,7 +155,7 @@ impl DirectoryInfo {
|
|||||||
/// Get the profiles directory for created profiles
|
/// Get the profiles directory for created profiles
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn profiles_dir(&self) -> PathBuf {
|
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
|
/// Gets the logs dir for a given profile
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
// IO error
|
// IO error
|
||||||
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::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)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum IOError {
|
pub enum IOError {
|
||||||
@ -113,15 +116,39 @@ pub async fn write(
|
|||||||
path: impl AsRef<std::path::Path>,
|
path: impl AsRef<std::path::Path>,
|
||||||
data: impl AsRef<[u8]>,
|
data: impl AsRef<[u8]>,
|
||||||
) -> Result<(), IOError> {
|
) -> Result<(), IOError> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref().to_owned();
|
||||||
tokio::fs::write(path, data)
|
let data = data.as_ref().to_owned();
|
||||||
.await
|
spawn_blocking(move || {
|
||||||
.map_err(|e| IOError::IOPathError {
|
let cloned_path = path.clone();
|
||||||
|
sync_write(data, path).map_err(|e| IOError::IOPathError {
|
||||||
source: e,
|
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
|
// rename
|
||||||
pub async fn rename(
|
pub async fn rename(
|
||||||
from: impl AsRef<std::path::Path>,
|
from: impl AsRef<std::path::Path>,
|
||||||
|
|||||||
@ -139,7 +139,7 @@ async function findLauncherDir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshDir() {
|
async function refreshDir() {
|
||||||
await change_config_dir(settingsDir.value)
|
await change_config_dir(settingsDir.value).catch(handleError)
|
||||||
settings.value = await accessSettings().catch(handleError)
|
settings.value = await accessSettings().catch(handleError)
|
||||||
settingsDir.value = settings.value.loaded_config_dir
|
settingsDir.value = settings.value.loaded_config_dir
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user