Skip to content

Commit c4f5b1a

Browse files
committed
chore: development v0.2.32 - comprehensive testing complete [auto-commit]
1 parent 391f739 commit c4f5b1a

File tree

11 files changed

+44
-25
lines changed

11 files changed

+44
-25
lines changed

Cargo.lock

Lines changed: 8 additions & 8 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
@@ -32,7 +32,7 @@ members = [
3232
# Workspace Package Metadata (inherited by all crates)
3333
# ─────────────────────────────────────────────────────────────────────────────
3434
[workspace.package]
35-
version = "0.2.31"
35+
version = "0.2.32"
3636
edition = "2024"
3737
rust-version = "1.85"
3838
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.31)
24+
### Benchmark Results (v0.2.32)
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.31** | **18.7 Million** | **~142 seconds** | All disks, fast mode |
36+
| **UFFS v0.2.32** | **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-cli/src/commands.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ async fn search_multi_drive_filtered(
937937

938938
// Build path resolver from FULL data BEFORE filtering
939939
// This is the key fix for the <unknown> path bug!
940-
let mut path_resolver = if needs_paths {
940+
let path_resolver = if needs_paths {
941941
match uffs_core::FastPathResolver::build(&full_df, drive_char) {
942942
Ok(resolver) => Some(resolver),
943943
Err(e) => {
@@ -1233,7 +1233,7 @@ async fn search_multi_drive_streaming<W: Write + Send + 'static>(
12331233

12341234
// Build path resolver from FULL data BEFORE filtering
12351235
// This is critical for resolving paths correctly!
1236-
let mut path_resolver = match uffs_core::FastPathResolver::build(&df, drive_char) {
1236+
let path_resolver = match uffs_core::FastPathResolver::build(&df, drive_char) {
12371237
Ok(resolver) => Some(resolver),
12381238
Err(e) => {
12391239
let _ = tx

crates/uffs-core/src/path_resolver.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -387,37 +387,52 @@ impl FastPathResolver {
387387

388388
/// Add a "path" column with trailing slashes for directories (C++ parity).
389389
///
390-
/// This method adds a trailing backslash to directory paths to match
391-
/// the C++ `UltraFastFileSearch` output format.
390+
/// Builds paths correctly for hard links by using `parent_frs` + `name`
391+
/// instead of just resolving `frs`. Each hard link gets its correct
392+
/// path based on its parent.
392393
///
393394
/// # Errors
394395
///
395-
/// Returns an error if the frs or `is_directory` columns are missing.
396+
/// Returns an error if required columns are missing.
396397
pub fn add_path_column_with_dir_suffix(&self, df: &DataFrame) -> Result<DataFrame> {
397-
let frs_col = df.column("frs")?.u64()?;
398+
let parent_frs_col = df.column("parent_frs")?.u64()?;
399+
let name_col = df.column("name")?.str()?;
398400
let is_dir_col = df.column("is_directory")?.bool()?;
399401
let stream_name_col = df.column("stream_name").ok().and_then(|col| col.str().ok());
400402

401403
// Collect values for parallel iteration
402-
let frs_values: Vec<Option<u64>> = frs_col.into_iter().collect();
404+
let parent_frs_values: Vec<Option<u64>> = parent_frs_col.into_iter().collect();
405+
let name_values: Vec<Option<&str>> = name_col.into_iter().collect();
403406
let is_dir_values: Vec<Option<bool>> = is_dir_col.into_iter().collect();
404407
let stream_names: Vec<Option<&str>> = stream_name_col.map_or_else(
405-
|| vec![None; frs_values.len()],
408+
|| vec![None; parent_frs_values.len()],
406409
|col| col.into_iter().collect(),
407410
);
408411

409-
// Resolve paths in parallel with directory suffix
410-
let paths: Vec<String> = frs_values
412+
// Resolve paths in parallel: parent_path + name + optional stream
413+
let paths: Vec<String> = parent_frs_values
411414
.par_iter()
415+
.zip(name_values.par_iter())
412416
.zip(is_dir_values.par_iter())
413417
.zip(stream_names.par_iter())
414-
.map(|((frs, is_dir), stream_name)| {
415-
let mut path =
416-
frs.map_or_else(|| "<null>".to_owned(), |frs_val| self.resolve(frs_val));
418+
.map(|(((parent_frs, name), is_dir), stream_name)| {
419+
// Resolve parent directory path
420+
let parent_path =
421+
parent_frs.map_or_else(|| "<null>".to_owned(), |frs_val| self.resolve(frs_val));
422+
423+
// Build full path: parent + backslash + name
424+
let file_name = name.unwrap_or("<unnamed>");
425+
let mut path = if parent_path.ends_with('\\') {
426+
format!("{parent_path}{file_name}")
427+
} else {
428+
format!("{parent_path}\\{file_name}")
429+
};
430+
417431
// Add trailing backslash for directories
418432
if is_dir.unwrap_or(false) && !path.ends_with('\\') {
419433
path.push('\\');
420434
}
435+
421436
// Append stream name for ADS (e.g., "file.txt:Zone.Identifier")
422437
if let Some(stream) = stream_name {
423438
if !stream.is_empty() {

0 commit comments

Comments
 (0)