Fix issues relating to app directory changes (#5826)
* Fix canceling app directory change * Improve directory moving error messages * tombi
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -10155,6 +10155,7 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"enumset",
|
"enumset",
|
||||||
|
"eyre",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fs4",
|
"fs4",
|
||||||
"futures",
|
"futures",
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"] }
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
|||||||
Reference in New Issue
Block a user