ability to move a folder with resource in the resource manager

This commit is contained in:
Dmitry Stepanov
2025-07-27 11:12:56 +03:00
parent 1a0d9a5e33
commit aea76d9208
2 changed files with 187 additions and 66 deletions
+8 -66
View File
@@ -56,7 +56,6 @@ use crate::{
wrap_panel::WrapPanelBuilder,
HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface, VerticalAlignment,
},
walkdir,
},
load_image,
message::MessageSender,
@@ -241,69 +240,6 @@ fn try_move_resource(
}
}
fn try_move_folder(src_dir: &Path, dest_dir: &Path, resource_manager: &ResourceManager) {
if dest_dir.starts_with(src_dir) {
// Trying to drop a folder into its own subfolder
return;
}
// Early validation to prevent error spam when trying to move a folder out of the
// assets directory.
if !is_path_in_registry(dest_dir, resource_manager) {
return;
}
// At this point we have a folder dropped on some other folder. In this case
// we need to move all the assets from the dropped folder to a new subfolder (with the same
// name as the dropped folder) of the other folder first. After that we can move the rest
// of the files and finally delete the dropped folder.
let mut what_where_stack = vec![(src_dir.to_path_buf(), dest_dir.to_path_buf())];
let mut any_error = false;
while let Some((src_dir, target_dir)) = what_where_stack.pop() {
let src_dir_name = some_or_continue!(src_dir.file_name());
let target_sub_dir = target_dir.join(src_dir_name);
if !target_sub_dir.exists() {
if let Err(err) = std::fs::create_dir(&target_sub_dir) {
err!(
"Unable to create {} directory. Reason: {}",
target_sub_dir.display(),
err
);
continue;
}
}
let target_sub_dir_normalized = ok_or_continue!(make_relative_path(&target_sub_dir));
for entry in walkdir::WalkDir::new(&src_dir)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.is_file() {
let ext = some_or_continue!(path.extension());
let file_name = some_or_continue!(path.file_name());
if is_supported_resource(ext, resource_manager) {
let dest_path = target_sub_dir_normalized.join(file_name);
if !try_move_resource(path, &dest_path, resource_manager) {
any_error = true;
}
}
} else if entry.path().is_dir() && entry.path() != src_dir {
// Sub-folders will be processed after all assets from current dir
// were moved.
what_where_stack.push((entry.path().to_path_buf(), target_sub_dir.clone()));
}
}
}
if !any_error {
Log::verify(std::fs::remove_dir_all(src_dir));
}
}
impl AssetBrowser {
pub const ADD_ASSET_NORMAL_BRUSH: &'static str = "AssetBrowser.AddAssetNormalBrush";
pub const ADD_ASSET_HOVER_BRUSH: &'static str = "AssetBrowser.AddAssetHoverBrush";
@@ -859,7 +795,11 @@ impl AssetBrowser {
);
self.schedule_refresh();
} else if src_item_path.is_dir() {
try_move_folder(src_item_path, dest_dir, &engine.resource_manager);
Log::verify(block_on(engine.resource_manager.try_move_folder(
src_item_path,
dest_dir,
true,
)));
self.schedule_refresh();
}
}
@@ -1078,7 +1018,9 @@ impl AssetBrowser {
try_move_resource(&item.path, &dest_dir.join(file_name), resource_manager);
}
} else if src_dir != Path::new("") {
try_move_folder(src_dir, dest_dir, resource_manager);
Log::verify(block_on(
resource_manager.try_move_folder(src_dir, dest_dir, true),
));
self.schedule_refresh();
}
}
+179
View File
@@ -48,6 +48,7 @@ use crate::{
Resource, TypedResourceData, UntypedResource,
};
use fxhash::FxHashSet;
use fyrox_core::{make_relative_path, some_or_continue, some_or_return};
use std::{
fmt::{Debug, Display, Formatter},
io::Error,
@@ -288,6 +289,79 @@ impl Display for ResourceMovementError {
}
}
/// A set of potential errors that may occur when moving a folder with resources.
#[derive(Debug)]
pub enum FolderMovementError {
/// An IO error.
Io(std::io::Error),
/// A file error.
FileError(FileError),
/// A [`ResourceMovementError`].
ResourceMovementError(ResourceMovementError),
/// The folder is not in the registry.
NotInRegistry {
/// A path of the folder being moved.
dest_dir: PathBuf,
},
/// Trying to move a folder into one of its own sub-folders.
HierarchyError {
/// Path of the folder being moved.
src_dir: PathBuf,
/// Destination directory.
dest_dir: PathBuf,
},
}
impl From<FileError> for FolderMovementError {
fn from(value: FileError) -> Self {
Self::FileError(value)
}
}
impl From<std::io::Error> for FolderMovementError {
fn from(value: Error) -> Self {
Self::Io(value)
}
}
impl From<ResourceMovementError> for FolderMovementError {
fn from(value: ResourceMovementError) -> Self {
Self::ResourceMovementError(value)
}
}
impl Display for FolderMovementError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FolderMovementError::Io(err) => {
write!(f, "Io error: {err}")
}
FolderMovementError::FileError(err) => {
write!(f, "File error: {err}")
}
FolderMovementError::ResourceMovementError(err) => {
write!(f, "{err}")
}
FolderMovementError::NotInRegistry { dest_dir } => {
write!(
f,
"Unable to move the {} folder, because it is not in the registry!",
dest_dir.display()
)
}
FolderMovementError::HierarchyError { src_dir, dest_dir } => {
write!(
f,
"Trying to move a folder into one of its own sub-folders. \
Source folder is {}, destination folder is {}",
src_dir.display(),
dest_dir.display()
)
}
}
}
}
impl ResourceManager {
/// Creates a resource manager with default settings and loaders.
pub fn new(io: Arc<dyn ResourceIo>, task_pool: Arc<TaskPool>) -> Self {
@@ -554,6 +628,29 @@ impl ResourceManager {
let resources = self.state().reload_resources();
join_all(resources).await;
}
/// Checks if there's a loader for the given resource path.
pub fn is_supported_resource(&self, path: &Path) -> bool {
self.state().is_supported_resource(path)
}
/// Checks if the given path is located inside the folder tracked by the resource registry.
pub fn is_path_in_registry(&self, path: &Path) -> bool {
self.state().is_path_in_registry(path)
}
/// Tries to move a folder to some other folder.
#[allow(clippy::await_holding_lock)]
pub async fn try_move_folder(
&self,
src_dir: &Path,
dest_dir: &Path,
overwrite_existing: bool,
) -> Result<(), FolderMovementError> {
self.state()
.try_move_folder(src_dir, dest_dir, overwrite_existing)
.await
}
}
impl ResourceManagerState {
@@ -1357,6 +1454,88 @@ impl ResourceManagerState {
self.move_resource_by_path(resource_path, new_path, overwrite_existing)
.await
}
/// Checks if there's a loader for the given resource path.
pub fn is_supported_resource(&self, path: &Path) -> bool {
let ext = some_or_return!(path.extension(), false);
let ext = some_or_return!(ext.to_str(), false);
self.loaders
.lock()
.iter()
.any(|loader| loader.supports_extension(ext))
}
/// Checks if the given path is located inside the folder tracked by the resource registry.
pub fn is_path_in_registry(&self, path: &Path) -> bool {
let registry = self.resource_registry.lock();
if let Some(registry_directory) = registry.directory() {
if let Ok(canonical_registry_path) = registry_directory.canonicalize() {
if let Ok(canonical_path) = path.canonicalize() {
return canonical_path.starts_with(canonical_registry_path);
}
}
}
false
}
/// Tries to move a folder to some other folder.
pub async fn try_move_folder(
&self,
src_dir: &Path,
dest_dir: &Path,
overwrite_existing: bool,
) -> Result<(), FolderMovementError> {
if dest_dir.starts_with(src_dir) {
return Err(FolderMovementError::HierarchyError {
src_dir: src_dir.to_path_buf(),
dest_dir: dest_dir.to_path_buf(),
});
}
// Early validation to prevent error spam when trying to move a folder out of the
// assets directory.
if !self.is_path_in_registry(dest_dir) {
return Err(FolderMovementError::NotInRegistry {
dest_dir: dest_dir.to_path_buf(),
});
}
// At this point we have a folder dropped on some other folder. In this case
// we need to move all the assets from the dropped folder to a new subfolder (with the same
// name as the dropped folder) of the other folder first. After that we can move the rest
// of the files and finally delete the dropped folder.
let mut what_where_stack = vec![(src_dir.to_path_buf(), dest_dir.to_path_buf())];
while let Some((src_dir, target_dir)) = what_where_stack.pop() {
let src_dir_name = some_or_continue!(src_dir.file_name());
let target_sub_dir = target_dir.join(src_dir_name);
if !self.resource_io.exists(&target_sub_dir).await {
std::fs::create_dir(&target_sub_dir)?;
}
let target_sub_dir_normalized = ok_or_continue!(make_relative_path(&target_sub_dir));
for path in self.resource_io.walk_directory(&src_dir, 1).await? {
if path.is_file() {
let file_name = some_or_continue!(path.file_name());
if self.is_supported_resource(&path) {
let dest_path = target_sub_dir_normalized.join(file_name);
self.move_resource_by_path(path, &dest_path, overwrite_existing)
.await?;
}
} else if path.is_dir() && path != src_dir {
// Sub-folders will be processed after all assets from current dir
// were moved.
what_where_stack.push((path, target_sub_dir.clone()));
}
}
}
std::fs::remove_dir_all(src_dir)?;
Ok(())
}
}
#[cfg(test)]