Last push of ditched initial design.

This is only getting pushed for reference' sake.
I don't even know how functional this version is.
This commit is contained in:
2026-04-20 17:55:56 -04:00
parent 43892364ed
commit 4cd1f90d70
9 changed files with 1621 additions and 118 deletions
Generated
+1382 -5
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -8,6 +8,8 @@ license = "MIT"
publish = false
[dependencies]
num-traits = "0.2.19"
ratatui = "0.30"
ron = ">=0.8, <0.13"
#rust-i18n = "3.1.3"
@@ -28,6 +30,6 @@ features = ["derive"]
# with unsafe ffi disasters trying to solve problems.
#
# And yes, I spent time tracking down the first release with that constant.
# v0.2.25 is almost 9 years old as of writing this comment
# v0.2.25 is almost 9 years old as of writing this comment.
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
libc = "~0.2.25"
+5
View File
@@ -6,6 +6,7 @@ use clap::{ArgAction, Parser};
pub static CONFIG: LazyLock<Args> = LazyLock::new(|| Args::parse());
#[derive(Parser, Debug, Clone)]
#[clap(author, version, about)]
pub struct Args {
/// Path to source file or block device
#[arg(short, long, value_hint = clap::ValueHint::DirPath)]
@@ -50,4 +51,8 @@ pub struct Args {
#[cfg(all(unix, not(target_os = "macos")))]
#[arg(short = 'd', long = "no-direct", action = ArgAction::SetFalse)]
pub direct_io: bool,
/// Disable the TUI in favour of a classic CLI experience.
#[arg(short = 't', long = "no-tui", action = ArgAction::SetFalse)]
pub tui: bool,
}
+33
View File
@@ -3,8 +3,14 @@ mod io;
mod mapping;
mod path;
mod recovery;
mod tui;
use std::sync::mpsc;
use std::thread;
use cli::CONFIG;
use recovery::Recover;
use tui::Tui;
use anyhow;
@@ -18,5 +24,32 @@ fn main() -> anyhow::Result<()> {
let mut recover_tool = Recover::new()?;
recover_tool.run()?;
if CONFIG.tui {
run_tui()?;
} else {
run_cli();
}
Ok(())
}
fn run_cli() {}
fn run_tui() -> std::io::Result<()> {
let mut tui = Tui::new();
// Enter Raw terminal mode.
let mut terminal = ratatui::init();
let (tx, rx) = mpsc::channel::<crate::tui::Event>();
let tx_input_fetcher = tx.clone();
thread::spawn(move || tui::input_fetcher(tx_input_fetcher));
let tui_result = tui.run(&mut terminal, rx);
// Exit Raw terminal mode.
ratatui::restore();
tui_result
}
+4 -10
View File
@@ -7,14 +7,14 @@ use serde::{Deserialize, Serialize};
// Use `sort_by_key()` to be safe.
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Cluster {
pub domain: Domain,
domain: Domain<usize>,
pub stage: Stage,
}
impl Default for Cluster {
fn default() -> Self {
Cluster {
domain: Domain::default(),
domain: Domain::from(0..1),
stage: Stage::default(),
}
}
@@ -31,10 +31,7 @@ impl Cluster {
for _ in 0..(domain_len / cluster_len) {
clusters.push(Cluster {
domain: Domain {
start,
end: start + cluster_len,
},
domain: Domain::from(start..start + cluster_len),
stage: self.stage,
});
@@ -42,10 +39,7 @@ impl Cluster {
}
clusters.push(Cluster {
domain: Domain {
start,
end: self.domain.end,
},
domain: Domain::from(start..self.domain.end),
stage: self.stage,
});
+31 -19
View File
@@ -1,28 +1,20 @@
use std::ops::{Deref, DerefMut, Range};
use serde::{Deserialize, Serialize};
/// Domain, in sectors.
/// Requires sector_size to be provided elsewhere for conversion to bytes.
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Domain {
pub start: usize,
pub end: usize,
}
impl Default for Domain {
fn default() -> Self {
Domain { start: 0, end: 1 }
}
}
impl Domain {
/// Return length of domain in sectors.
#[allow(dead_code)]
pub fn len(self) -> usize {
self.end - self.start
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Domain<Idx>(Range<Idx>)
where
Idx: PartialEq + Eq + PartialOrd + Ord;
impl<Idx> Domain<Idx>
where
Idx: PartialEq + PartialOrd + Ord,
{
/// Returns the type of overlap between this domain and another.
pub fn overlap(&self, other: Domain) -> DomainOverlap {
pub fn overlap(&self, other: Self) -> DomainOverlap {
if self.end <= other.start || other.end <= self.start {
// Cases 7, 8, 12, and 13 of map::tests::test_update
DomainOverlap::None
@@ -42,6 +34,26 @@ impl Domain {
}
}
impl<Idx> Deref for Domain<Idx> {
type Target = Range<Idx>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<Idx> DerefMut for Domain<Idx> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<Idx> From<Range<Idx>> for Domain<Idx> {
fn from(value: Range<Idx>) -> Self {
Self(value)
}
}
pub enum DomainOverlap {
None,
SelfEngulfsOther,
+75 -81
View File
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct MapFile {
pub sector_size: usize,
pub domain: Domain,
pub domain: Domain<usize>,
pub map: Vec<Cluster>,
}
@@ -29,9 +29,9 @@ impl Default for MapFile {
fn default() -> Self {
MapFile {
sector_size: crate::FB_SECTOR_SIZE,
domain: Domain::default(),
domain: Domain::from(0..1),
map: vec![Cluster {
domain: Domain::default(),
domain: Domain::from(0..1),
stage: Stage::Patchwork { depth: 0 },
}],
}
@@ -136,7 +136,7 @@ impl MapFile {
/// Extend the domain of the MapFile.
/// Returns None if the domain cannot be changed or is unchanged.
/// Returns the delta of the previous domain end and the new end.
pub fn extend(&mut self, end: usize) -> Option<usize> {
pub fn extend(&mut self..usize) -> Option<usize> {
if end <= self.domain.end {
return None;
}
@@ -147,10 +147,7 @@ impl MapFile {
// Add new data as untested.
self.update(Cluster {
domain: Domain {
start: old_end,
end: self.domain.end,
},
domain: Domain::from(old_end..self.domain.end),
..Default::default()
});
Some(delta)
@@ -189,10 +186,7 @@ fn other_engulfs_self_update(new: Cluster, old: &mut Cluster, map: &mut Vec<Clus
if new.domain.end != old_end {
// Case 10 of map::tests::test_update
map.push(Cluster {
domain: Domain {
start: new.domain.end,
end: old_end,
},
domain: Domain::from(new.domain.end..old_end),
stage: old.stage,
})
}
@@ -217,14 +211,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 1, end: 3 },
domain: Domain::from(1..3),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
});
map.map.sort();
@@ -233,11 +227,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3),
..Default::default()
}
]
@@ -256,14 +250,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 1, end: 3 },
domain: Domain::from(1..3),
..Default::default()
});
map.map.sort();
@@ -272,11 +266,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1),
..Default::default()
},
Cluster {
domain: Domain { start: 1, end: 3 },
domain: Domain::from(1..3),
..Default::default()
}
]
@@ -294,14 +288,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 1, end: 3 },
domain: Domain::from(1..3),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
});
map.map.sort();
@@ -309,7 +303,7 @@ mod tests {
assert_eq!(
map.map,
vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
}]
);
@@ -327,14 +321,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 1, end: 3 },
domain: Domain::from(1..3),
..Default::default()
});
map.map.sort();
@@ -343,11 +337,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1),
..Default::default()
},
Cluster {
domain: Domain { start: 1, end: 3 },
domain: Domain::from(1..3),
..Default::default()
}
]
@@ -365,14 +359,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
});
map.map.sort();
@@ -380,7 +374,7 @@ mod tests {
assert_eq!(
map.map,
vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
}]
);
@@ -398,14 +392,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
});
map.map.sort();
@@ -414,11 +408,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3),
..Default::default()
}
]
@@ -436,14 +430,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
});
map.map.sort();
@@ -452,11 +446,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3),
..Default::default()
}
]
@@ -473,14 +467,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3),
..Default::default()
});
map.map.sort();
@@ -489,11 +483,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 2 },
domain: Domain::from(0..2),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3),
..Default::default()
}
]
@@ -511,14 +505,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 1, end: 2 },
domain: Domain::from(1..2),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
});
map.map.sort();
@@ -526,7 +520,7 @@ mod tests {
assert_eq!(
map.map,
vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3 ),
..Default::default()
}]
);
@@ -548,14 +542,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 1, end: 2 },
domain: Domain::from(1..2 ),
..Default::default()
});
map.map.sort();
@@ -564,15 +558,15 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1 ),
..Default::default()
},
Cluster {
domain: Domain { start: 1, end: 2 },
domain: Domain::from(1..2 ),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3 ),
..Default::default()
}
]
@@ -590,14 +584,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3 ),
stage: Stage::Patchwork { depth: 0 },
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3 ),
stage: Stage::Intact,
});
map.map.sort();
@@ -605,7 +599,7 @@ mod tests {
assert_eq!(
map.map,
vec![Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3 ),
stage: Stage::Intact
}]
);
@@ -622,14 +616,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3 ),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1 ),
..Default::default()
});
map.map.sort();
@@ -638,11 +632,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1 ),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3 ),
..Default::default()
}
]
@@ -660,14 +654,14 @@ mod tests {
let mut map = MapFile {
map: vec![Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1 ),
..Default::default()
}],
..Default::default()
};
map.update(Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3 ),
..Default::default()
});
map.map.sort();
@@ -676,11 +670,11 @@ mod tests {
map.map,
vec![
Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1 ),
..Default::default()
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3 ),
..Default::default()
}
]
@@ -696,7 +690,7 @@ mod tests {
// If this fails here, there's something SERIOUSLY wrong.
assert!(
mf_stage == Stage::Patchwork { depth: 0 },
"Determined stage to be {:?}, when {:?} was expeccted.",
"Determined stage to be {:?}, when {:?} was expected.",
mf_stage,
Stage::Patchwork { depth: 0 }
);
@@ -764,50 +758,50 @@ mod tests {
fn defrag() {
let mut mf = MapFile {
sector_size: 1,
domain: Domain { start: 0, end: 8 },
domain: Domain::from(0..8 ),
map: vec![
Cluster {
domain: Domain { start: 0, end: 1 },
domain: Domain::from(0..1 ),
stage: Stage::Patchwork { depth: 0 },
},
Cluster {
domain: Domain { start: 1, end: 2 },
domain: Domain::from(1..1 ),
stage: Stage::Patchwork { depth: 0 },
},
Cluster {
domain: Domain { start: 2, end: 3 },
domain: Domain::from(2..3 ),
stage: Stage::Patchwork { depth: 0 },
},
Cluster {
domain: Domain { start: 3, end: 4 },
domain: Domain::from(3..4 ),
stage: Stage::Isolate,
},
Cluster {
domain: Domain { start: 4, end: 5 },
domain: Domain::from(4..5 ),
stage: Stage::Isolate,
},
Cluster {
domain: Domain { start: 5, end: 6 },
domain: Domain::from(5..6 ),
stage: Stage::Patchwork { depth: 1 },
},
Cluster {
domain: Domain { start: 6, end: 7 },
domain: Domain::from(6..7 ),
stage: Stage::Patchwork { depth: 0 },
},
Cluster {
domain: Domain { start: 7, end: 8 },
domain: Domain::from(7..8 ),
stage: Stage::Damaged,
},
Cluster {
domain: Domain { start: 8, end: 10 },
domain: Domain::from(8..10 ),
stage: Stage::Intact,
},
Cluster {
domain: Domain { start: 10, end: 11 },
domain: Domain::from(10..10 ),
stage: Stage::BruteForceAndDesperation,
},
Cluster {
domain: Domain { start: 11, end: 12 },
domain: Domain::from(11..12 ),
stage: Stage::BruteForceAndDesperation,
},
],
@@ -815,31 +809,31 @@ mod tests {
let expected = vec![
Cluster {
domain: Domain { start: 0, end: 3 },
domain: Domain::from(0..3 ),
stage: Stage::Patchwork { depth: 0 },
},
Cluster {
domain: Domain { start: 3, end: 5 },
domain: Domain::from(3..5 ),
stage: Stage::Isolate,
},
Cluster {
domain: Domain { start: 5, end: 6 },
domain: Domain::from(5..6 ),
stage: Stage::Patchwork { depth: 1 },
},
Cluster {
domain: Domain { start: 6, end: 7 },
domain: Domain::from(6..7 ),
stage: Stage::Patchwork { depth: 0 },
},
Cluster {
domain: Domain { start: 7, end: 8 },
domain: Domain::from(7..8 ),
stage: Stage::Damaged,
},
Cluster {
domain: Domain { start: 8, end: 10 },
domain: Domain::from(8..10 ),
stage: Stage::Intact,
},
Cluster {
domain: Domain { start: 10, end: 12 },
domain: Domain::from(10..12 ),
stage: Stage::BruteForceAndDesperation,
},
];
+5 -2
View File
@@ -91,12 +91,13 @@ impl Recover {
while self.map.get_stage() == (Stage::Patchwork { depth }) {
// Order of these two expressions matters, stupid.
buf_capacity /= depth;
depth += 1;
buf_capacity /= depth + 1;
for cluster in self.map.get_clusters(Stage::Patchwork { depth }) {
self.read_domain(buf.as_mut(), cluster.domain, buf_capacity, Stage::Isolate)?;
}
depth += 1;
}
Ok(())
@@ -123,6 +124,8 @@ impl Recover {
stage: Stage::Intact,
};
dbg!(cluster.domain);
match self.read_sectors(buf.as_mut()) {
Ok(bytes) => {
self.output
+83
View File
@@ -0,0 +1,83 @@
use std::io;
use std::sync::mpsc;
use ratatui::crossterm;
use ratatui::layout::{Constraint, Layout};
use ratatui::symbols::border;
use ratatui::widgets::{Block, Widget};
use ratatui::{DefaultTerminal, Frame};
#[derive(Debug, Default)]
pub struct Tui {
// bool::default() -> false
exit: bool,
}
impl Tui {
pub fn new() -> Self {
Self::default()
}
pub fn run(
&mut self,
terminal: &mut DefaultTerminal,
rx: mpsc::Receiver<Event>,
) -> io::Result<()> {
while !self.exit {
// Render frame.
terminal.draw(|frame| self.draw(frame))?;
// Event handler
// unwraps, bc what could go wrong?
match rx.recv().unwrap() {
Event::Input(key_event) => self.handle_key_event(key_event)?,
}
}
Ok(())
}
pub fn draw(&self, frame: &mut Frame) {
frame.render_widget(self, frame.area());
}
pub fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) -> io::Result<()> {
if key_event.kind == crossterm::event::KeyEventKind::Press
&& key_event.code == crossterm::event::KeyCode::Char('q')
{
self.exit = true;
}
Ok(())
}
}
// impl on reference to avoid accidentally mutating.
impl Widget for &Tui {
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
{
let [main_area, _info_area] =
Layout::vertical([Constraint::Percentage(75), Constraint::Fill(1)]).areas(area);
let main_block = Block::bordered()
.title(" Viewer ")
.border_set(border::THICK);
main_block.render(main_area, buf);
}
}
pub enum Event {
Input(crossterm::event::KeyEvent),
}
pub fn input_fetcher(tx: mpsc::Sender<Event>) {
loop {
// unwraps, bc what could go wrong?
match crossterm::event::read().unwrap() {
crossterm::event::Event::Key(key_event) => tx.send(Event::Input(key_event)).unwrap(),
_ => (),
}
}
}