You've already forked AstralRinth
forked from didirus/AstralRinth
* Create get_resource_file macro to get an embedded resource If the tauri feature is enabled, the resource will be loaded from Tauri resources. If the tauri feature is disabled, the resource will be extracted to a temp directory. * Wrap process execution to inject system properties through stdin * Pass the time values as ISO 8601 datetimes * Remove entirely internal modrinth.process.uuid * Redo Java version checking somewhat and fix a few bugs with it * Fix game launch with early access versions of Java * Format Java code * Use Gradle to build Java code * Make Gradle build reproducible * Fix constant rebuilds * Get rid of unnecessary rebuilds * Fix modrinth.profile.modified being the same as modrinth.profile.created * Make javac use all lints and treat them as errors * Force Gradle color output * Add Java formatting config * Make gradlew executable * Revert to manually extracting class files * Switch to using update resource macro * fix: make `app-lib` build again --------- Co-authored-by: Alejandro González <me@alegon.dev>
342 lines
9.6 KiB
Rust
342 lines
9.6 KiB
Rust
// 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::{io::Write, path::Path};
|
|
use tempfile::NamedTempFile;
|
|
use tokio::task::spawn_blocking;
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum IOError {
|
|
#[error("{source}, path: {path}")]
|
|
IOPathError {
|
|
#[source]
|
|
source: std::io::Error,
|
|
path: String,
|
|
},
|
|
#[error(transparent)]
|
|
IOError(#[from] std::io::Error),
|
|
}
|
|
|
|
impl IOError {
|
|
pub fn from(source: std::io::Error) -> Self {
|
|
Self::IOError(source)
|
|
}
|
|
pub fn with_path(
|
|
source: std::io::Error,
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Self {
|
|
let path = path.as_ref();
|
|
|
|
Self::IOPathError {
|
|
source,
|
|
path: path.to_string_lossy().to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn canonicalize(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<std::path::PathBuf, IOError> {
|
|
let path = path.as_ref();
|
|
dunce::canonicalize(path).map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn read_dir(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<tokio::fs::ReadDir, IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::read_dir(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn create_dir(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<(), IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::create_dir(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn create_dir_all(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<(), IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::create_dir_all(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn remove_dir_all(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<(), IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::remove_dir_all(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
/// Reads a text file to a string, automatically detecting its encoding and
|
|
/// substituting any invalid characters with the Unicode replacement character.
|
|
///
|
|
/// This function is best suited for reading Minecraft instance files, whose
|
|
/// encoding may vary depending on the platform, launchers, client versions
|
|
/// (older Minecraft versions tended to rely on the system's default codepage
|
|
/// more on Windows platforms), and mods used, while not being highly sensitive
|
|
/// to occasional occurrences of mojibake or character replacements.
|
|
pub async fn read_any_encoding_to_string(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<(String, &'static encoding_rs::Encoding), IOError> {
|
|
let path = path.as_ref();
|
|
let file_bytes =
|
|
tokio::fs::read(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})?;
|
|
|
|
let file_encoding = {
|
|
let mut encoding_detector = chardetng::EncodingDetector::new();
|
|
encoding_detector.feed(&file_bytes, true);
|
|
encoding_detector.guess(None, true)
|
|
};
|
|
|
|
let (file_string, actual_file_encoding, _) =
|
|
file_encoding.decode(&file_bytes);
|
|
Ok((file_string.to_string(), actual_file_encoding))
|
|
}
|
|
|
|
pub async fn read(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<Vec<u8>, IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::read(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn write(
|
|
path: impl AsRef<std::path::Path>,
|
|
data: impl AsRef<[u8]>,
|
|
) -> Result<(), IOError> {
|
|
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: cloned_path.to_string_lossy().to_string(),
|
|
})
|
|
})
|
|
.await
|
|
.map_err(|_| std::io::Error::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::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(())
|
|
}
|
|
|
|
pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result<bool, IOError> {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::MetadataExt;
|
|
Ok(old_dir.metadata()?.dev() == new_dir.metadata()?.dev())
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
{
|
|
let old_dir = canonicalize(old_dir)?;
|
|
let new_dir = canonicalize(new_dir)?;
|
|
|
|
let old_component = old_dir.components().next();
|
|
let new_component = new_dir.components().next();
|
|
|
|
match (old_component, new_component) {
|
|
(
|
|
Some(std::path::Component::Prefix(old)),
|
|
Some(std::path::Component::Prefix(new)),
|
|
) => Ok(old.as_os_str() == new.as_os_str()),
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn rename_or_move(
|
|
from: impl AsRef<std::path::Path>,
|
|
to: impl AsRef<std::path::Path>,
|
|
) -> Result<(), IOError> {
|
|
let from = from.as_ref();
|
|
let to = to.as_ref();
|
|
|
|
if to
|
|
.parent()
|
|
.map_or(Ok(false), |to_dir| is_same_disk(from, to_dir))?
|
|
{
|
|
tokio::fs::rename(from, to)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: from.to_string_lossy().to_string(),
|
|
})
|
|
} else {
|
|
move_recursive(from, to).await
|
|
}
|
|
}
|
|
|
|
#[async_recursion::async_recursion]
|
|
async fn move_recursive(from: &Path, to: &Path) -> Result<(), IOError> {
|
|
if from.is_file() {
|
|
copy(from, to).await?;
|
|
remove_file(from).await?;
|
|
return Ok(());
|
|
}
|
|
|
|
create_dir(to).await?;
|
|
|
|
let mut dir = read_dir(from).await?;
|
|
while let Some(entry) = dir.next_entry().await? {
|
|
let new_path = to.join(entry.file_name());
|
|
move_recursive(&entry.path(), &new_path).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn copy(
|
|
from: impl AsRef<std::path::Path>,
|
|
to: impl AsRef<std::path::Path>,
|
|
) -> Result<u64, IOError> {
|
|
let from: &Path = from.as_ref();
|
|
let to = to.as_ref();
|
|
tokio::fs::copy(from, to)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: from.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn remove_file(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<(), IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::remove_file(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn open_file(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<tokio::fs::File, IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::File::open(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn remove_dir(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<(), IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::remove_dir(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
pub async fn metadata(
|
|
path: impl AsRef<std::path::Path>,
|
|
) -> Result<std::fs::Metadata, IOError> {
|
|
let path = path.as_ref();
|
|
tokio::fs::metadata(path)
|
|
.await
|
|
.map_err(|e| IOError::IOPathError {
|
|
source: e,
|
|
path: path.to_string_lossy().to_string(),
|
|
})
|
|
}
|
|
|
|
/// Gets a resource file from the executable. Returns `theseus::Result<(TempDir, PathBuf)>`.
|
|
#[macro_export]
|
|
macro_rules! get_resource_file {
|
|
(directory: $relative_dir:expr, file: $file_name:expr) => {
|
|
'get_resource_file: {
|
|
let dir = match tempfile::tempdir() {
|
|
Ok(dir) => dir,
|
|
Err(e) => {
|
|
break 'get_resource_file $crate::Result::Err(
|
|
$crate::util::io::IOError::from(e).into(),
|
|
);
|
|
}
|
|
};
|
|
let path = dir.path().join($file_name);
|
|
if let Err(e) = $crate::util::io::write(
|
|
&path,
|
|
include_bytes!(concat!($relative_dir, "/", $file_name)),
|
|
)
|
|
.await
|
|
{
|
|
break 'get_resource_file $crate::Result::Err(e.into());
|
|
}
|
|
let path = match $crate::util::io::canonicalize(path) {
|
|
Ok(path) => path,
|
|
Err(e) => {
|
|
break 'get_resource_file $crate::Result::Err(e.into());
|
|
}
|
|
};
|
|
$crate::Result::Ok((dir, path))
|
|
}
|
|
};
|
|
|
|
($relative_dir:literal / $file_name:literal) => {
|
|
get_resource_file!(directory: $relative_dir, file: $file_name)
|
|
};
|
|
|
|
(env $dir_env_name:literal / $file_name:literal) => {
|
|
get_resource_file!(directory: env!($dir_env_name), file: $file_name)
|
|
};
|
|
}
|