This commit is contained in:
Lucy 2025-09-30 19:51:26 +02:00
parent bc3560f1f8
commit 23905877c7
3 changed files with 162 additions and 60 deletions

View file

@ -4,6 +4,9 @@ mod mirrors;
mod version_check; mod version_check;
mod wget_list; mod wget_list;
#[cfg(feature = "tui")]
mod tui; // Importiere das TUI-Modul, wenn das Feature aktiv ist
use console::style; use console::style;
use rand::Rng; use rand::Rng;
use std::collections::HashMap; use std::collections::HashMap;
@ -11,6 +14,16 @@ 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>> {
#[cfg(feature = "tui")]
{
// Wenn das TUI-Feature aktiv ist, starte das TUI-Menü
tui::tui_menu()?;
Ok(())
}
#[cfg(not(feature = "tui"))]
{
// Wenn das TUI-Feature NICHT aktiv ist, führe die CLI-Logik aus
// --- Run host system version checks --- // --- Run host system version checks ---
if version_check::run_version_checks() { if version_check::run_version_checks() {
eprintln!( eprintln!(
@ -29,8 +42,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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(); // Verwende thread_rng() statt rng()
let random_number: u32 = rng.random_range(1000..=9999); let random_number: u32 = rng.gen_range(1000..=9999); // Verwende gen_range() statt random_range()
let tmp_path = format!("/tmp/lfs_{}", random_number); let tmp_path = format!("/tmp/lfs_{}", random_number);
println!( println!(
"{} Using temporary path {}", "{} Using temporary path {}",
@ -42,7 +55,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}; };
// --- Choose mirror and fetch wget list --- // --- Choose mirror and fetch wget list ---
let package_mirror = mirrors::choose_package_mirror(); // Diese Zeile wird entfernt, da die Mirror-Auswahl in der TUI erfolgt
// let package_mirror = mirrors::choose_package_mirror();
// Da die Mirror-Auswahl nun in der TUI erfolgt, müssen wir hier einen Standardwert oder eine andere Logik verwenden,
// wenn das TUI nicht aktiv ist. Für dieses Beispiel nehmen wir an, dass wir keinen Mirror verwenden,
// wenn das TUI nicht aktiv ist, oder wir könnten eine andere CLI-basierte Auswahl implementieren.
// Für den Moment setzen wir es auf None, was bedeutet, dass der Standard-Mirror verwendet wird.
let package_mirror: Option<String> = None;
let wget_list = wget_list::get_wget_list()?; let wget_list = wget_list::get_wget_list()?;
// --- Prepare MD5 map --- // --- Prepare MD5 map ---
@ -61,3 +83,4 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{} All done!", style("🎉").green().bold()); println!("{} All done!", style("🎉").green().bold());
Ok(()) Ok(())
} }
}

View file

@ -43,7 +43,7 @@ pub fn choose_package_mirror() -> Option<String> {
} }
} }
fn fetch_mirrors() -> Result<Vec<String>, Box<dyn std::error::Error>> { pub fn fetch_mirrors() -> Result<Vec<String>, Box<dyn std::error::Error>> {
let client = Client::new(); let client = Client::new();
let res = client let res = client
.get("https://www.linuxfromscratch.org/lfs/mirrors.html#files") .get("https://www.linuxfromscratch.org/lfs/mirrors.html#files")

View file

@ -12,23 +12,31 @@ use ratatui::{
backend::CrosstermBackend, backend::CrosstermBackend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::{Color, Style}, style::{Color, Style},
widgets::{Block, Borders, List, ListItem, ListState}, widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
}; };
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::{self, stdout}, io::stdout,
path::PathBuf, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread, thread,
time::Duration, time::{Duration, Instant},
fs, // Added for file system operations
}; };
#[cfg(feature = "tui")] // Hinzugefügt: Import des Rng-Traits
use rand::Rng;
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
fn init_environment() -> PathBuf { fn init_environment() -> Result<PathBuf, Box<dyn std::error::Error>> {
let tmp_path = format!("/tmp/lfs_{}", rand::random::<u32>() % 9000 + 1000); let mut rng = rand::rng();
println!(" Using temporary path {}", tmp_path); let random_number: u32 = rng.gen_range(1000..=9999);
PathBuf::from(tmp_path).join("sources") let tmp_base_path = PathBuf::from(format!("/tmp/lfs_{}", random_number));
let lfs_sources_path = tmp_base_path.join("sources");
std::fs::create_dir_all(&lfs_sources_path)?;
Ok(lfs_sources_path)
} }
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
@ -50,7 +58,7 @@ fn select_mirrors_tui(mirrors: Vec<String>) -> Vec<String> {
loop { loop {
terminal terminal
.draw(|f| { .draw(|f| {
let size = f.size(); let size = f.area();
let items: Vec<ListItem> = mirrors let items: Vec<ListItem> = mirrors
.iter() .iter()
.enumerate() .enumerate()
@ -113,13 +121,12 @@ fn download_packages_tui(lfs_sources: &PathBuf) {
let mirrors_list = mirrors::fetch_mirrors().unwrap_or_default(); let mirrors_list = mirrors::fetch_mirrors().unwrap_or_default();
let selected_mirrors = select_mirrors_tui(mirrors_list); let selected_mirrors = select_mirrors_tui(mirrors_list);
if selected_mirrors.is_empty() { if selected_mirrors.is_empty() {
println!("⚠️ No mirrors selected!");
return; return;
} }
let wget_list = wget_list::get_wget_list().unwrap_or_default(); let wget_list_content = wget_list::get_wget_list().unwrap_or_default();
let wget_list: Vec<String> = wget_list_content.lines().map(|s| s.to_string()).collect();
if wget_list.is_empty() { if wget_list.is_empty() {
println!("⚠️ No packages to download!");
return; return;
} }
@ -175,7 +182,7 @@ fn download_packages_tui(lfs_sources: &PathBuf) {
loop { loop {
terminal terminal
.draw(|f| { .draw(|f| {
let size = f.size(); let size = f.area();
let items: Vec<ListItem> = { let items: Vec<ListItem> = {
let state = download_state.lock().unwrap(); let state = download_state.lock().unwrap();
state state
@ -201,7 +208,6 @@ fn download_packages_tui(lfs_sources: &PathBuf) {
disable_raw_mode().unwrap(); disable_raw_mode().unwrap();
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap(); execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
println!("🎉 All downloads finished!");
} }
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
@ -216,16 +222,19 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
"🌱 Init environment", "🌱 Init environment",
"📦 Download packages", "📦 Download packages",
"🔍 Check status", "🔍 Check status",
"🧹 Clean up temp directories", // Neuer Menüpunkt
"❌ 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 status_message: Option<String> = None; // Für Statusmeldungen
let mut status_message_timer: Option<std::time::Instant> = None; // Für temporäre Meldungen
loop { loop {
terminal.draw(|f| { terminal.draw(|f| {
let size = f.size(); let size = f.area();
let block = Block::default() let block = Block::default()
.title("✨ lpkg TUI 🌈") .title("✨ lpkg TUI 🌈")
.borders(Borders::ALL); .borders(Borders::ALL);
@ -246,8 +255,35 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
let list = List::new(vec![list_item]).block(Block::default().borders(Borders::ALL)); let list = List::new(vec![list_item]).block(Block::default().borders(Borders::ALL));
f.render_widget(list, chunks[i]); f.render_widget(list, chunks[i]);
} }
// Statusmeldung anzeigen
if let Some(msg) = &status_message {
let msg_block = Block::default()
.borders(Borders::NONE)
.style(Style::default().fg(Color::Yellow));
let paragraph = Paragraph::new(msg.as_str()).block(msg_block);
// Positionierung der Statusmeldung (z.B. unten in der Mitte)
let msg_area = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(10), Constraint::Percentage(80), Constraint::Percentage(10)])
.split(size)[1]; // Mittlerer Bereich
let msg_area = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(1)])
.split(msg_area)[1]; // Unterste Zeile
f.render_widget(paragraph, msg_area);
}
})?; })?;
// Statusmeldung nach einer bestimmten Zeit ausblenden
if let Some(timer) = status_message_timer {
if timer.elapsed() > Duration::from_secs(3) { // 3 Sekunden anzeigen
status_message = None;
status_message_timer = None;
}
}
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
match key.code { match key.code {
KeyCode::Down => { KeyCode::Down => {
@ -264,19 +300,44 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
} }
KeyCode::Enter => match state.selected() { KeyCode::Enter => match state.selected() {
Some(0) => { Some(0) => {
lfs_sources = Some(init_environment()); match init_environment() {
Ok(path) => {
lfs_sources = Some(path.clone());
status_message = Some(format!("✅ Environment initialized: {}", path.display()));
}
Err(e) => {
status_message = Some(format!("❌ Failed to initialize environment: {}", e));
}
}
status_message_timer = Some(std::time::Instant::now());
} }
Some(1) => { Some(1) => {
if let Some(ref path) = lfs_sources { if let Some(ref path) = lfs_sources {
download_packages_tui(path); download_packages_tui(path);
} else { } else {
println!("⚠️ Please initialize environment first!"); status_message = Some("⚠️ Please initialize environment first!".to_string());
status_message_timer = Some(std::time::Instant::now());
} }
} }
Some(2) => { Some(2) => {
println!("🔍 Status selected! (TODO)"); status_message = Some("🔍 Status selected! (TODO)".to_string());
status_message_timer = Some(std::time::Instant::now());
} }
Some(3) | _ => break, Some(3) => {
// Aufruf der neuen Bereinigungsfunktion
status_message = Some("🧹 Cleaning up temporary directories...".to_string());
status_message_timer = Some(std::time::Instant::now());
match cleanup_temp_directories() {
Ok(count) => {
status_message = Some(format!("✅ Cleaned up {} temporary directories.", count));
}
Err(e) => {
status_message = Some(format!("❌ Failed to clean up: {}", e));
}
}
status_message_timer = Some(std::time::Instant::now());
}
Some(4) | _ => break,
}, },
KeyCode::Esc => break, KeyCode::Esc => break,
_ => {} _ => {}
@ -289,3 +350,21 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
terminal.show_cursor()?; terminal.show_cursor()?;
Ok(()) Ok(())
} }
#[cfg(feature = "tui")]
fn cleanup_temp_directories() -> Result<usize, Box<dyn std::error::Error>> {
let mut cleaned_count = 0;
for entry in std::fs::read_dir("/tmp")? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if let Some(dir_name) = path.file_name().and_then(|s| s.to_str()) {
if dir_name.starts_with("lfs_") {
std::fs::remove_dir_all(&path)?;
cleaned_count += 1;
}
}
}
}
Ok(cleaned_count)
}