This commit is contained in:
Lutsai Aleksandr 2024-12-16 21:49:48 +03:00
parent ed9ca59452
commit 7f2ac0f3f3
5 changed files with 336 additions and 49 deletions

167
Cargo.lock generated
View file

@ -202,6 +202,26 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "bindgen"
version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.6.0"
@ -272,6 +292,15 @@ dependencies = [
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -284,6 +313,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "compact_str"
version = "0.8.0"
@ -330,6 +370,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags",
"crossterm_winapi",
"futures-core",
"mio",
"parking_lot",
"rustix",
@ -596,6 +637,12 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.15.2"
@ -661,6 +708,15 @@ dependencies = [
"syn",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
@ -682,12 +738,28 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@ -723,6 +795,17 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "loopdev-3"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90a97d7a5124296ee9124a815acdc3dc4a91f577b72812b3f1f99bb959b46e8d"
dependencies = [
"bindgen",
"errno",
"libc",
]
[[package]]
name = "lru"
version = "0.12.5"
@ -756,6 +839,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@ -783,7 +872,9 @@ version = "0.1.0"
dependencies = [
"crossterm",
"ratatui",
"sys-mount",
"tokio",
"tokio-stream",
"udisks2",
]
@ -800,6 +891,16 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "objc"
version = "0.2.7"
@ -1015,7 +1116,7 @@ dependencies = [
"crossterm",
"indoc",
"instability",
"itertools",
"itertools 0.13.0",
"lru",
"paste",
"strum",
@ -1068,6 +1169,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.42"
@ -1192,6 +1299,17 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smart-default"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "socket2"
version = "0.5.8"
@ -1247,6 +1365,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sys-mount"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6acb8bb63826062d5a44b68298cf2e25b84bc151bc0c31c35a83b61f818682a"
dependencies = [
"bitflags",
"libc",
"loopdev-3",
"smart-default",
"thiserror",
"tracing",
]
[[package]]
name = "temp-dir"
version = "0.1.14"
@ -1266,6 +1398,26 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.42.0"
@ -1295,6 +1447,17 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
@ -1391,7 +1554,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools",
"itertools 0.13.0",
"unicode-segmentation",
"unicode-width 0.1.14",
]

View file

@ -4,7 +4,9 @@ version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.28.1"
crossterm = { version = "0.28.1", features = ["event-stream"]}
ratatui = "0.29.0"
sys-mount = "3.0.1"
tokio = {version = "1.42.0", features = ["full"]}
tokio-stream = "0.1.17"
udisks2 = "0.2.0"

View file

@ -1,4 +1,5 @@
use std::{
collections::HashMap,
ffi::{OsStr, OsString},
os::unix::ffi::{OsStrExt, OsStringExt},
sync::{Arc, Mutex},
@ -6,19 +7,20 @@ use std::{
use crate::mountpoints;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Block {
pub id: String,
pub object_path: String,
pub dev: String,
pub label: String,
pub mount: Option<String>,
pub fstype: String,
pub mounted: bool,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Drive {
pub id: String,
pub path: String,
pub object_path: String,
pub model: String,
pub ejectable: bool,
pub blocks: Vec<Block>,
@ -44,14 +46,14 @@ pub async fn collect_drives_from_udisk() -> udisks2::Result<Vec<Drive>> {
let path = path.to_string();
if let Ok(drv) = i.drive().await {
let drv = Drive {
path,
object_path: 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) {
if let Some(d) = drives.iter_mut().find(|i| i.object_path == drv.object_path) {
d.model = drv.model;
d.ejectable = drv.ejectable;
d.id = drv.id;
@ -61,7 +63,7 @@ pub async fn collect_drives_from_udisk() -> udisks2::Result<Vec<Drive>> {
} else if let Ok(blk) = i.block().await {
let drv_path = blk.drive().await?.to_string();
let block = Block {
id: blk.id().await?,
object_path: path,
dev: String::from_utf8_lossy(&blk.device().await?)
.chars()
.filter(|c| c != &'\0')
@ -69,13 +71,14 @@ pub async fn collect_drives_from_udisk() -> udisks2::Result<Vec<Drive>> {
label: blk.id_label().await?,
mount: None,
fstype: blk.id_type().await?,
mounted: false,
};
if let Some(d) = drives.iter_mut().find(|i| i.path == drv_path) {
if let Some(d) = drives.iter_mut().find(|i| i.object_path == drv_path) {
d.blocks.push(block);
} else {
drives.push(Drive {
path: drv_path,
object_path: drv_path,
id: String::new(),
model: String::new(),
ejectable: false,
@ -94,7 +97,7 @@ pub async fn collect_all() -> udisks2::Result<Vec<Drive>> {
let mut fstab = Drive {
id: "fstab".to_owned(),
path: "fstab".to_owned(),
object_path: "fstab".to_owned(),
model: "fstab".to_owned(),
ejectable: false,
blocks: Vec::new(),
@ -107,22 +110,55 @@ pub async fn collect_all() -> udisks2::Result<Vec<Drive>> {
.and_then(|d| d.blocks.iter_mut().find(|b| b.dev == i.dev));
if let Some(block) = block {
block.mount = i.path;
block.mounted = i.mounted;
} else {
fstab.blocks.push(Block {
id: i.dev.clone(),
object_path: String::new(),
dev: i.dev,
label: String::new(),
mount: i.path,
fstype: i.fs,
mounted: i.mounted,
});
}
}
drives.push(fstab);
drives.sort_by_cached_key(|b| b.path.clone());
drives.sort_by_cached_key(|b| b.object_path.clone());
for i in &mut drives {
i.blocks.sort_by_cached_key(|b| b.dev.clone());
}
return Ok(drives);
Ok(drives)
}
pub async fn mount(block: &Block) -> udisks2::Result<()> {
let mut drives: Vec<Drive> = Vec::new();
let client = udisks2::Client::new().await?;
client
.object(block.object_path.clone())?
.filesystem()
.await?
.mount(HashMap::new())
.await?;
// client.part
Ok(())
}
pub async fn unmount(block: &Block) -> udisks2::Result<()> {
let mut drives: Vec<Drive> = Vec::new();
let client = udisks2::Client::new().await?;
client
.object(block.object_path.clone())?
.filesystem()
.await?
.unmount(HashMap::new())
.await?;
// client.part
Ok(())
}

View file

@ -3,48 +3,109 @@ mod mountpoints;
use std::{
ffi::{OsStr, OsString},
io::stderr,
os::unix::ffi::{OsStrExt, OsStringExt},
sync::{Arc, Mutex},
sync::Arc,
time::Duration,
};
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use crossterm::{
event::{Event, EventStream, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use drives::Drive;
use mountpoints::MountPoint;
use ratatui::{
layout::{Alignment, Constraint, Layout},
prelude::CrosstermBackend,
style::Color,
text::{Line, Text},
widgets::{
Block, BorderType, Padding, Paragraph, Row, StatefulWidget, Table, TableState, Widget, Wrap,
},
Frame,
Frame, Terminal,
};
use tokio::sync::Mutex;
use tokio_stream::StreamExt;
enum Command {
None,
Mount(String),
Umount(String),
}
#[tokio::main]
async fn main() -> udisks2::Result<()> {
let mut terminal = ratatui::init();
enable_raw_mode().unwrap();
execute!(stderr(), EnterAlternateScreen).unwrap();
let mut terminal = Terminal::new(CrosstermBackend::new(stderr())).unwrap();
let mut ts = TableState::new();
let period = Duration::from_secs_f32(1.0 / 10.0);
let mut interval = tokio::time::interval(period);
let mut events = EventStream::new();
let state: Arc<Mutex<Vec<drives::Drive>>> = Arc::new(Mutex::new(Vec::new()));
let s = state.clone();
tokio::spawn(async move {
loop {
let drv = drives::collect_all().await?;
let drv = drives::collect_all().await.unwrap();
s.lock().await.clone_from(&drv);
tokio::time::sleep(Duration::from_millis(500)).await;
}
});
let mut output = String::new();
let mut last_status = String::new();
loop {
let drv = state.lock().await.clone();
let mut selected: Option<drives::Block> = None;
terminal
.draw(|f| draw(f, &mut ts, &drv))
.draw(|f| draw(f, &mut ts, &drv, &mut selected, &last_status))
.expect("failed to draw frame");
if let Event::Key(key) = event::read().unwrap() {
tokio::select! {
_ = interval.tick() => { },
Some(Ok(event)) = events.next() => {
if let Event::Key(key) = event {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Up => ts.select_previous(),
KeyCode::Down => ts.select_next(),
KeyCode::Esc => break,
KeyCode::Up | KeyCode::Char('k') => ts.select_previous(),
KeyCode::Down | KeyCode::Char('j') => ts.select_next(),
KeyCode::Char('m') => if let Some(b) = &selected {
last_status = format!("{:?}", drives::mount(b).await);
}
KeyCode::Char('u') => if let Some(b) = &selected {
last_status = format!("{:?}", drives::unmount(b).await);
}
KeyCode::Esc | KeyCode::Char('q') => break,
KeyCode::Enter => {
output = selected.unwrap().mount.unwrap();
break;
}
_ => {}
}
}
}
},
}
ratatui::restore();
}
disable_raw_mode().unwrap();
execute!(stderr(), LeaveAlternateScreen).unwrap();
println!("{output}");
Ok(())
}
fn draw(frame: &mut Frame, state: &mut TableState, drv: &[drives::Drive]) {
fn draw(
frame: &mut Frame,
state: &mut TableState,
drv: &[drives::Drive],
selected: &mut Option<drives::Block>,
last_status: &str,
) {
let text = Text::raw("Hello World!");
frame.render_widget(text, frame.area());
@ -61,12 +122,22 @@ fn draw(frame: &mut Frame, state: &mut TableState, drv: &[drives::Drive]) {
.border_style(Color::Yellow);
block.clone().render(layout[0], frame.buffer_mut());
let rows = drv.iter().flat_map(|d| &d.blocks).map(|i| {
let rows: Vec<drives::Block> = drv.iter().flat_map(|d| d.blocks.clone()).collect();
state
.selected()
.and_then(|n| rows.get(n).cloned())
.clone_into(selected);
let rows = rows.iter().map(|i| {
Row::new(vec![
i.dev.clone(),
i.label.clone(),
i.mount.clone().unwrap_or_default(),
"M".to_owned(),
match i.mounted {
true => "M".to_owned(),
false => "O".to_owned(),
},
])
});
let widths = [
@ -81,7 +152,9 @@ fn draw(frame: &mut Frame, state: &mut TableState, drv: &[drives::Drive]) {
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")
Paragraph::new(format!(
"j - UP, k - DOWN, l - Goto mountpoint, m - Mount, u - Unmount, e - Eject\n{selected:?} {last_status:?}"
))
.wrap(Wrap { trim: true }),
layout[1],
);

View file

@ -5,10 +5,22 @@ pub struct MountPoint {
pub dev: String,
pub path: Option<String>,
pub fs: String,
pub mounted: bool,
}
impl MountPoint {
pub fn collect_from_file(path: &str) -> Vec<MountPoint> {
const FSTYPE_IGNORE: [&str; 8] = [
"tmpfs",
"swap",
"devtmpfs",
"devpts",
"hugetlbfs",
"mqueue",
"fuse.portal",
"fuse.gvfsd-fuse",
];
const PATH_IGNORE: [&str; 3] = ["/tmp", "/sys", "/proc"];
std::io::BufReader::new(std::fs::File::open(PathBuf::from(path)).unwrap())
.lines()
.map_while(Result::ok)
@ -21,17 +33,16 @@ impl MountPoint {
.into(),
path: Some(parts.next()?.to_string()),
fs: parts.next()?.into(),
mounted: false,
})
})
.filter(|p| !FSTYPE_IGNORE.contains(&p.fs.as_str()))
.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")
})
if let Some(p) = &p.path {
!PATH_IGNORE.iter().any(|ignore| p.starts_with(ignore))
} else {
false
}
})
.collect()
}
@ -44,12 +55,14 @@ impl MountPoint {
.into_iter()
.filter(|p| !mnt.iter().any(|f| f.path == p.path))
.map(|p| MountPoint {
dev: p.dev,
path: None,
fs: p.fs,
mounted: false,
..p
})
.collect();
mnt.into_iter().chain(fstab).collect()
mnt.into_iter()
.map(|m| MountPoint { mounted: true, ..m })
.chain(fstab)
.collect()
}
}