mirror of
https://github.com/SL-RU/mmtui.git
synced 2025-03-04 00:14:45 +01:00
first
This commit is contained in:
commit
ed9ca59452
6 changed files with 1953 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
*~
|
1670
Cargo.lock
generated
Normal file
1670
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "mmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crossterm = "0.28.1"
|
||||||
|
ratatui = "0.29.0"
|
||||||
|
tokio = {version = "1.42.0", features = ["full"]}
|
||||||
|
udisks2 = "0.2.0"
|
128
src/drives.rs
Normal file
128
src/drives.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
os::unix::ffi::{OsStrExt, OsStringExt},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::mountpoints;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Block {
|
||||||
|
pub id: String,
|
||||||
|
pub dev: String,
|
||||||
|
pub label: String,
|
||||||
|
pub mount: Option<String>,
|
||||||
|
pub fstype: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Drive {
|
||||||
|
pub id: String,
|
||||||
|
pub path: String,
|
||||||
|
pub model: String,
|
||||||
|
pub ejectable: bool,
|
||||||
|
pub blocks: Vec<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn collect_drives_from_udisk() -> udisks2::Result<Vec<Drive>> {
|
||||||
|
let mut drives: Vec<Drive> = Vec::new();
|
||||||
|
let client = udisks2::Client::new().await?;
|
||||||
|
let objects = client
|
||||||
|
.object_manager()
|
||||||
|
.get_managed_objects()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|(object_path, _)| {
|
||||||
|
let Ok(obj) = client.object(object_path.clone()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some((object_path, obj))
|
||||||
|
});
|
||||||
|
|
||||||
|
for (path, i) in objects {
|
||||||
|
let path = path.to_string();
|
||||||
|
if let Ok(drv) = i.drive().await {
|
||||||
|
let drv = Drive {
|
||||||
|
path,
|
||||||
|
id: drv.id().await?,
|
||||||
|
model: drv.model().await?,
|
||||||
|
ejectable: drv.ejectable().await?,
|
||||||
|
blocks: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(d) = drives.iter_mut().find(|i| i.path == drv.path) {
|
||||||
|
d.model = drv.model;
|
||||||
|
d.ejectable = drv.ejectable;
|
||||||
|
d.id = drv.id;
|
||||||
|
} else {
|
||||||
|
drives.push(drv);
|
||||||
|
}
|
||||||
|
} else if let Ok(blk) = i.block().await {
|
||||||
|
let drv_path = blk.drive().await?.to_string();
|
||||||
|
let block = Block {
|
||||||
|
id: blk.id().await?,
|
||||||
|
dev: String::from_utf8_lossy(&blk.device().await?)
|
||||||
|
.chars()
|
||||||
|
.filter(|c| c != &'\0')
|
||||||
|
.collect::<String>(),
|
||||||
|
label: blk.id_label().await?,
|
||||||
|
mount: None,
|
||||||
|
fstype: blk.id_type().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(d) = drives.iter_mut().find(|i| i.path == drv_path) {
|
||||||
|
d.blocks.push(block);
|
||||||
|
} else {
|
||||||
|
drives.push(Drive {
|
||||||
|
path: drv_path,
|
||||||
|
id: String::new(),
|
||||||
|
model: String::new(),
|
||||||
|
ejectable: false,
|
||||||
|
blocks: vec![block],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(drives)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn collect_all() -> udisks2::Result<Vec<Drive>> {
|
||||||
|
let mut drives = collect_drives_from_udisk().await?;
|
||||||
|
let mounts = mountpoints::MountPoint::collect();
|
||||||
|
|
||||||
|
let mut fstab = Drive {
|
||||||
|
id: "fstab".to_owned(),
|
||||||
|
path: "fstab".to_owned(),
|
||||||
|
model: "fstab".to_owned(),
|
||||||
|
ejectable: false,
|
||||||
|
blocks: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in mounts {
|
||||||
|
let block = drives
|
||||||
|
.iter_mut()
|
||||||
|
.find(|d| d.blocks.iter().find(|b| b.dev == i.dev).is_some())
|
||||||
|
.and_then(|d| d.blocks.iter_mut().find(|b| b.dev == i.dev));
|
||||||
|
if let Some(block) = block {
|
||||||
|
block.mount = i.path;
|
||||||
|
} else {
|
||||||
|
fstab.blocks.push(Block {
|
||||||
|
id: i.dev.clone(),
|
||||||
|
dev: i.dev,
|
||||||
|
label: String::new(),
|
||||||
|
mount: i.path,
|
||||||
|
fstype: i.fs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drives.push(fstab);
|
||||||
|
drives.sort_by_cached_key(|b| b.path.clone());
|
||||||
|
for i in &mut drives {
|
||||||
|
i.blocks.sort_by_cached_key(|b| b.dev.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(drives);
|
||||||
|
}
|
88
src/main.rs
Normal file
88
src/main.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
mod drives;
|
||||||
|
mod mountpoints;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
os::unix::ffi::{OsStrExt, OsStringExt},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||||
|
use mountpoints::MountPoint;
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Alignment, Constraint, Layout},
|
||||||
|
style::Color,
|
||||||
|
text::{Line, Text},
|
||||||
|
widgets::{
|
||||||
|
Block, BorderType, Padding, Paragraph, Row, StatefulWidget, Table, TableState, Widget, Wrap,
|
||||||
|
},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> udisks2::Result<()> {
|
||||||
|
let mut terminal = ratatui::init();
|
||||||
|
let mut ts = TableState::new();
|
||||||
|
loop {
|
||||||
|
let drv = drives::collect_all().await?;
|
||||||
|
terminal
|
||||||
|
.draw(|f| draw(f, &mut ts, &drv))
|
||||||
|
.expect("failed to draw frame");
|
||||||
|
|
||||||
|
if let Event::Key(key) = event::read().unwrap() {
|
||||||
|
if key.kind == KeyEventKind::Press {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up => ts.select_previous(),
|
||||||
|
KeyCode::Down => ts.select_next(),
|
||||||
|
KeyCode::Esc => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ratatui::restore();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(frame: &mut Frame, state: &mut TableState, drv: &[drives::Drive]) {
|
||||||
|
let text = Text::raw("Hello World!");
|
||||||
|
frame.render_widget(text, frame.area());
|
||||||
|
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(ratatui::layout::Direction::Vertical)
|
||||||
|
.constraints(vec![Constraint::Fill(1), Constraint::Length(5)])
|
||||||
|
.split(frame.area());
|
||||||
|
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title(Line::styled("Mount", Color::White))
|
||||||
|
.title_alignment(Alignment::Center)
|
||||||
|
.padding(Padding::symmetric(1, 1))
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Color::Yellow);
|
||||||
|
block.clone().render(layout[0], frame.buffer_mut());
|
||||||
|
|
||||||
|
let rows = drv.iter().flat_map(|d| &d.blocks).map(|i| {
|
||||||
|
Row::new(vec![
|
||||||
|
i.dev.clone(),
|
||||||
|
i.label.clone(),
|
||||||
|
i.mount.clone().unwrap_or_default(),
|
||||||
|
"M".to_owned(),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
let widths = [
|
||||||
|
Constraint::Ratio(1, 3),
|
||||||
|
Constraint::Ratio(1, 3),
|
||||||
|
Constraint::Ratio(1, 3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
];
|
||||||
|
let table = Table::new(rows, widths)
|
||||||
|
.row_highlight_style(Color::Green)
|
||||||
|
.highlight_symbol(">");
|
||||||
|
|
||||||
|
StatefulWidget::render(table, block.inner(frame.area()), frame.buffer_mut(), state);
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new("j - UP, k - DOWN, l - Goto mountpoint\nm - Mount, u - Unmount, e - Eject")
|
||||||
|
.wrap(Wrap { trim: true }),
|
||||||
|
layout[1],
|
||||||
|
);
|
||||||
|
}
|
55
src/mountpoints.rs
Normal file
55
src/mountpoints.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use std::{io::BufRead, path::PathBuf};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MountPoint {
|
||||||
|
pub dev: String,
|
||||||
|
pub path: Option<String>,
|
||||||
|
pub fs: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MountPoint {
|
||||||
|
pub fn collect_from_file(path: &str) -> Vec<MountPoint> {
|
||||||
|
std::io::BufReader::new(std::fs::File::open(PathBuf::from(path)).unwrap())
|
||||||
|
.lines()
|
||||||
|
.map_while(Result::ok)
|
||||||
|
.filter_map(|l| {
|
||||||
|
let mut parts = l.split_whitespace();
|
||||||
|
Some(MountPoint {
|
||||||
|
dev: parts
|
||||||
|
.next()
|
||||||
|
.and_then(|d| if !d.starts_with('#') { Some(d) } else { None })?
|
||||||
|
.into(),
|
||||||
|
path: Some(parts.next()?.to_string()),
|
||||||
|
fs: parts.next()?.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter(|p| {
|
||||||
|
p.fs != "tmpfs" && p.fs != "swap"
|
||||||
|
&& p.path.clone().is_some_and(|p| {
|
||||||
|
!p.starts_with("/sys")
|
||||||
|
&& !p.starts_with("/tmp")
|
||||||
|
&& !p.starts_with("/run")
|
||||||
|
&& !p.starts_with("/proc")
|
||||||
|
&& !p.starts_with("/dev")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect() -> Vec<MountPoint> {
|
||||||
|
let mnt = Self::collect_from_file("/proc/self/mounts");
|
||||||
|
let fstab = Self::collect_from_file("/etc/fstab");
|
||||||
|
|
||||||
|
let fstab: Vec<MountPoint> = fstab
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| !mnt.iter().any(|f| f.path == p.path))
|
||||||
|
.map(|p| MountPoint {
|
||||||
|
dev: p.dev,
|
||||||
|
path: None,
|
||||||
|
fs: p.fs,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
mnt.into_iter().chain(fstab).collect()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue