Skip to content

Commit

Permalink
feat(core): change behavior when an invalid DAR path is passed
Browse files Browse the repository at this point in the history
Warning & Just copy
  • Loading branch information
SARDONYX-sard committed Jan 20, 2024
1 parent deb2ac6 commit ac7b373
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 142 deletions.
1 change: 1 addition & 0 deletions cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"noconflict",
"println",
"rfind",
"rustfmt",
"Rustup",
"Shippori",
"Skyrim",
Expand Down
2 changes: 2 additions & 0 deletions dar2oar_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum ConvertError {
NotFoundOarDir,
#[error("Not found \"DynamicAnimationReplacer\" directory")]
NotFoundDarDir,
#[error("Not found DAR priority(Number) directory")]
NotFoundPriorityDir,
#[error("Never converted.")]
NeverConverted,
#[error("Not found file name")]
Expand Down
184 changes: 107 additions & 77 deletions dar2oar_core/src/fs/converter/common.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
use crate::condition_parser::parse_dar2oar;
use crate::conditions::ConditionsConfig;
use crate::error::{ConvertError, Result};
use crate::fs::converter::parallel::is_contain_oar;
use crate::fs::converter::ConvertOptions;
use crate::fs::converter::{parallel::is_contain_oar, ConvertOptions};
use crate::fs::path_changer::ParsedPath;
use crate::fs::section_writer::{read_file, write_name_space_config, write_section_config};
use crate::fs::section_writer::{write_name_space_config, write_section_config};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicBool, Ordering::AcqRel, Ordering::Relaxed};
use tokio::fs;

/// Common parts of multi-threaded and single-threaded loop processing.
/// # Performance
/// - Since dir is created when a file is discovered, performance is improved if path.is_dir() is not put in path.
pub async fn convert_inner(
options: &ConvertOptions,
path: &Path,
path: impl AsRef<Path>,
parsed_path: &ParsedPath,
is_converted_once: &AtomicBool,
) -> Result<()> {
let path = path.as_ref();
let ConvertOptions {
dar_dir: _,
oar_dir,
Expand All @@ -29,98 +29,128 @@ pub async fn convert_inner(
hide_dar,
} = options;

let is_1st_person = parsed_path.is_1st_person;
let ParsedPath {
dar_root: _,
oar_root,
is_1st_person,
mod_name: parsed_mod_name,
priority,
remain_dir,
} = parsed_path;

let mut parsed_mod_name = mod_name
.clone()
.unwrap_or(parsed_path.mod_name.clone().unwrap_or("Unknown".into()));
if is_1st_person {
.as_deref()
.unwrap_or(parsed_mod_name.as_deref().unwrap_or("Unknown"))
.to_owned();
if *is_1st_person {
parsed_mod_name.push_str("_1st_person");
};

let oar_name_space_path = oar_dir
.as_ref()
.map(|path| match is_1st_person {
true => Path::new(path)
let oar_name_space = oar_dir
.as_deref()
.map(|oar_mod_root| match is_1st_person {
true => Path::new(oar_mod_root)
.join("meshes/actors/character/_1stperson/animations/OpenAnimationReplacer"),
false => {
Path::new(path).join("meshes/actors/character/animations/OpenAnimationReplacer")
}
false => Path::new(oar_mod_root)
.join("meshes/actors/character/animations/OpenAnimationReplacer"),
})
.unwrap_or(parsed_path.oar_root.clone())
.unwrap_or(oar_root.clone())
.join(&parsed_mod_name);

if path.extension().is_some() {
tracing::debug!("File: {:?}", path);
let file_name = path
.file_name()
.ok_or_else(|| ConvertError::NotFoundFileName)?
.to_str()
.ok_or_else(|| ConvertError::InvalidUtf8)?;

// files that do not have a priority dir, i.e., files on the same level as the priority dir,
// are copied to the name space folder location.
// for this reason, an empty string should be put in the name space folder.
let priority = &parsed_path.priority.clone().unwrap_or_default();
let section_name = match is_1st_person {
true => section_1person_table
.as_ref()
.and_then(|table| table.get(priority)),
false => section_table.as_ref().and_then(|table| table.get(priority)),
}
.unwrap_or(priority);

// e.g. mesh/[..]/ModName/SectionName/
let section_root = oar_name_space_path.join(section_name);
fs::create_dir_all(&section_root).await?;
if file_name == "_conditions.txt" {
let content = read_file(path).await?;
tracing::debug!("{path:?} Content:\n{}", content);

let config_json = ConditionsConfig {
name: section_name.into(),
priority: priority.parse()?,
conditions: parse_dar2oar(&content)?,
..Default::default()
};
write_section_config(section_root, config_json).await?;

// # Ordering validity:
// Use `AcqRel` to `happened before relationship`(form a memory read/write order between threads) of cas(compare_and_swap),
// so that other threads read after writing true to memory to prevent unnecessary file writing.
// - In case of cas failure, use `Relaxed` because the order is unimportant.
let _ = is_converted_once.compare_exchange(
false,
true,
Ordering::AcqRel,
Ordering::Relaxed,
);
tracing::debug!("File: {:?}", path);

let file_name = path
.file_name()
.ok_or_else(|| ConvertError::NotFoundFileName)?
.to_str()
.ok_or_else(|| ConvertError::InvalidUtf8)?;

/// Copy motion files(.hkx), gender dir or other.
macro_rules! copy_other_file {
($section_root:ident) => {
if let Some(remain) = remain_dir {
let non_leaf_dir = $section_root.join(remain);

// NOTE: If you call the function only once with this is_converted_once flag,
// the 1st_person&3person conversion will not work!
write_name_space_config(&oar_name_space_path, &parsed_mod_name, author.as_deref())
.await?;
} else {
// maybe motion files(.hkx), gender dir
if let Some(remain) = &parsed_path.remain_dir {
let non_leaf_dir = section_root.join(remain);
let file = &non_leaf_dir.join(file_name);
tracing::debug!("Create dirs: {:?}", &non_leaf_dir);
fs::create_dir_all(&non_leaf_dir).await?;

let file = &non_leaf_dir.join(file_name);
tracing::debug!("Copy with Nest Dir:\n- From: {path:?}\n- To: {file:?}");
fs::copy(path, file).await?;
} else {
let file = section_root.join(file_name);
let file = $section_root.join(file_name);
tracing::debug!("Copy:\n- From: {path:?}\n- To: {file:?}");
fs::create_dir_all(&section_root).await?;
fs::create_dir_all(&$section_root).await?;
fs::copy(path, file).await?;
}
}
};
}

if *hide_dar && path.is_file() && is_contain_oar(path).is_none() {
hide_path(path).await?;
match priority {
Ok(priority) => {
let priority_str = priority.to_string();
let section_name = match is_1st_person {
true => section_1person_table
.as_ref()
.and_then(|table| table.get(&priority_str)),
false => section_table
.as_ref()
.and_then(|table| table.get(&priority_str)),
}
.unwrap_or(&priority_str);

// e.g. mesh/[..]/OpenAnimationReplacer/ModName/SectionName/
let section_root = oar_name_space.join(section_name);
fs::create_dir_all(&section_root).await?;

if file_name == "_conditions.txt" {
let content = fs::read_to_string(path).await?;
tracing::debug!("{path:?} Content:\n{}", content);

let config_json = ConditionsConfig {
name: section_name.into(),
priority: *priority,
conditions: parse_dar2oar(&content)?,
..Default::default()
};
write_section_config(section_root, config_json).await?;

// # Ordering validity:
// Use `AcqRel` to `happened before relationship`(form a memory read/write order between threads) of cas(compare_and_swap),
// so that other threads read after writing true to memory to prevent unnecessary file writing.
// - In case of cas failure, use `Relaxed` because the order is unimportant.
let _ = is_converted_once.compare_exchange(false, true, AcqRel, Relaxed);

// NOTE: If you call the function only once with this is_converted_once flag,
// the 1st_person&3person conversion will not work!
write_name_space_config(&oar_name_space, &parsed_mod_name, author.as_deref())
.await?;
} else {
copy_other_file!(section_root)
};
}
}
Err(invalid_priority) => {
tracing::warn!(
r#"Got invalid priority: "{}".
DAR expects a numeric directory name directly under _CustomConditions, but this is invalid because it is not numeric.
Therefore, just copy it as a memo.
"#,
invalid_priority
);
let section_root = oar_name_space.join(invalid_priority);
// This is a consideration so that if a file is directly under DynamicAnimationReplacer,
// it will be copied in the same way directly under OpenAnimationReplacer.
let section_root = match section_root.extension().is_some() {
true => oar_name_space,
false => section_root,
};
copy_other_file!(section_root)
}
};

if *hide_dar && is_contain_oar(path).is_none() {
hide_path(path).await?
};
Ok(())
}

Expand Down
7 changes: 5 additions & 2 deletions dar2oar_core/src/fs/converter/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ pub async fn convert_dar_to_oar(

for (idx, entry) in entires.enumerate() {
let path = entry.map_err(|_| ConvertError::NotFoundEntry)?.path();
let parsed_path = Arc::new(match parse_dar_path(&path, None) {
if !path.is_file() {
continue;
}
let parsed_path = Arc::new(match parse_dar_path(&path) {
Ok(p) => p,
Err(_) => continue,
});
Expand All @@ -51,7 +54,7 @@ pub async fn convert_dar_to_oar(
tracing::debug!("[Start {}th conversion]\n{:?}", idx, &parsed_path);
convert_inner(
&options,
&path,
path.as_ref(),
parsed_path.as_ref(),
is_converted_once.as_ref(),
)
Expand Down
2 changes: 1 addition & 1 deletion dar2oar_core/src/fs/converter/sequential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub async fn convert_dar_to_oar(
tracing::debug!("Dir: {:?}", path);
continue;
}
let parsed_path = match parse_dar_path(path, None) {
let parsed_path = match parse_dar_path(path) {
Ok(p) => p,
Err(_) => continue,
};
Expand Down
Loading

0 comments on commit ac7b373

Please sign in to comment.