Skip to content

Commit d3a8080

Browse files
committed
feat: streaming output for multi-drive search
Changes: - Results stream to stdout as each drive completes (no waiting for all drives) - Supports CSV (default) and NDJSON (--format json) streaming formats - No progress bars for streaming mode - output IS the progress indicator - Limit (if specified) applies to total output, not per-drive - Default limit is 0 (unlimited) This matches the behavior of the legacy uffs.com where results appear immediately as each drive finishes reading its MFT.
1 parent 710a013 commit d3a8080

1 file changed

Lines changed: 29 additions & 6 deletions

File tree

crates/uffs-cli/src/commands.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ impl<W: Write> StreamingWriter<W> {
6868
}
6969
}
7070

71-
let mut writer = self.writer.lock().map_err(|e| anyhow::anyhow!("Lock error: {e}"))?;
71+
let mut writer = self
72+
.writer
73+
.lock()
74+
.map_err(|e| anyhow::anyhow!("Lock error: {e}"))?;
7275

7376
match self.format {
7477
StreamingFormat::Csv => self.write_csv_batch(&mut *writer, df),
@@ -77,11 +80,23 @@ impl<W: Write> StreamingWriter<W> {
7780
}
7881

7982
fn write_csv_batch(&self, writer: &mut W, df: &uffs_mft::DataFrame) -> Result<usize> {
80-
let columns: Vec<_> = df.get_column_names().iter().map(|s| s.to_string()).collect();
83+
let columns: Vec<_> = df
84+
.get_column_names()
85+
.iter()
86+
.map(|s| s.to_string())
87+
.collect();
8188

8289
// Write header only once
8390
if !self.header_written.swap(true, Ordering::SeqCst) {
84-
writeln!(writer, "{}", columns.iter().map(|c| format!("\"{c}\"")).collect::<Vec<_>>().join(","))?;
91+
writeln!(
92+
writer,
93+
"{}",
94+
columns
95+
.iter()
96+
.map(|c| format!("\"{c}\""))
97+
.collect::<Vec<_>>()
98+
.join(",")
99+
)?;
85100
}
86101

87102
let mut rows_written = 0;
@@ -100,7 +115,9 @@ impl<W: Write> StreamingWriter<W> {
100115

101116
let mut values = Vec::with_capacity(columns.len());
102117
for col_name in &columns {
103-
let col = df.column(col_name).map_err(|e| anyhow::anyhow!("Column error: {e}"))?;
118+
let col = df
119+
.column(col_name)
120+
.map_err(|e| anyhow::anyhow!("Column error: {e}"))?;
104121
let val = format_cell_value(col, row_idx);
105122
values.push(val);
106123
}
@@ -113,7 +130,11 @@ impl<W: Write> StreamingWriter<W> {
113130
}
114131

115132
fn write_json_batch(&self, writer: &mut W, df: &uffs_mft::DataFrame) -> Result<usize> {
116-
let columns: Vec<_> = df.get_column_names().iter().map(|s| s.to_string()).collect();
133+
let columns: Vec<_> = df
134+
.get_column_names()
135+
.iter()
136+
.map(|s| s.to_string())
137+
.collect();
117138

118139
let mut rows_written = 0;
119140
let height = df.height();
@@ -135,7 +156,9 @@ impl<W: Write> StreamingWriter<W> {
135156
if i > 0 {
136157
obj.push_str(", ");
137158
}
138-
let col = df.column(col_name).map_err(|e| anyhow::anyhow!("Column error: {e}"))?;
159+
let col = df
160+
.column(col_name)
161+
.map_err(|e| anyhow::anyhow!("Column error: {e}"))?;
139162
let val = format_json_value(col, row_idx);
140163
obj.push_str(&format!("\"{col_name}\": {val}"));
141164
}

0 commit comments

Comments
 (0)