working
This commit is contained in:
parent
db62ec1d88
commit
85b4ad55b3
5 changed files with 279 additions and 270 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -692,6 +692,12 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.27.0"
|
version = "0.27.0"
|
||||||
|
|
@ -1188,6 +1194,16 @@ version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.37.3"
|
version = "0.37.3"
|
||||||
|
|
@ -1263,6 +1279,7 @@ dependencies = [
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"md5",
|
"md5",
|
||||||
|
"num_cpus",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ console = "0.16.1"
|
||||||
crossterm = { version = "0.29.0", optional = true }
|
crossterm = { version = "0.29.0", optional = true }
|
||||||
indicatif = "0.18.0"
|
indicatif = "0.18.0"
|
||||||
md5 = "0.8.0"
|
md5 = "0.8.0"
|
||||||
|
num_cpus = "1.17.0"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
ratatui = { version = "0.29.0", optional = true }
|
ratatui = { version = "0.29.0", optional = true }
|
||||||
reqwest = { version = "0.12.23", features = ["blocking", "json"] }
|
reqwest = { version = "0.12.23", features = ["blocking", "json"] }
|
||||||
|
|
|
||||||
25
src/main.rs
25
src/main.rs
|
|
@ -1,6 +1,7 @@
|
||||||
mod downloader;
|
mod downloader;
|
||||||
mod md5_utils;
|
mod md5_utils;
|
||||||
mod mirrors;
|
mod mirrors;
|
||||||
|
mod version_check;
|
||||||
mod wget_list;
|
mod wget_list;
|
||||||
|
|
||||||
use console::style;
|
use console::style;
|
||||||
|
|
@ -10,11 +11,26 @@ use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// --- Run host system version checks ---
|
||||||
|
if version_check::run_version_checks() {
|
||||||
|
eprintln!(
|
||||||
|
"{} Host system does not meet minimum requirements. Exiting.",
|
||||||
|
style("❌").red().bold()
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{} All version checks passed. Starting downloader...",
|
||||||
|
style("✅").green().bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Determine LFS sources path ---
|
||||||
let lfs_sources = match env::var("LFS") {
|
let lfs_sources = match env::var("LFS") {
|
||||||
Ok(lfs) => PathBuf::from(lfs).join("sources"),
|
Ok(lfs) => PathBuf::from(lfs).join("sources"),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::thread_rng();
|
||||||
let random_number: u32 = rng.random_range(1000..=9999);
|
let random_number: u32 = rng.gen_range(1000..=9999);
|
||||||
let tmp_path = format!("/tmp/lfs_{}", random_number);
|
let tmp_path = format!("/tmp/lfs_{}", random_number);
|
||||||
println!(
|
println!(
|
||||||
"{} Using temporary path {}",
|
"{} Using temporary path {}",
|
||||||
|
|
@ -25,11 +41,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- Choose mirror and fetch wget list ---
|
||||||
let package_mirror = mirrors::choose_package_mirror();
|
let package_mirror = mirrors::choose_package_mirror();
|
||||||
|
|
||||||
let wget_list = wget_list::get_wget_list()?;
|
let wget_list = wget_list::get_wget_list()?;
|
||||||
|
|
||||||
// MD5 Map vorbereiten
|
// --- Prepare MD5 map ---
|
||||||
let mut md5_map: HashMap<String, String> = HashMap::new();
|
let mut md5_map: HashMap<String, String> = HashMap::new();
|
||||||
let md5_content = md5_utils::get_md5sums()?;
|
let md5_content = md5_utils::get_md5sums()?;
|
||||||
for line in md5_content.lines() {
|
for line in md5_content.lines() {
|
||||||
|
|
@ -39,6 +55,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Download files ---
|
||||||
downloader::download_files(&wget_list, &lfs_sources, package_mirror, Some(&md5_map))?;
|
downloader::download_files(&wget_list, &lfs_sources, package_mirror, Some(&md5_map))?;
|
||||||
|
|
||||||
println!("{} All done!", style("🎉").green().bold());
|
println!("{} All done!", style("🎉").green().bold());
|
||||||
|
|
|
||||||
372
src/tui.rs
372
src/tui.rs
|
|
@ -5,8 +5,6 @@ use crossterm::{
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
use rand::Rng;
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Terminal,
|
Terminal,
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
|
|
@ -17,59 +15,113 @@ use ratatui::{
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
use spinners::{Spinner, Spinners};
|
use spinners::{Spinner, Spinners};
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
use std::{
|
use std::io::{self, stdout};
|
||||||
collections::HashMap,
|
#[cfg(feature = "tui")]
|
||||||
env,
|
use std::path::PathBuf;
|
||||||
io::stdout,
|
#[cfg(feature = "tui")]
|
||||||
path::PathBuf,
|
use std::process::Command;
|
||||||
sync::{Arc, Mutex, mpsc::channel},
|
|
||||||
thread,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
use crate::{downloader, md5_utils, mirrors, wget_list};
|
use crate::{downloader, mirrors, wget_list};
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
fn init_environment() -> (PathBuf, String) {
|
fn init_environment() -> PathBuf {
|
||||||
match env::var("LFS") {
|
let tmp_path = "/tmp/lfs_tmp"; // Simplified for demo
|
||||||
Ok(lfs) => (
|
PathBuf::from(tmp_path).join("sources")
|
||||||
PathBuf::from(lfs).join("sources"),
|
|
||||||
"Using LFS environment path.".into(),
|
|
||||||
),
|
|
||||||
Err(_) => {
|
|
||||||
let mut rng = rand::rng();
|
|
||||||
let random_number: u32 = rng.random_range(1000..=9999);
|
|
||||||
let tmp_path = format!("/tmp/lfs_{}", random_number);
|
|
||||||
(
|
|
||||||
PathBuf::from(&tmp_path).join("sources"),
|
|
||||||
format!("Using temporary path {}", tmp_path),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
fn prepare_wget_list() -> Vec<String> {
|
fn download_packages(lfs_sources: &PathBuf) {
|
||||||
wget_list::get_wget_list()
|
let spinner = Spinner::new(Spinners::Dots9, "Downloading packages...".into());
|
||||||
.unwrap_or_default()
|
let wget_list = wget_list::get_wget_list().unwrap_or_default();
|
||||||
.lines()
|
let package_mirror =
|
||||||
.map(|s| s.to_string())
|
mirrors::choose_package_mirror().unwrap_or_else(|| "ftp.fau.de".to_string());
|
||||||
.collect()
|
|
||||||
|
// Simplified download call
|
||||||
|
let _ = downloader::download_files(&wget_list, lfs_sources, Some(package_mirror), None);
|
||||||
|
|
||||||
|
spinner.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
fn prepare_md5_map() -> HashMap<String, String> {
|
fn format_drive_tui() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut map = HashMap::new();
|
// Mocked drive list for demo
|
||||||
if let Ok(md5_content) = md5_utils::get_md5sums() {
|
let drives = vec!["/dev/sda".to_string(), "/dev/sdb".to_string()];
|
||||||
for line in md5_content.lines() {
|
let mut state = ListState::default();
|
||||||
let mut parts = line.split_whitespace();
|
state.select(Some(0));
|
||||||
if let (Some(hash), Some(filename)) = (parts.next(), parts.next()) {
|
|
||||||
map.insert(filename.to_string(), hash.to_string());
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| {
|
||||||
|
let size = f.size();
|
||||||
|
let block = Block::default()
|
||||||
|
.title("💾 Format Drive")
|
||||||
|
.borders(Borders::ALL);
|
||||||
|
f.render_widget(block, size);
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(2)
|
||||||
|
.constraints(vec![Constraint::Length(3); drives.len()])
|
||||||
|
.split(size);
|
||||||
|
|
||||||
|
for (i, drive) in drives.iter().enumerate() {
|
||||||
|
let mut style = Style::default();
|
||||||
|
if Some(i) == state.selected() {
|
||||||
|
style = style.bg(Color::Red).fg(Color::White);
|
||||||
|
}
|
||||||
|
let list_item = ListItem::new(drive.clone()).style(style);
|
||||||
|
let list = List::new(vec![list_item]).block(Block::default().borders(Borders::ALL));
|
||||||
|
f.render_widget(list, chunks[i]);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Down => {
|
||||||
|
let i = state.selected().unwrap_or(0);
|
||||||
|
if i < drives.len() - 1 {
|
||||||
|
state.select(Some(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
let i = state.selected().unwrap_or(0);
|
||||||
|
if i > 0 {
|
||||||
|
state.select(Some(i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
if let Some(idx) = state.selected() {
|
||||||
|
let drive = &drives[idx];
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
|
println!("⚠️ Confirm formatting {}? (y/n)", drive);
|
||||||
|
let mut input = String::new();
|
||||||
|
io::stdin().read_line(&mut input)?;
|
||||||
|
if matches!(input.trim().to_lowercase().as_str(), "y" | "yes") {
|
||||||
|
println!("Formatting {}...", drive);
|
||||||
|
let _ = Command::new("mkfs.ext4").arg(drive).status();
|
||||||
|
println!("✅ Done!");
|
||||||
|
}
|
||||||
|
enable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), EnterAlternateScreen)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Esc => break,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
map
|
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
|
|
@ -77,244 +129,82 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut stdout = stdout();
|
let mut stdout = stdout();
|
||||||
execute!(stdout, EnterAlternateScreen)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
let result = (|| -> Result<(), Box<dyn std::error::Error>> {
|
let menu_items = vec![
|
||||||
let menu_items = [
|
|
||||||
"🌱 Init environment",
|
"🌱 Init environment",
|
||||||
"🌐 Select mirror",
|
|
||||||
"📦 Download packages",
|
"📦 Download packages",
|
||||||
"🔍 Check status",
|
"💾 Format drive",
|
||||||
"❌ Exit",
|
"❌ Exit",
|
||||||
];
|
];
|
||||||
let mut state = ListState::default();
|
let mut state = ListState::default();
|
||||||
state.select(Some(0));
|
state.select(Some(0));
|
||||||
|
|
||||||
let mut lfs_sources: Option<PathBuf> = None;
|
let mut lfs_sources: Option<PathBuf> = None;
|
||||||
let mut mirrors_list: Vec<String> = Vec::new();
|
|
||||||
let mut selected_mirror: Option<String> = None;
|
|
||||||
let log_messages: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
let progress_state: Arc<Mutex<HashMap<String, Option<Spinner>>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
|
|
||||||
let (tx, rx) = channel::<String>();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
while let Ok(msg) = rx.try_recv() {
|
|
||||||
log_messages.lock().unwrap().push(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.draw(|f| {
|
terminal.draw(|f| {
|
||||||
let size = f.area();
|
let size = f.size();
|
||||||
|
let block = Block::default()
|
||||||
|
.title("✨ lpkg TUI 🌈")
|
||||||
|
.borders(Borders::ALL);
|
||||||
|
f.render_widget(block, size);
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(1)
|
.margin(2)
|
||||||
.constraints(
|
.constraints(vec![Constraint::Length(3); menu_items.len()])
|
||||||
vec![Constraint::Length(3); menu_items.len()]
|
|
||||||
.into_iter()
|
|
||||||
.chain(vec![Constraint::Min(5)])
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.split(size);
|
.split(size);
|
||||||
|
|
||||||
for (i, item) in menu_items.iter().enumerate() {
|
for (i, item) in menu_items.iter().enumerate() {
|
||||||
let style = if Some(i) == state.selected() {
|
let mut style = Style::default();
|
||||||
Style::default().bg(Color::Blue).fg(Color::White)
|
if Some(i) == state.selected() {
|
||||||
} else {
|
style = style.bg(Color::Red).fg(Color::White);
|
||||||
Style::default()
|
}
|
||||||
};
|
|
||||||
let list_item = ListItem::new(*item).style(style);
|
let list_item = ListItem::new(*item).style(style);
|
||||||
f.render_widget(
|
let list = List::new(vec![list_item]).block(Block::default().borders(Borders::ALL));
|
||||||
List::new(vec![list_item]).block(Block::default().borders(Borders::ALL)),
|
f.render_widget(list, chunks[i]);
|
||||||
chunks[i],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let logs = log_messages.lock().unwrap();
|
|
||||||
let mut combined_logs: Vec<ListItem> = logs
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.take(chunks.last().unwrap().height as usize - 2)
|
|
||||||
.map(|l| ListItem::new(l.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let progress = progress_state.lock().unwrap();
|
|
||||||
for (file, spinner_opt) in progress.iter() {
|
|
||||||
let display_status = if let Some(spinner) = spinner_opt {
|
|
||||||
spinner.to_string()
|
|
||||||
} else {
|
|
||||||
"✅ Done".to_string()
|
|
||||||
};
|
|
||||||
combined_logs.push(ListItem::new(format!("{}: {}", file, display_status)));
|
|
||||||
}
|
|
||||||
|
|
||||||
f.render_widget(
|
|
||||||
List::new(combined_logs)
|
|
||||||
.block(Block::default().title("Logs").borders(Borders::ALL)),
|
|
||||||
*chunks.last().unwrap(),
|
|
||||||
);
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if event::poll(Duration::from_millis(100))? {
|
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Down => state
|
KeyCode::Down => {
|
||||||
.select(state.selected().map(|i| (i + 1).min(menu_items.len() - 1))),
|
let i = state.selected().unwrap_or(0);
|
||||||
KeyCode::Up => state.select(state.selected().map(|i| i.saturating_sub(1))),
|
if i < menu_items.len() - 1 {
|
||||||
|
state.select(Some(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
let i = state.selected().unwrap_or(0);
|
||||||
|
if i > 0 {
|
||||||
|
state.select(Some(i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
KeyCode::Enter => match state.selected() {
|
KeyCode::Enter => match state.selected() {
|
||||||
Some(0) => {
|
Some(0) => lfs_sources = Some(init_environment()),
|
||||||
let (path, msg) = init_environment();
|
|
||||||
lfs_sources = Some(path);
|
|
||||||
log_messages.lock().unwrap().push(msg);
|
|
||||||
}
|
|
||||||
Some(1) => {
|
Some(1) => {
|
||||||
if mirrors_list.is_empty() {
|
if let Some(ref path) = lfs_sources {
|
||||||
mirrors_list = mirrors::fetch_mirrors().unwrap_or_else(|_| {
|
download_packages(path);
|
||||||
vec![
|
} else {
|
||||||
"ftp.fau.de".to_string(),
|
println!("⚠️ Please initialize environment first!");
|
||||||
"mirror.kernel.org".to_string(),
|
|
||||||
"mirror.example.org".to_string(),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mirror_state = ListState::default();
|
|
||||||
mirror_state.select(Some(0));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
terminal.draw(|f| {
|
|
||||||
let size = f.area();
|
|
||||||
let mirror_items: Vec<ListItem> = mirrors_list
|
|
||||||
.iter()
|
|
||||||
.map(|m| ListItem::new(m.clone()))
|
|
||||||
.collect();
|
|
||||||
f.render_widget(
|
|
||||||
List::new(mirror_items)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Select Mirror")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.highlight_style(
|
|
||||||
Style::default()
|
|
||||||
.bg(Color::Blue)
|
|
||||||
.fg(Color::White),
|
|
||||||
),
|
|
||||||
size,
|
|
||||||
);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Event::Key(k) = event::read()? {
|
|
||||||
match k.code {
|
|
||||||
KeyCode::Down => mirror_state.select(
|
|
||||||
mirror_state
|
|
||||||
.selected()
|
|
||||||
.map(|i| (i + 1).min(mirrors_list.len() - 1)),
|
|
||||||
),
|
|
||||||
KeyCode::Up => mirror_state.select(
|
|
||||||
mirror_state
|
|
||||||
.selected()
|
|
||||||
.map(|i| i.saturating_sub(1)),
|
|
||||||
),
|
|
||||||
KeyCode::Enter => {
|
|
||||||
if let Some(idx) = mirror_state.selected() {
|
|
||||||
selected_mirror =
|
|
||||||
Some(mirrors_list[idx].clone());
|
|
||||||
log_messages.lock().unwrap().push(format!(
|
|
||||||
"Selected mirror: {}",
|
|
||||||
mirrors_list[idx]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Esc => break,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(2) => {
|
Some(2) => {
|
||||||
if let Some(ref path) = lfs_sources {
|
format_drive_tui()?;
|
||||||
let mirror = selected_mirror
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "ftp.fau.de".to_string());
|
|
||||||
let wget_list = prepare_wget_list();
|
|
||||||
let md5_map = prepare_md5_map();
|
|
||||||
|
|
||||||
if wget_list.is_empty() {
|
|
||||||
log_messages
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push("⚠️ No packages to download!".into());
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
Some(3) | _ => break,
|
||||||
let progress_clone = Arc::clone(&progress_state);
|
|
||||||
let tx_clone = tx.clone();
|
|
||||||
let path_clone = path.clone();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
for file in wget_list {
|
|
||||||
let spinner = Spinner::new(
|
|
||||||
Spinners::Dots9,
|
|
||||||
format!("Downloading {}", file),
|
|
||||||
);
|
|
||||||
progress_clone
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(file.clone(), Some(spinner));
|
|
||||||
|
|
||||||
let result = downloader::download_files(
|
|
||||||
&file,
|
|
||||||
&path_clone,
|
|
||||||
Some(mirror.clone()),
|
|
||||||
Some(&md5_map),
|
|
||||||
);
|
|
||||||
progress_clone
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(file.clone(), None);
|
|
||||||
|
|
||||||
let status_msg = match result {
|
|
||||||
Ok(_) => format!("✅ {}", file),
|
|
||||||
Err(_) => format!("❌ {}", file),
|
|
||||||
};
|
|
||||||
let _ = tx_clone.send(status_msg);
|
|
||||||
}
|
|
||||||
let _ = tx_clone.send("🎉 All downloads complete!".into());
|
|
||||||
});
|
|
||||||
|
|
||||||
log_messages
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push("⬇️ Download started...".into());
|
|
||||||
} else {
|
|
||||||
log_messages
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push("⚠️ Initialize environment first!".into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(3) => log_messages
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push("🔍 Status check (TODO)".into()),
|
|
||||||
Some(4) => break,
|
|
||||||
Some(_) | None => break,
|
|
||||||
},
|
},
|
||||||
KeyCode::Esc => break,
|
KeyCode::Esc => break,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})();
|
|
||||||
|
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
terminal.show_cursor()?;
|
terminal.show_cursor()?;
|
||||||
result
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
84
src/version_check.rs
Normal file
84
src/version_check.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
use std::process::Command;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
|
||||||
|
let output = Command::new(cmd).args(args).output().ok()?;
|
||||||
|
if output.status.success() {
|
||||||
|
Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_version(installed: &str, required: &str) -> bool {
|
||||||
|
let parse_ver = |v: &str| {
|
||||||
|
v.split(|c| c == '.' || c == '-')
|
||||||
|
.filter_map(|s| s.parse::<u32>().ok())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
let i = parse_ver(installed);
|
||||||
|
let r = parse_ver(required);
|
||||||
|
|
||||||
|
for (a, b) in i.iter().zip(r.iter()) {
|
||||||
|
if a > b {
|
||||||
|
return true;
|
||||||
|
} else if a < b {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.len() >= r.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ver_check(program: &str, arg: &str, min_version: &str) -> bool {
|
||||||
|
match run_command(program, &[arg]) {
|
||||||
|
Some(output) => {
|
||||||
|
let ver = output
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.unwrap_or("")
|
||||||
|
.split_whitespace()
|
||||||
|
.last()
|
||||||
|
.unwrap_or("");
|
||||||
|
if check_version(ver, min_version) {
|
||||||
|
println!("OK: {:<12} {:<8} >= {}", program, ver, min_version);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"ERROR: {:<12} version {} is too old ({} required)",
|
||||||
|
program, ver, min_version
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("ERROR: Cannot find {}", program);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_version_checks() -> bool {
|
||||||
|
let mut ok = true;
|
||||||
|
|
||||||
|
ok &= ver_check("bash", "--version", "3.2");
|
||||||
|
ok &= ver_check("gcc", "--version", "5.4");
|
||||||
|
ok &= ver_check("make", "--version", "4.0");
|
||||||
|
ok &= ver_check("tar", "--version", "1.22");
|
||||||
|
|
||||||
|
// Kernel check
|
||||||
|
if let Some(kernel) = run_command("uname", &["-r"]) {
|
||||||
|
if check_version(&kernel, "5.4") {
|
||||||
|
println!("OK: Linux Kernel {} >= 5.4", kernel);
|
||||||
|
} else {
|
||||||
|
println!("ERROR: Linux Kernel {} is too old (5.4 required)", kernel);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU cores
|
||||||
|
let cores = num_cpus::get();
|
||||||
|
println!("OK: {} logical cores available", cores);
|
||||||
|
|
||||||
|
ok
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue