Bug fixes round 3 (#298)

* fixed bugs

* title case

* bugs; ioerror

* reset breadcrumbs

* more fixes

* more fixes

* scrolling bug

* more fixes

* more fixes

* clippy

* canonicalize fix

* fixed requested changes

* removed debouncer update
This commit is contained in:
Wyatt Verchere
2023-07-19 14:13:25 -07:00
committed by GitHub
parent 6650cc9ce4
commit 1f478ec9fc
34 changed files with 932 additions and 602 deletions

View File

@@ -1,8 +1,11 @@
use std::path::PathBuf;
use crate::event::{
emit::{emit_command, emit_warning},
CommandPayload,
use crate::{
event::{
emit::{emit_command, emit_warning},
CommandPayload,
},
util::io,
};
/// Handles external functions (such as through URL deep linkage)
@@ -46,7 +49,7 @@ pub async fn parse_command(
} else {
// We assume anything else is a filepath to an .mrpack file
let path = PathBuf::from(command_string);
let path = path.canonicalize()?;
let path = io::canonicalize(path)?;
if let Some(ext) = path.extension() {
if ext == "mrpack" {
return Ok(CommandPayload::RunMRPack { path });

View File

@@ -5,6 +5,7 @@ use std::path::PathBuf;
use crate::event::emit::{emit_loading, init_loading};
use crate::util::fetch::{fetch_advanced, fetch_json};
use crate::util::io;
use crate::util::jre::extract_java_majorminor_version;
use crate::{
state::JavaGlobals,
@@ -114,7 +115,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
let path = state.directories.java_versions_dir();
if path.exists() {
tokio::fs::remove_dir_all(&path).await?;
io::remove_dir_all(&path).await?;
}
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file))

View File

@@ -1,6 +1,8 @@
use crate::State;
use crate::{
util::io::{self, IOError},
State,
};
use serde::{Deserialize, Serialize};
use tokio::fs::read_to_string;
#[derive(Serialize, Deserialize, Debug)]
pub struct Logs {
@@ -36,8 +38,11 @@ pub async fn get_logs(
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
let mut logs = Vec::new();
if logs_folder.exists() {
for entry in std::fs::read_dir(logs_folder)? {
let entry = entry?;
for entry in std::fs::read_dir(&logs_folder)
.map_err(|e| IOError::with_path(e, &logs_folder))?
{
let entry =
entry.map_err(|e| IOError::with_path(e, &logs_folder))?;
let path = entry.path();
if path.is_dir() {
if let Some(datetime_string) = path.file_name() {
@@ -79,21 +84,21 @@ pub async fn get_output_by_datetime(
) -> crate::Result<String> {
let state = State::get().await?;
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
Ok(
read_to_string(logs_folder.join(datetime_string).join("stdout.log"))
.await?,
)
let path = logs_folder.join(datetime_string).join("stdout.log");
Ok(io::read_to_string(&path).await?)
}
#[tracing::instrument]
pub async fn delete_logs(profile_uuid: uuid::Uuid) -> crate::Result<()> {
let state = State::get().await?;
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
for entry in std::fs::read_dir(logs_folder)? {
let entry = entry?;
for entry in std::fs::read_dir(&logs_folder)
.map_err(|e| IOError::with_path(e, &logs_folder))?
{
let entry = entry.map_err(|e| IOError::with_path(e, &logs_folder))?;
let path = entry.path();
if path.is_dir() {
std::fs::remove_dir_all(path)?;
io::remove_dir_all(&path).await?;
}
}
Ok(())
@@ -106,6 +111,7 @@ pub async fn delete_logs_by_datetime(
) -> crate::Result<()> {
let state = State::get().await?;
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
std::fs::remove_dir_all(logs_folder.join(datetime_string))?;
let path = logs_folder.join(datetime_string);
io::remove_dir_all(&path).await?;
Ok(())
}

View File

@@ -29,7 +29,10 @@ pub mod prelude {
profile::{self, Profile},
profile_create, settings,
state::JavaGlobals,
util::jre::JavaVersion,
util::{
io::{canonicalize, IOError},
jre::JavaVersion,
},
State,
};
}

View File

@@ -6,6 +6,7 @@ use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType};
use crate::util::fetch::{
fetch, fetch_advanced, fetch_json, write_cached_icon,
};
use crate::util::io;
use crate::State;
use reqwest::Method;
@@ -13,7 +14,6 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::fs;
#[derive(Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
@@ -263,7 +263,7 @@ pub async fn generate_pack_from_file(
path: PathBuf,
profile: PathBuf,
) -> crate::Result<CreatePackDescription> {
let file = fs::read(&path).await?;
let file = io::read(&path).await?;
Ok(CreatePackDescription {
file: bytes::Bytes::from(file),
icon: None,

View File

@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use uuid::Uuid;
use crate::state::MinecraftChild;
use crate::{state::MinecraftChild, util::io::IOError};
pub use crate::{
state::{
Hooks, JavaSettings, MemorySettings, Profile, Settings, WindowSize,
@@ -121,7 +121,13 @@ pub async fn wait_for_by_uuid(uuid: &Uuid) -> crate::Result<()> {
// Kill a running child process directly, and wait for it to be killed
#[tracing::instrument(skip(running))]
pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> {
running.current_child.write().await.kill().await?;
running
.current_child
.write()
.await
.kill()
.await
.map_err(IOError::from)?;
wait_for(running).await
}

View File

@@ -9,6 +9,7 @@ use crate::pack::install_from::{
use crate::prelude::JavaVersion;
use crate::state::ProjectMetadata;
use crate::util::io::{self, IOError};
use crate::{
auth::{self, refresh},
event::{emit::emit_profile, ProfilePayloadType},
@@ -27,11 +28,7 @@ use std::{
sync::Arc,
};
use tokio::io::AsyncReadExt;
use tokio::{
fs::{self, File},
process::Command,
sync::RwLock,
};
use tokio::{fs::File, process::Command, sync::RwLock};
/// Remove a profile
#[tracing::instrument]
@@ -110,8 +107,8 @@ pub async fn edit_icon(
) -> crate::Result<()> {
let state = State::get().await?;
if let Some(icon) = icon_path {
let bytes = tokio::fs::read(icon).await?;
let res = if let Some(icon) = icon_path {
let bytes = io::read(icon).await?;
let mut profiles = state.profiles.write().await;
@@ -133,8 +130,6 @@ pub async fn edit_icon(
ProfilePayloadType::Edited,
)
.await?;
State::sync().await?;
Ok(())
}
None => Err(crate::ErrorKind::UnmanagedProfileError(
@@ -151,7 +146,9 @@ pub async fn edit_icon(
State::sync().await?;
Ok(())
}
};
State::sync().await?;
res
}
// Gets the optimal JRE key for the given profile, using Daedalus
@@ -416,7 +413,7 @@ pub async fn add_project_from_path(
project_type: Option<String>,
) -> crate::Result<PathBuf> {
if let Some(profile) = get(profile_path, None).await? {
let file = fs::read(path).await?;
let file = io::read(path).await?;
let file_name = path
.file_name()
.unwrap_or_default()
@@ -525,7 +522,9 @@ pub async fn export_mrpack(
let profile_base_path = &profile.path;
let mut file = File::create(export_path).await?;
let mut file = File::create(&export_path)
.await
.map_err(|e| IOError::with_path(e, &export_path))?;
let mut writer = ZipFileWriter::new(&mut file);
// Create mrpack json configuration file
@@ -592,9 +591,13 @@ pub async fn export_mrpack(
// File is not in the config file, add it to the .mrpack zip
if path.is_file() {
let mut file = File::open(&path).await?;
let mut file = File::open(&path)
.await
.map_err(|e| IOError::with_path(e, &path))?;
let mut data = Vec::new();
file.read_to_end(&mut data).await?;
file.read_to_end(&mut data)
.await
.map_err(|e| IOError::with_path(e, &path))?;
let builder = ZipEntryBuilder::new(
format!("overrides/{relative_path}"),
Compression::Deflate,
@@ -639,13 +642,21 @@ pub async fn get_potential_override_folders(
let mrpack_files = get_modrinth_pack_list(&mrpack);
let mut path_list: Vec<PathBuf> = Vec::new();
let mut read_dir = fs::read_dir(&profile_path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let mut read_dir = io::read_dir(&profile_path).await?;
while let Some(entry) = read_dir
.next_entry()
.await
.map_err(|e| IOError::with_path(e, &profile_path))?
{
let path: PathBuf = entry.path();
if path.is_dir() {
// Two layers of files/folders if its a folder
let mut read_dir = fs::read_dir(&path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let mut read_dir = io::read_dir(&path).await?;
while let Some(entry) = read_dir
.next_entry()
.await
.map_err(|e| IOError::with_path(e, &profile_path))?
{
let path: PathBuf = entry.path();
let name = path.strip_prefix(&profile_path)?.to_path_buf();
if !mrpack_files.contains(&name.to_string_lossy().to_string()) {
@@ -712,9 +723,11 @@ pub async fn run_credentials(
let result = Command::new(command)
.args(&cmd.collect::<Vec<&str>>())
.current_dir(path)
.spawn()?
.spawn()
.map_err(|e| IOError::with_path(e, path))?
.wait()
.await?;
.await
.map_err(IOError::from)?;
if !result.success() {
return Err(crate::ErrorKind::LauncherError(format!(
@@ -925,8 +938,12 @@ pub async fn build_folder(
path: &Path,
path_list: &mut Vec<PathBuf>,
) -> crate::Result<()> {
let mut read_dir = fs::read_dir(path).await?;
while let Some(entry) = read_dir.next_entry().await? {
let mut read_dir = io::read_dir(path).await?;
while let Some(entry) = read_dir
.next_entry()
.await
.map_err(|e| IOError::with_path(e, path))?
{
let path = entry.path();
if path.is_dir() {
build_folder(&path, path_list).await?;

View File

@@ -1,5 +1,6 @@
//! Theseus profile management interface
use crate::state::LinkedData;
use crate::util::io::{self, canonicalize};
use crate::{
event::{emit::emit_profile, ProfilePayloadType},
prelude::ModLoader,
@@ -9,11 +10,9 @@ pub use crate::{
State,
};
use daedalus::modded::LoaderVersion;
use dunce::canonicalize;
use futures::prelude::*;
use std::path::PathBuf;
use tokio::fs;
use tokio_stream::wrappers::ReadDirStream;
use tracing::{info, trace};
use uuid::Uuid;
@@ -48,7 +47,7 @@ pub async fn profile_create(
.into());
}
if ReadDirStream::new(fs::read_dir(&path).await?)
if ReadDirStream::new(io::read_dir(&path).await?)
.next()
.await
.is_some()
@@ -56,7 +55,7 @@ pub async fn profile_create(
return Err(ProfileCreationError::NotEmptyFolder.into());
}
} else {
fs::create_dir_all(&path).await?;
io::create_dir_all(&path).await?;
}
info!(
@@ -80,7 +79,7 @@ pub async fn profile_create(
Profile::new(uuid, name, game_version, path.clone()).await?;
let result = async {
if let Some(ref icon) = icon {
let bytes = tokio::fs::read(icon).await?;
let bytes = io::read(icon).await?;
profile
.set_icon(
&state.directories.caches_dir(),

View File

@@ -1,5 +1,5 @@
//! Theseus error type
use crate::profile_create;
use crate::{profile_create, util};
use tracing_error::InstrumentError;
#[derive(thiserror::Error, Debug)]
@@ -29,7 +29,7 @@ pub enum ErrorKind {
AuthTaskError(#[from] crate::state::AuthTaskError),
#[error("I/O error: {0}")]
IOError(#[from] std::io::Error),
IOError(#[from] util::io::IOError),
#[error("Error launching Minecraft: {0}")]
LauncherError(String),

View File

@@ -3,7 +3,7 @@
use super::{auth::Credentials, parse_rule};
use crate::{
state::{MemorySettings, WindowSize},
util::platform::classpath_separator,
util::{io::IOError, platform::classpath_separator},
};
use daedalus::{
get_path_from_artifact,
@@ -393,7 +393,8 @@ pub async fn get_processor_main_class(
path: String,
) -> crate::Result<Option<String>> {
let main_class = tokio::task::spawn_blocking(move || {
let zipfile = std::fs::File::open(&path)?;
let zipfile = std::fs::File::open(&path)
.map_err(|e| IOError::with_path(e, &path))?;
let mut archive = zip::ZipArchive::new(zipfile).map_err(|_| {
crate::ErrorKind::LauncherError(format!(
"Cannot read processor at {}",
@@ -413,7 +414,7 @@ pub async fn get_processor_main_class(
let reader = BufReader::new(file);
for line in reader.lines() {
let mut line = line?;
let mut line = line.map_err(IOError::from)?;
line.retain(|c| !c.is_whitespace());
if line.starts_with("Main-Class:") {

View File

@@ -6,7 +6,7 @@ use crate::{
LoadingBarId,
},
state::State,
util::{fetch::*, platform::OsExt},
util::{fetch::*, io, platform::OsExt},
};
use daedalus::{
self as d,
@@ -17,7 +17,7 @@ use daedalus::{
modded::LoaderVersion,
};
use futures::prelude::*;
use tokio::{fs, sync::OnceCell};
use tokio::sync::OnceCell;
#[tracing::instrument(skip(st, version))]
pub async fn download_minecraft(
@@ -71,7 +71,7 @@ pub async fn download_version_info(
.join(format!("{version_id}.json"));
let res = if path.exists() && !force.unwrap_or(false) {
fs::read(path)
io::read(path)
.err_into::<crate::Error>()
.await
.and_then(|ref it| Ok(serde_json::from_slice(it)?))
@@ -152,7 +152,7 @@ pub async fn download_assets_index(
.join(format!("{}.json", &version.asset_index.id));
let res = if path.exists() {
fs::read(path)
io::read(path)
.err_into::<crate::Error>()
.await
.and_then(|ref it| Ok(serde_json::from_slice(it)?))
@@ -245,8 +245,8 @@ pub async fn download_libraries(
tracing::debug!("Loading libraries");
tokio::try_join! {
fs::create_dir_all(st.directories.libraries_dir()),
fs::create_dir_all(st.directories.version_natives_dir(version))
io::create_dir_all(st.directories.libraries_dir()),
io::create_dir_all(st.directories.version_natives_dir(version))
}?;
let num_files = libraries.len();
loading_try_for_each_concurrent(

View File

@@ -1,9 +1,10 @@
//! Logic for launching Minecraft
use crate::event::emit::{emit_loading, init_or_edit_loading};
use crate::event::{LoadingBarId, LoadingBarType};
use crate::jre::{JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY};
use crate::jre::{self, JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY};
use crate::prelude::JavaVersion;
use crate::state::ProfileInstallStage;
use crate::util::io;
use crate::EventState;
use crate::{
process,
@@ -13,10 +14,8 @@ use crate::{
use chrono::Utc;
use daedalus as d;
use daedalus::minecraft::VersionInfo;
use dunce::canonicalize;
use st::Profile;
use std::collections::HashMap;
use std::fs;
use std::{process::Stdio, sync::Arc};
use tokio::process::Command;
use uuid::Uuid;
@@ -125,7 +124,7 @@ pub async fn install_minecraft(
State::sync().await?;
let state = State::get().await?;
let instance_path = &canonicalize(&profile.path)?;
let instance_path = &io::canonicalize(&profile.path)?;
let metadata = state.metadata.read().await;
let version = metadata
@@ -160,7 +159,7 @@ pub async fn install_minecraft(
.await?
.ok_or_else(|| {
crate::ErrorKind::OtherError(
"No available java installation".to_string(),
"Missing correct java installation".to_string(),
)
})?;
@@ -282,7 +281,7 @@ pub async fn install_minecraft(
Ok(())
}
#[tracing::instrument]
#[tracing::instrument(skip_all)]
#[theseus_macros::debug_pin]
#[allow(clippy::too_many_arguments)]
pub async fn launch_minecraft(
@@ -310,7 +309,7 @@ pub async fn launch_minecraft(
let state = State::get().await?;
let metadata = state.metadata.read().await;
let instance_path = &canonicalize(&profile.path)?;
let instance_path = &io::canonicalize(&profile.path)?;
let version = metadata
.minecraft
@@ -343,10 +342,20 @@ pub async fn launch_minecraft(
.await?
.ok_or_else(|| {
crate::ErrorKind::LauncherError(
"No available java installation".to_string(),
"Missing correct java installation".to_string(),
)
})?;
// Test jre version
let java_version = jre::check_jre(java_version.path.clone().into())
.await?
.ok_or_else(|| {
crate::ErrorKind::LauncherError(format!(
"Java path invalid or non-functional: {}",
java_version.path
))
})?;
let client_path = state
.directories
.version_dir(&version_jar)
@@ -433,7 +442,7 @@ pub async fn launch_minecraft(
.profile_logs_dir(profile.uuid)
.join(&datetime_string)
};
fs::create_dir_all(&logs_dir)?;
io::create_dir_all(&logs_dir).await?;
let stdout_log_path = logs_dir.join("stdout.log");

View File

@@ -12,6 +12,7 @@ use tracing::error;
use crate::event::emit::emit_process;
use crate::event::ProcessPayloadType;
use crate::util::io::IOError;
use crate::EventState;
use tokio::task::JoinHandle;
use uuid::Uuid;
@@ -39,7 +40,15 @@ impl Children {
// The threads for stdout and stderr are spawned here
// Unlike a Hashmap's 'insert', this directly returns the reference to the MinecraftChild rather than any previously stored MinecraftChild that may exist
#[tracing::instrument(skip(self))]
#[tracing::instrument(skip(
self,
uuid,
log_path,
mc_command,
post_command,
censor_strings
))]
#[tracing::instrument(level = "trace", skip(self))]
#[theseus_macros::debug_pin]
pub async fn insert_process(
&mut self,
@@ -51,7 +60,7 @@ impl Children {
censor_strings: HashMap<String, String>,
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
// Takes the first element of the commands vector and spawns it
let mut child = mc_command.spawn()?;
let mut child = mc_command.spawn().map_err(IOError::from)?;
// Create std watcher threads for stdout and stderr
let shared_output =
@@ -125,7 +134,12 @@ impl Children {
// Wait on current Minecraft Child
let mut mc_exit_status;
loop {
if let Some(t) = current_child.write().await.try_wait()? {
if let Some(t) = current_child
.write()
.await
.try_wait()
.map_err(IOError::from)?
{
mc_exit_status = t;
break;
}
@@ -156,7 +170,7 @@ impl Children {
if let Some(mut m_command) = post_command {
{
let mut current_child = current_child.write().await;
let new_child = m_command.spawn()?;
let new_child = m_command.spawn().map_err(IOError::from)?;
current_pid = new_child.id().ok_or_else(|| {
crate::ErrorKind::LauncherError(
"Process immediately failed, could not get PID"
@@ -174,7 +188,12 @@ impl Children {
.await?;
loop {
if let Some(t) = current_child.write().await.try_wait()? {
if let Some(t) = current_child
.write()
.await
.try_wait()
.map_err(IOError::from)?
{
mc_exit_status = t;
break;
}
@@ -210,7 +229,12 @@ impl Children {
) -> crate::Result<Option<std::process::ExitStatus>> {
if let Some(child) = self.get(uuid) {
let child = child.write().await;
let status = child.current_child.write().await.try_wait()?;
let status = child
.current_child
.write()
.await
.try_wait()
.map_err(IOError::from)?;
Ok(status)
} else {
Ok(None)
@@ -224,7 +248,14 @@ impl Children {
if let Some(child) = self.get(&key) {
let child = child.clone();
let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() {
if child
.current_child
.write()
.await
.try_wait()
.map_err(IOError::from)?
.is_none()
{
keys.push(key);
}
}
@@ -258,7 +289,14 @@ impl Children {
if let Some(child) = self.get(&key) {
let child = child.clone();
let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() {
if child
.current_child
.write()
.await
.try_wait()
.map_err(IOError::from)?
.is_none()
{
profiles.push(child.profile_path.clone());
}
}
@@ -274,7 +312,14 @@ impl Children {
if let Some(child) = self.get(&key) {
let child = child.clone();
let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() {
if child
.current_child
.write()
.await
.try_wait()
.map_err(IOError::from)?
.is_none()
{
if let Some(prof) = crate::api::profile::get(
&child.profile_path.clone(),
None,
@@ -312,7 +357,11 @@ impl SharedOutput {
) -> crate::Result<Self> {
Ok(SharedOutput {
output: Arc::new(RwLock::new(String::new())),
log_file: Arc::new(RwLock::new(File::create(log_file_path).await?)),
log_file: Arc::new(RwLock::new(
File::create(log_file_path)
.await
.map_err(|e| IOError::with_path(e, log_file_path))?,
)),
censor_strings,
})
}
@@ -330,7 +379,12 @@ impl SharedOutput {
let mut buf_reader = BufReader::new(child_stdout);
let mut line = String::new();
while buf_reader.read_line(&mut line).await? > 0 {
while buf_reader
.read_line(&mut line)
.await
.map_err(IOError::from)?
> 0
{
let val_line = self.censor_log(line.clone());
{
@@ -339,7 +393,10 @@ impl SharedOutput {
}
{
let mut log_file = self.log_file.write().await;
log_file.write_all(val_line.as_bytes()).await?;
log_file
.write_all(val_line.as_bytes())
.await
.map_err(IOError::from)?;
}
line.clear();
@@ -354,7 +411,12 @@ impl SharedOutput {
let mut buf_reader = BufReader::new(child_stderr);
let mut line = String::new();
while buf_reader.read_line(&mut line).await? > 0 {
while buf_reader
.read_line(&mut line)
.await
.map_err(IOError::from)?
> 0
{
let val_line = self.censor_log(line.clone());
{
@@ -363,7 +425,10 @@ impl SharedOutput {
}
{
let mut log_file = self.log_file.write().await;
log_file.write_all(val_line.as_bytes()).await?;
log_file
.write_all(val_line.as_bytes())
.await
.map_err(IOError::from)?;
}
line.clear();

View File

@@ -9,11 +9,11 @@ use crate::state::{ModrinthVersion, ProjectMetadata, ProjectType};
use crate::util::fetch::{
fetch, fetch_json, write, write_cached_icon, IoSemaphore,
};
use crate::util::io::{self, IOError};
use crate::State;
use chrono::{DateTime, Utc};
use daedalus::get_hash;
use daedalus::modded::LoaderVersion;
use dunce::canonicalize;
use futures::prelude::*;
use notify::{RecommendedWatcher, RecursiveMode};
use notify_debouncer_mini::Debouncer;
@@ -24,7 +24,6 @@ use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use tokio::fs;
use uuid::Uuid;
const PROFILE_JSON_PATH: &str = "profile.json";
@@ -161,7 +160,7 @@ impl Profile {
Ok(Self {
uuid,
install_stage: ProfileInstallStage::NotInstalled,
path: canonicalize(path)?,
path: io::canonicalize(path)?,
metadata: ProfileMetadata {
name,
icon: None,
@@ -274,8 +273,11 @@ impl Profile {
let mut read_paths = |path: &str| {
let new_path = self.path.join(path);
if new_path.exists() {
for path in std::fs::read_dir(self.path.join(path))? {
let path = path?.path();
let path = self.path.join(path);
for path in std::fs::read_dir(&path)
.map_err(|e| IOError::with_path(e, &path))?
{
let path = path.map_err(IOError::from)?.path();
if path.is_file() {
files.push(path);
}
@@ -305,7 +307,7 @@ impl Profile {
) -> crate::Result<()> {
let path = profile_path.join(path);
fs::create_dir_all(&path).await?;
io::create_dir_all(&path).await?;
watcher
.watcher()
@@ -476,7 +478,7 @@ impl Profile {
project.disabled = true;
}
fs::rename(path, &new_path).await?;
io::rename(&path, &new_path).await?;
let mut profiles = state.profiles.write().await;
if let Some(profile) = profiles.0.get_mut(&self.path) {
@@ -501,7 +503,7 @@ impl Profile {
) -> crate::Result<()> {
let state = State::get().await?;
if self.projects.contains_key(path) {
fs::remove_file(path).await?;
io::remove_file(path).await?;
if !dont_remove_arr.unwrap_or(false) {
let mut profiles = state.profiles.write().await;
@@ -530,9 +532,11 @@ impl Profiles {
file_watcher: &mut Debouncer<RecommendedWatcher>,
) -> crate::Result<Self> {
let mut profiles = HashMap::new();
fs::create_dir_all(dirs.profiles_dir()).await?;
let mut entries = fs::read_dir(dirs.profiles_dir()).await?;
while let Some(entry) = entries.next_entry().await? {
io::create_dir_all(&dirs.profiles_dir()).await?;
let mut entries = io::read_dir(&dirs.profiles_dir()).await?;
while let Some(entry) =
entries.next_entry().await.map_err(IOError::from)?
{
let path = entry.path();
if path.is_dir() {
let prof = match Self::read_profile_from_dir(&path).await {
@@ -545,7 +549,7 @@ impl Profiles {
}
};
if let Some(profile) = prof {
let path = canonicalize(path)?;
let path = io::canonicalize(path)?;
Profile::watch_fs(&path, file_watcher).await?;
profiles.insert(path, profile);
}
@@ -629,7 +633,7 @@ impl Profiles {
Profile::watch_fs(&profile.path, &mut file_watcher).await?;
self.0.insert(
canonicalize(&profile.path)?
io::canonicalize(&profile.path)?
.to_str()
.ok_or(
crate::ErrorKind::UTFError(profile.path.clone()).as_error(),
@@ -645,12 +649,13 @@ impl Profiles {
&mut self,
path: &Path,
) -> crate::Result<Option<Profile>> {
let path =
PathBuf::from(&canonicalize(path)?.to_string_lossy().to_string());
let path = PathBuf::from(
&io::canonicalize(path)?.to_string_lossy().to_string(),
);
let profile = self.0.remove(&path);
if path.exists() {
fs::remove_dir_all(path).await?;
io::remove_dir_all(&path).await?;
}
Ok(profile)
@@ -666,7 +671,7 @@ impl Profiles {
let json_path = Path::new(&path.to_string_lossy().to_string())
.join(PROFILE_JSON_PATH);
fs::write(json_path, json).await?;
io::write(&json_path, &json).await?;
Ok::<_, crate::Error>(())
})
.await?;
@@ -675,7 +680,7 @@ impl Profiles {
}
async fn read_profile_from_dir(path: &Path) -> crate::Result<Profile> {
let json = fs::read(path.join(PROFILE_JSON_PATH)).await?;
let json = io::read(&path.join(PROFILE_JSON_PATH)).await?;
let mut profile = serde_json::from_slice::<Profile>(&json)?;
profile.path = PathBuf::from(path);
Ok(profile)

View File

@@ -5,6 +5,7 @@ use crate::state::Profile;
use crate::util::fetch::{
fetch_json, write_cached_icon, FetchSemaphore, IoSemaphore,
};
use crate::util::io::IOError;
use async_zip::tokio::read::fs::ZipFileReader;
use chrono::{DateTime, Utc};
use reqwest::Method;
@@ -252,7 +253,7 @@ async fn read_icon_from_file(
Ok(None)
}
#[tracing::instrument(skip(profile, io_semaphore, fetch_semaphore))]
#[tracing::instrument(skip(paths, profile, io_semaphore, fetch_semaphore))]
#[theseus_macros::debug_pin]
pub async fn infer_data_from_files(
profile: Profile,
@@ -265,10 +266,12 @@ pub async fn infer_data_from_files(
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
for path in paths {
let mut file = tokio::fs::File::open(path.clone()).await?;
let mut file = tokio::fs::File::open(path.clone())
.await
.map_err(|e| IOError::with_path(e, &path))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?;
file.read_to_end(&mut buffer).await.map_err(IOError::from)?;
let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
file_path_hashes.insert(hash, path.clone());

View File

@@ -9,10 +9,9 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::time;
use tokio::sync::{RwLock, Semaphore};
use tokio::{
fs::{self, File},
io::AsyncWriteExt,
};
use tokio::{fs::File, io::AsyncWriteExt};
use super::io::{self, IOError};
#[derive(Debug)]
pub struct IoSemaphore(pub RwLock<Semaphore>);
@@ -193,7 +192,7 @@ where
let io_semaphore = semaphore.0.read().await;
let _permit = io_semaphore.acquire().await?;
let json = fs::read(path).await?;
let json = io::read(path).await?;
let json = serde_json::from_slice::<T>(&json)?;
Ok(json)
@@ -209,11 +208,15 @@ pub async fn write<'a>(
let _permit = io_semaphore.acquire().await?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?;
io::create_dir_all(parent).await?;
}
let mut file = File::create(path).await?;
file.write_all(bytes).await?;
let mut file = File::create(path)
.await
.map_err(|e| IOError::with_path(e, path))?;
file.write_all(bytes)
.await
.map_err(|e| IOError::with_path(e, path))?;
tracing::trace!("Done writing file {}", path.display());
Ok(())
}
@@ -235,7 +238,7 @@ pub async fn write_cached_icon(
write(&path, &bytes, semaphore).await?;
let path = dunce::canonicalize(path)?;
let path = io::canonicalize(path)?;
Ok(path)
}

149
theseus/src/util/io.rs Normal file
View File

@@ -0,0 +1,149 @@
// IO error
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error.
#[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(),
}
}
}
// dunce canonicalize
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(),
})
}
// read_dir
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(),
})
}
// create_dir_all
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(),
})
}
// remove_dir_all
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(),
})
}
// read_to_string
pub async fn read_to_string(
path: impl AsRef<std::path::Path>,
) -> Result<String, IOError> {
let path = path.as_ref();
tokio::fs::read_to_string(path)
.await
.map_err(|e| IOError::IOPathError {
source: e,
path: path.to_string_lossy().to_string(),
})
}
// read
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(),
})
}
// write
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 {
source: e,
path: path.to_string_lossy().to_string(),
})
}
// rename
pub async fn rename(
from: impl AsRef<std::path::Path>,
to: impl AsRef<std::path::Path>,
) -> Result<(), IOError> {
let from = from.as_ref();
let to = to.as_ref();
tokio::fs::rename(from, to)
.await
.map_err(|e| IOError::IOPathError {
source: e,
path: from.to_string_lossy().to_string(),
})
}
// remove file
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(),
})
}

View File

@@ -1,4 +1,4 @@
use dunce::canonicalize;
use super::io;
use futures::prelude::*;
use serde::{Deserialize, Serialize};
use std::env;
@@ -270,7 +270,7 @@ pub async fn check_java_at_filepaths(
pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
// Attempt to canonicalize the potential java filepath
// If it fails, this path does not exist and None is returned (no Java here)
let Ok(path) = canonicalize(path) else { return None };
let Ok(path) = io::canonicalize(path) else { return None };
// Checks for existence of Java at this filepath
// Adds JAVA_BIN to the end of the path if it is not already there

View File

@@ -1,5 +1,6 @@
//! Theseus utility functions
pub mod fetch;
pub mod io;
pub mod jre;
pub mod platform;