Skip to content

Commit d5993d8

Browse files
committed
chore: development v0.2.94 - comprehensive testing complete [auto-commit]
1 parent 07e75e7 commit d5993d8

File tree

13 files changed

+75
-36
lines changed

13 files changed

+75
-36
lines changed

Cargo.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ exclude = [
3939
# Workspace Package Metadata (inherited by all crates)
4040
# ─────────────────────────────────────────────────────────────────────────────
4141
[workspace.package]
42-
version = "0.2.93"
42+
version = "0.2.94"
4343
edition = "2024"
4444
rust-version = "1.85"
4545
license = "MPL-2.0 OR LicenseRef-UFFS-Commercial"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Traditional file search tools (including `os.walk`, `FindFirstFile`, etc.) work
2121

2222
**UFFS reads the MFT directly** - once - and queries it in memory using Polars DataFrames. This is like reading the entire phonebook once instead of looking up each name individually.
2323

24-
### Benchmark Results (v0.2.93)
24+
### Benchmark Results (v0.2.94)
2525

2626
| Drive Type | Records | Time | Throughput |
2727
|------------|---------|------|------------|
@@ -33,7 +33,7 @@ Traditional file search tools (including `os.walk`, `FindFirstFile`, etc.) work
3333

3434
| Comparison | Records | Time | Notes |
3535
|------------|---------|------|-------|
36-
| **UFFS v0.2.93** | **18.7 Million** | **~142 seconds** | All disks, fast mode |
36+
| **UFFS v0.2.94** | **18.7 Million** | **~142 seconds** | All disks, fast mode |
3737
| UFFS v0.1.30 | 18.7 Million | ~315 seconds | Baseline |
3838
| Everything | 19 Million | 178 seconds | All disks |
3939
| WizFile | 6.5 Million | 299 seconds | Single HDD |

crates/uffs-core/src/index_search.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,21 @@ impl SearchResult {
428428
/// stream).
429429
#[must_use]
430430
pub fn from_record(record: &FileRecord, index: &MftIndex) -> Self {
431+
let is_directory = record.is_directory();
432+
// C++ parity: directories have empty name, files have actual name
433+
let name = if is_directory {
434+
String::new()
435+
} else {
436+
index.record_name(record).to_owned()
437+
};
438+
431439
Self {
432-
name: index.record_name(record).to_owned(),
440+
name,
433441
path: None, // Path resolution is expensive, done on demand
434442
size: record.first_stream.size.length,
435443
frs: record.frs,
436444
parent_frs: u64::from(record.first_name.parent_frs),
437-
is_directory: record.is_directory(),
445+
is_directory,
438446
stream_name: String::new(),
439447
name_index: 0,
440448
stream_index: 0,
@@ -455,14 +463,22 @@ impl SearchResult {
455463
let stream_info = index
456464
.get_stream_at(record, stream_idx)
457465
.unwrap_or(&record.first_stream);
466+
let is_directory = record.is_directory();
467+
468+
// C++ parity: directories have empty name, files have actual name
469+
let name = if is_directory {
470+
String::new()
471+
} else {
472+
index.get_name(&name_info.name).to_owned()
473+
};
458474

459475
Self {
460-
name: index.get_name(&name_info.name).to_owned(),
476+
name,
461477
path: None,
462478
size: stream_info.size.length,
463479
frs: record.frs,
464480
parent_frs: u64::from(name_info.parent_frs),
465-
is_directory: record.is_directory(),
481+
is_directory,
466482
stream_name: index.stream_name(stream_info).to_owned(),
467483
name_index: name_idx,
468484
stream_index: stream_idx,
@@ -802,13 +818,14 @@ impl<'a> IndexQuery<'a> {
802818

803819
(0..name_count).flat_map(move |name_idx| {
804820
let inner_cached_path = outer_cached_path.clone();
821+
let is_dir = record.is_directory();
805822
(0..stream_count).map(move |stream_idx| {
806823
let mut result =
807824
SearchResult::from_expanded(record, index, name_idx, stream_idx);
808825
if resolve_paths {
809826
if let Some(stream) = index.get_stream_at(record, stream_idx) {
810827
// Use cached path for primary name (idx 0), build for hard links
811-
let base_path = if name_idx == 0 {
828+
let mut base_path = if name_idx == 0 {
812829
inner_cached_path
813830
.clone()
814831
.unwrap_or_else(|| index.build_path(record.frs))
@@ -819,6 +836,10 @@ impl<'a> IndexQuery<'a> {
819836
// Append stream name for ADS
820837
let stream_name = index.stream_name(stream);
821838
let path = if stream_name.is_empty() {
839+
// Add trailing backslash for directories (C++ parity)
840+
if is_dir && !base_path.ends_with('\\') {
841+
base_path.push('\\');
842+
}
822843
base_path
823844
} else {
824845
format!("{base_path}:{stream_name}")

crates/uffs-core/src/path_resolver.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ impl FastPathResolver {
270270
}
271271
}
272272

273-
// Build final path
274-
path_buf.push(self.volume);
273+
// Build final path (uppercase drive letter for C++ parity)
274+
path_buf.push(self.volume.to_ascii_uppercase());
275275
path_buf.push_str(":\\");
276276

277277
// Append components in reverse order
@@ -618,9 +618,13 @@ impl PathResolver {
618618
}
619619
}
620620

621-
// Build path from components (reverse order)
621+
// Build path from components (reverse order, uppercase drive letter)
622622
components.reverse();
623-
let path = format!("{}:\\{}", self.volume, components.join("\\"));
623+
let path = format!(
624+
"{}:\\{}",
625+
self.volume.to_ascii_uppercase(),
626+
components.join("\\")
627+
);
624628

625629
// Cache the result
626630
self.cache.insert(frs, path.clone());

crates/uffs-mft/src/index.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,12 +1053,12 @@ impl MftIndex {
10531053
}
10541054
}
10551055

1056-
// Reverse and join with volume prefix
1056+
// Reverse and join with volume prefix (backslash for C++ parity)
10571057
components.reverse();
10581058
format!(
1059-
"{}:/{}",
1060-
self.volume.to_ascii_lowercase(),
1061-
components.join("/")
1059+
"{}:\\{}",
1060+
self.volume.to_ascii_uppercase(),
1061+
components.join("\\")
10621062
)
10631063
}
10641064

@@ -1115,12 +1115,12 @@ impl MftIndex {
11151115
current_frs = u64::from(parent_frs);
11161116
}
11171117

1118-
// Reverse and join
1118+
// Reverse and join (backslash for C++ parity)
11191119
components.reverse();
11201120
format!(
1121-
"{}:/{}",
1122-
self.volume.to_ascii_lowercase(),
1123-
components.join("/")
1121+
"{}:\\{}",
1122+
self.volume.to_ascii_uppercase(),
1123+
components.join("\\")
11241124
)
11251125
}
11261126
}
@@ -1254,14 +1254,14 @@ impl PathResolver {
12541254

12551255
// Build path with single allocation
12561256
let mut path = String::with_capacity(total_len);
1257-
path.push(self.volume.to_ascii_lowercase());
1257+
path.push(self.volume.to_ascii_uppercase());
12581258
path.push(':');
12591259

12601260
for &chain_idx in chain.iter().rev() {
12611261
if let Some(record) = index.records.get(chain_idx) {
12621262
let name = index.record_name(record);
12631263
if !name.is_empty() && name != "." {
1264-
path.push('/');
1264+
path.push('\\');
12651265
path.push_str(name);
12661266
}
12671267
}
@@ -1287,7 +1287,7 @@ impl PathResolver {
12871287
let parent_path = if let Some(pidx) = index.frs_to_idx_opt(parent_frs) {
12881288
self.materialize_path(index, pidx)
12891289
} else if parent_frs == ROOT_FRS {
1290-
format!("{}:", self.volume.to_ascii_lowercase())
1290+
format!("{}:", self.volume.to_ascii_uppercase())
12911291
} else {
12921292
return String::new();
12931293
};
@@ -1298,7 +1298,7 @@ impl PathResolver {
12981298
} else {
12991299
let mut path = String::with_capacity(parent_path.len() + 1 + name.len());
13001300
path.push_str(&parent_path);
1301-
path.push('/');
1301+
path.push('\\');
13021302
path.push_str(name);
13031303
path
13041304
}

0 commit comments

Comments
 (0)