Fix issues relating to app directory changes (#5826)

* Fix canceling app directory change

* Improve directory moving error messages

* tombi
This commit is contained in:
aecsocket
2026-04-17 15:00:08 +01:00
committed by GitHub
parent 7c642f7078
commit 9483656881
7 changed files with 87 additions and 27 deletions

1
Cargo.lock generated
View File

@@ -10155,6 +10155,7 @@ dependencies = [
"either", "either",
"encoding_rs", "encoding_rs",
"enumset", "enumset",
"eyre",
"flate2", "flate2",
"fs4", "fs4",
"futures", "futures",

View File

@@ -1,4 +1,5 @@
use crate::api::Result; use crate::api::Result;
use tauri::Runtime;
use theseus::prelude::*; use theseus::prelude::*;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> { pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
@@ -28,10 +29,10 @@ pub async fn settings_set(settings: Settings) -> Result<()> {
} }
#[tauri::command] #[tauri::command]
pub async fn cancel_directory_change() -> Result<()> { pub async fn cancel_directory_change<R: Runtime>(
let state = State::get().await?; app: tauri::AppHandle<R>,
let identifier = &state.app_identifier; ) -> Result<()> {
let identifier = &app.config().identifier;
settings::cancel_directory_change(identifier).await?; settings::cancel_directory_change(identifier).await?;
Ok(()) Ok(())
} }

View File

@@ -37,6 +37,7 @@ dunce = { workspace = true }
either = { workspace = true } either = { workspace = true }
encoding_rs = { workspace = true } encoding_rs = { workspace = true }
enumset = { workspace = true } enumset = { workspace = true }
eyre = { workspace = true }
flate2 = { workspace = true } flate2 = { workspace = true }
fs4 = { workspace = true, features = ["tokio"] } fs4 = { workspace = true, features = ["tokio"] }
futures = { workspace = true, features = ["alloc", "async-await"] } futures = { workspace = true, features = ["alloc", "async-await"] }

View File

@@ -16,6 +16,9 @@ pub struct LabrinthError {
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ErrorKind { pub enum ErrorKind {
#[error("{0:?}")]
Any(eyre::Report),
#[error("Filesystem error: {0}")] #[error("Filesystem error: {0}")]
FSError(String), FSError(String),
@@ -214,6 +217,17 @@ impl<E: Into<ErrorKind>> From<E> for Error {
} }
} }
impl From<eyre::Report> for Error {
fn from(value: eyre::Report) -> Self {
let error = Arc::new(ErrorKind::Any(value));
Self {
raw: error.clone(),
source: error.in_current_span(),
}
}
}
impl ErrorKind { impl ErrorKind {
pub fn as_error(self) -> Error { pub fn as_error(self) -> Error {
self.into() self.into()

View File

@@ -364,10 +364,9 @@ impl DirectoryInfo {
.map_err(|e| { .map_err(|e| {
crate::Error::from(crate::ErrorKind::DirectoryMoveError( crate::Error::from(crate::ErrorKind::DirectoryMoveError(
format!( format!(
"Failed to move directory from {} to {}: {}", "Failed to move directory from {} to {}: {e:?}",
x.old.display(), x.old.display(),
x.new.display(), x.new.display(),
e
), ),
)) ))
})?; })?;
@@ -421,7 +420,7 @@ impl DirectoryInfo {
io_semaphore, io_semaphore,
) )
.await.map_err(|e| { crate::Error::from( .await.map_err(|e| { crate::Error::from(
crate::ErrorKind::DirectoryMoveError(format!("Failed to move directory from {} to {}: {}", x.old.display(), x.new.display(), e))) crate::ErrorKind::DirectoryMoveError(format!("Failed to move directory from {} to {}: {e:?}", x.old.display(), x.new.display())))
})?; })?;
let _ = emit_loading( let _ = emit_loading(

View File

@@ -74,9 +74,12 @@ pub struct State {
/// Process manager /// Process manager
pub process_manager: ProcessManager, pub process_manager: ProcessManager,
/// App identifier string (like com.modrinth.ModrinthApp) // NOTE: we explicitly must NOT store the app identifier in the state object,
pub app_identifier: String, // because creating the state object is fallible (e.g. database missing),
// but we rely on the app identifier to create the state (data dir).
//
// /// App identifier string (like com.modrinth.ModrinthApp)
// pub app_identifier: String,
/// Friends socket /// Friends socket
pub friends_socket: FriendsSocket, pub friends_socket: FriendsSocket,
@@ -188,7 +191,7 @@ impl State {
friends_socket, friends_socket,
pool, pool,
file_watcher, file_watcher,
app_identifier, // app_identifier,
})) }))
} }
} }

View File

@@ -1,6 +1,7 @@
// 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 eyre::{Context, ContextCompat, Result, eyre};
use std::{ use std::{
io::{ErrorKind, Write}, io::{ErrorKind, Write},
path::Path, path::Path,
@@ -181,17 +182,34 @@ fn sync_write(
std::io::Result::Ok(()) std::io::Result::Ok(())
} }
pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result<bool, IOError> { pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result<bool> {
#[cfg(unix)] #[cfg(unix)]
{ {
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
Ok(old_dir.metadata()?.dev() == new_dir.metadata()?.dev())
use eyre::eyre;
// we need to use `symlink_metadata` instead of `metadata`, because
// if this file is a symlink, we need to query the symlink file itself,
// rather than the target.
// downloaded JREs use symlinks to point to certain stuff like LICENSE
// files.
// this fixes moving JRE dirs.
let old_meta = std::fs::symlink_metadata(old_dir)
.wrap_err_with(|| eyre!("getting meta of old dir {old_dir:?}"))?;
let new_meta = std::fs::symlink_metadata(new_dir)
.wrap_err_with(|| eyre!("getting meta of new dir {new_dir:?}"))?;
Ok(old_meta.dev() == new_meta.dev())
} }
#[cfg(windows)] #[cfg(windows)]
{ {
let old_dir = canonicalize(old_dir)?; let old_dir = canonicalize(old_dir)
let new_dir = canonicalize(new_dir)?; .wrap_err_with(|| eyre!("canonicalizing {old_dir:?}"))?;
let new_dir = canonicalize(new_dir)
.wrap_err_with(|| eyre!("canonicalizing {new_dir:?}"))?;
let old_component = old_dir.components().next(); let old_component = old_dir.components().next();
let new_component = new_dir.components().next(); let new_component = new_dir.components().next();
@@ -209,39 +227,62 @@ pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result<bool, IOError> {
pub async fn rename_or_move( pub async fn rename_or_move(
from: impl AsRef<std::path::Path>, from: impl AsRef<std::path::Path>,
to: impl AsRef<std::path::Path>, to: impl AsRef<std::path::Path>,
) -> Result<(), IOError> { ) -> Result<()> {
let from = from.as_ref(); let from = from.as_ref();
let to = to.as_ref(); let to = to.as_ref();
if to let to_parent = to
.parent() .parent()
.map_or(Ok(false), |to_dir| is_same_disk(from, to_dir))? .wrap_err_with(|| eyre!("getting parent of `to` dir {to:?}"))?;
{ let same_disk = is_same_disk(from, to_parent).wrap_err_with(|| {
eyre!("checking if `to_parent` ({to_parent:?}) and `from` ({from:?}) are on the same disk")
})?;
if same_disk {
tokio::fs::rename(from, to) tokio::fs::rename(from, to)
.await .await
.map_err(|e| IOError::IOPathError { .map_err(|e| IOError::IOPathError {
source: e, source: e,
path: from.to_string_lossy().to_string(), path: from.to_string_lossy().to_string(),
}) })
.wrap_err_with(|| eyre!("moving {from:?} to {to:?} on same disk"))
} else { } else {
move_recursive(from, to).await move_recursive(from, to).await.with_context(|| {
eyre!("moving {from:?} to {to:?} on different disks")
})
} }
} }
#[async_recursion::async_recursion] #[async_recursion::async_recursion]
async fn move_recursive(from: &Path, to: &Path) -> Result<(), IOError> { async fn move_recursive(from: &Path, to: &Path) -> Result<()> {
if from.is_file() { if from.is_file() {
copy(from, to).await?; copy(from, to)
remove_file(from).await?; .await
.wrap_err_with(|| eyre!("copying {from:?} to {to:?}"))?;
remove_file(from).await.wrap_err_with(|| {
eyre!("removing {from:?} after copying to {to:?}")
})?;
return Ok(()); return Ok(());
} }
create_dir(to).await?; create_dir(to)
.await
.wrap_err_with(|| eyre!("creating dir for {to:?}"))?;
let mut dir = read_dir(from).await?; let mut dir = read_dir(from)
while let Some(entry) = dir.next_entry().await? { .await
.wrap_err_with(|| eyre!("reading dir {from:?}"))?;
while let Some(entry) = dir
.next_entry()
.await
.wrap_err_with(|| eyre!("reading dir entry in {from:?}"))?
{
let new_path = to.join(entry.file_name()); let new_path = to.join(entry.file_name());
move_recursive(&entry.path(), &new_path).await?; move_recursive(&entry.path(), &new_path)
.await
.with_context(|| {
eyre!("moving {:?} to {new_path:?}", entry.path())
})?;
} }
Ok(()) Ok(())