aboutsummaryrefslogtreecommitdiff
path: root/crates/nres
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-02-12 01:00:46 +0300
committerValentin Popov <valentin@popov.link>2026-02-12 01:00:46 +0300
commit3c06e768d603ea4452fc95cc48951af9270f76a1 (patch)
tree453f9b747788ec4b0a12cf42a3ef5f96c53c254c /crates/nres
parent70ed6480c2b2b2ecab4956216c1e8e85b0938b4c (diff)
downloadfparkan-3c06e768d603ea4452fc95cc48951af9270f76a1.tar.xz
fparkan-3c06e768d603ea4452fc95cc48951af9270f76a1.zip
feat: добавить поддержку атомарной замены файлов для Windows и тесты на максимальную длину имени
Diffstat (limited to 'crates/nres')
-rw-r--r--crates/nres/Cargo.toml3
-rw-r--r--crates/nres/src/lib.rs49
-rw-r--r--crates/nres/src/tests.rs35
3 files changed, 75 insertions, 12 deletions
diff --git a/crates/nres/Cargo.toml b/crates/nres/Cargo.toml
index 7f85352..25f3494 100644
--- a/crates/nres/Cargo.toml
+++ b/crates/nres/Cargo.toml
@@ -5,3 +5,6 @@ edition = "2021"
[dependencies]
common = { path = "../common" }
+
+[target.'cfg(windows)'.dependencies]
+windows-sys = { version = "0.59", features = ["Win32_Storage_FileSystem"] }
diff --git a/crates/nres/src/lib.rs b/crates/nres/src/lib.rs
index 1fa3b39..e0631e3 100644
--- a/crates/nres/src/lib.rs
+++ b/crates/nres/src/lib.rs
@@ -651,18 +651,43 @@ fn write_atomic(path: &Path, content: &[u8]) -> Result<()> {
file.flush()?;
drop(file);
- match fs::rename(&tmp_path, path) {
- Ok(()) => Ok(()),
- Err(rename_err) => {
- if path.exists() {
- fs::remove_file(path)?;
- fs::rename(&tmp_path, path)?;
- Ok(())
- } else {
- let _ = fs::remove_file(&tmp_path);
- Err(Error::Io(rename_err))
- }
- }
+ if let Err(err) = replace_file_atomically(&tmp_path, path) {
+ let _ = fs::remove_file(&tmp_path);
+ return Err(Error::Io(err));
+ }
+
+ Ok(())
+}
+
+#[cfg(not(windows))]
+fn replace_file_atomically(src: &Path, dst: &Path) -> std::io::Result<()> {
+ fs::rename(src, dst)
+}
+
+#[cfg(windows)]
+fn replace_file_atomically(src: &Path, dst: &Path) -> std::io::Result<()> {
+ use std::iter;
+ use std::os::windows::ffi::OsStrExt;
+ use windows_sys::Win32::Storage::FileSystem::{
+ MoveFileExW, MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH,
+ };
+
+ let src_wide: Vec<u16> = src.as_os_str().encode_wide().chain(iter::once(0)).collect();
+ let dst_wide: Vec<u16> = dst.as_os_str().encode_wide().chain(iter::once(0)).collect();
+
+ // Replace destination in one OS call, avoiding remove+rename gaps on Windows.
+ let ok = unsafe {
+ MoveFileExW(
+ src_wide.as_ptr(),
+ dst_wide.as_ptr(),
+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
+ )
+ };
+
+ if ok == 0 {
+ Err(std::io::Error::last_os_error())
+ } else {
+ Ok(())
}
}
diff --git a/crates/nres/src/tests.rs b/crates/nres/src/tests.rs
index 51ec8b4..6de02e5 100644
--- a/crates/nres/src/tests.rs
+++ b/crates/nres/src/tests.rs
@@ -771,6 +771,41 @@ fn nres_synthetic_read_find_and_edit() {
}
#[test]
+fn nres_max_name_length_roundtrip() {
+ let max_name = "12345678901234567890123456789012345";
+ assert_eq!(max_name.len(), 35);
+
+ let src = build_nres_bytes(&[SyntheticEntry {
+ kind: 9,
+ attr1: 1,
+ attr2: 2,
+ attr3: 3,
+ name: max_name,
+ data: b"payload",
+ }]);
+
+ let archive = Archive::open_bytes(Arc::from(src.into_boxed_slice()), OpenOptions::default())
+ .expect("open synthetic nres failed");
+
+ assert_eq!(archive.entry_count(), 1);
+ assert_eq!(archive.find(max_name), Some(EntryId(0)));
+ assert_eq!(
+ archive.find(&max_name.to_ascii_lowercase()),
+ Some(EntryId(0))
+ );
+
+ let entry = archive.get(EntryId(0)).expect("missing entry 0");
+ assert_eq!(entry.meta.name, max_name);
+ assert_eq!(
+ archive
+ .read(EntryId(0))
+ .expect("read payload failed")
+ .as_slice(),
+ b"payload"
+ );
+}
+
+#[test]
fn nres_find_falls_back_when_sort_index_is_out_of_range() {
let mut bytes = build_nres_bytes(&[
SyntheticEntry {