working
This commit is contained in:
parent
23905877c7
commit
208234df27
7 changed files with 478 additions and 127 deletions
217
Cargo.lock
generated
217
Cargo.lock
generated
|
|
@ -30,6 +30,15 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
|
|
@ -131,6 +140,15 @@ version = "2.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
|
|
@ -278,6 +296,15 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
|
|
@ -321,6 +348,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.31.2"
|
||||
|
|
@ -411,6 +448,16 @@ dependencies = [
|
|||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
|
@ -422,6 +469,12 @@ dependencies = [
|
|||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.11"
|
||||
|
|
@ -606,6 +659,16 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.24"
|
||||
|
|
@ -712,6 +775,21 @@ dependencies = [
|
|||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html_parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
|
|
@ -1277,14 +1355,17 @@ dependencies = [
|
|||
"clap",
|
||||
"console",
|
||||
"crossterm 0.29.0",
|
||||
"html_parser",
|
||||
"indicatif",
|
||||
"md5",
|
||||
"num_cpus",
|
||||
"rand 0.9.2",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"scraper",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spinners",
|
||||
]
|
||||
|
||||
|
|
@ -1323,6 +1404,50 @@ version = "2.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.17",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
|
|
@ -1574,6 +1699,35 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.23"
|
||||
|
|
@ -1844,6 +1998,17 @@ dependencies = [
|
|||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -2105,6 +2270,46 @@ dependencies = [
|
|||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.17",
|
||||
]
|
||||
|
||||
[[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 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
|
|
@ -2235,6 +2440,18 @@ version = "0.2.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
|
|
|
|||
|
|
@ -8,15 +8,18 @@ anyhow = "1.0.100"
|
|||
clap = { version = "4.5.48", features= ["derive"] }
|
||||
console = "0.16.1"
|
||||
crossterm = { version = "0.29.0", optional = true }
|
||||
html_parser = "0.7.0"
|
||||
indicatif = "0.18.0"
|
||||
md5 = "0.8.0"
|
||||
num_cpus = "1.17.0"
|
||||
rand = "0.9.2"
|
||||
ratatui = { version = "0.29.0", optional = true }
|
||||
regex = "1.11.3"
|
||||
reqwest = { version = "0.12.23", features = ["blocking", "json"] }
|
||||
scraper = "0.19.0"
|
||||
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
spinners = "4.1.1"
|
||||
|
||||
[features]
|
||||
|
|
|
|||
33
src/html.rs
Normal file
33
src/html.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use html_parser::Dom;
|
||||
use reqwest::blocking::get;
|
||||
use std::error::Error;
|
||||
|
||||
/// Lädt die HTML-Seite von der angegebenen URL herunter und konvertiert sie in JSON
|
||||
pub fn fetch_and_parse_html_to_json(url: &str) -> Result<String, Box<dyn Error>> {
|
||||
// HTML herunterladen
|
||||
let response = get(url)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Fehler beim Abrufen der URL {}: {}", url, response.status()).into());
|
||||
}
|
||||
|
||||
let body = response.text()?;
|
||||
|
||||
// HTML parsen
|
||||
let dom = Dom::parse(&body)?;
|
||||
|
||||
// In JSON konvertieren
|
||||
let json = dom.to_json_pretty()?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fetch_and_parse() {
|
||||
let url = "https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html";
|
||||
let json = fetch_and_parse_html_to_json(url).expect("Fehler beim Parsen");
|
||||
assert!(json.contains("Host System Requirements"));
|
||||
}
|
||||
}
|
||||
40
src/main.rs
40
src/main.rs
|
|
@ -1,11 +1,12 @@
|
|||
mod downloader;
|
||||
mod html;
|
||||
mod md5_utils;
|
||||
mod mirrors;
|
||||
mod version_check;
|
||||
mod wget_list;
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
mod tui; // Importiere das TUI-Modul, wenn das Feature aktiv ist
|
||||
mod tui;
|
||||
|
||||
use console::style;
|
||||
use rand::Rng;
|
||||
|
|
@ -16,34 +17,30 @@ use std::path::PathBuf;
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "tui")]
|
||||
{
|
||||
// Wenn das TUI-Feature aktiv ist, starte das TUI-Menü
|
||||
// TUI-Modus
|
||||
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 ---
|
||||
if version_check::run_version_checks() {
|
||||
eprintln!(
|
||||
"{} Host system does not meet minimum requirements. Exiting.",
|
||||
style("❌").red().bold()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
// --- Dynamische Version-Prüfung aus HTML ---
|
||||
let json = html::fetch_and_parse_html_to_json(
|
||||
"https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html",
|
||||
)?;
|
||||
version_check::run_version_checks_from_json(&json);
|
||||
|
||||
println!(
|
||||
"{} All version checks passed. Starting downloader...",
|
||||
style("✅").green().bold()
|
||||
);
|
||||
|
||||
// --- Determine LFS sources path ---
|
||||
// --- Bestimme LFS-Sources-Pfad ---
|
||||
let lfs_sources = match env::var("LFS") {
|
||||
Ok(lfs) => PathBuf::from(lfs).join("sources"),
|
||||
Err(_) => {
|
||||
let mut rng = rand::thread_rng(); // Verwende thread_rng() statt rng()
|
||||
let random_number: u32 = rng.gen_range(1000..=9999); // Verwende gen_range() statt random_range()
|
||||
let mut rng = rand::thread_rng();
|
||||
let random_number: u32 = rng.gen_range(1000..=9999);
|
||||
let tmp_path = format!("/tmp/lfs_{}", random_number);
|
||||
println!(
|
||||
"{} Using temporary path {}",
|
||||
|
|
@ -54,20 +51,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
};
|
||||
|
||||
// --- Choose mirror and fetch wget list ---
|
||||
// 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.
|
||||
// --- CLI Mirror-Auswahl: default oder erweiterbar ---
|
||||
let package_mirror: Option<String> = None;
|
||||
|
||||
|
||||
// --- Hole wget-Liste ---
|
||||
let wget_list = wget_list::get_wget_list()?;
|
||||
|
||||
// --- Prepare MD5 map ---
|
||||
// --- Bereite MD5-Map vor ---
|
||||
let mut md5_map: HashMap<String, String> = HashMap::new();
|
||||
let md5_content = md5_utils::get_md5sums()?;
|
||||
for line in md5_content.lines() {
|
||||
|
|
@ -77,7 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Download files ---
|
||||
// --- Lade Dateien herunter ---
|
||||
downloader::download_files(&wget_list, &lfs_sources, package_mirror, Some(&md5_map))?;
|
||||
|
||||
println!("{} All done!", style("🎉").green().bold());
|
||||
|
|
|
|||
|
|
@ -3,6 +3,29 @@ use reqwest::blocking::Client;
|
|||
use scraper::{Html, Selector};
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub fn fetch_mirrors() -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let client = Client::new();
|
||||
let res = client
|
||||
.get("https://www.linuxfromscratch.org/lfs/mirrors.html#files")
|
||||
.send()?
|
||||
.text()?;
|
||||
let document = Html::parse_document(&res);
|
||||
let selector = Selector::parse("a[href^='http']").unwrap();
|
||||
let mirrors = document
|
||||
.select(&selector)
|
||||
.filter_map(|element| {
|
||||
let href = element.value().attr("href")?;
|
||||
// Basic filtering to get potential mirror URLs
|
||||
if href.contains("ftp.gnu.org") || href.contains("mirror") {
|
||||
Some(href.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(mirrors)
|
||||
}
|
||||
|
||||
pub fn choose_package_mirror() -> Option<String> {
|
||||
let mirrors = match fetch_mirrors() {
|
||||
Ok(mirrors) => mirrors,
|
||||
|
|
@ -42,26 +65,3 @@ pub fn choose_package_mirror() -> Option<String> {
|
|||
Some(chosen.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_mirrors() -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
let client = Client::new();
|
||||
let res = client
|
||||
.get("https://www.linuxfromscratch.org/lfs/mirrors.html#files")
|
||||
.send()?
|
||||
.text()?;
|
||||
let document = Html::parse_document(&res);
|
||||
let selector = Selector::parse("a[href^='http']").unwrap();
|
||||
let mirrors = document
|
||||
.select(&selector)
|
||||
.filter_map(|element| {
|
||||
let href = element.value().attr("href")?;
|
||||
// Basic filtering to get potential mirror URLs
|
||||
if href.contains("ftp.gnu.org") || href.contains("mirror") {
|
||||
Some(href.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(mirrors)
|
||||
}
|
||||
|
|
|
|||
163
src/tui.rs
163
src/tui.rs
|
|
@ -1,4 +1,6 @@
|
|||
#[cfg(feature = "tui")]
|
||||
use crate::html::fetch_and_parse_html_to_json;
|
||||
#[cfg(feature = "tui")]
|
||||
use crate::{downloader, md5_utils, mirrors, wget_list};
|
||||
#[cfg(feature = "tui")]
|
||||
use crossterm::{
|
||||
|
|
@ -7,6 +9,8 @@ use crossterm::{
|
|||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
#[cfg(feature = "tui")]
|
||||
use rand::Rng;
|
||||
#[cfg(feature = "tui")]
|
||||
use ratatui::{
|
||||
Terminal,
|
||||
backend::CrosstermBackend,
|
||||
|
|
@ -17,25 +21,47 @@ use ratatui::{
|
|||
#[cfg(feature = "tui")]
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
io::stdout,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
fs, // Added for file system operations
|
||||
};
|
||||
#[cfg(feature = "tui")] // Hinzugefügt: Import des Rng-Traits
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
use reqwest::blocking::get;
|
||||
#[cfg(feature = "tui")]
|
||||
use scraper::{Html, Selector};
|
||||
#[cfg(feature = "tui")]
|
||||
use serde_json::json;
|
||||
|
||||
// ----------------- HTML fetch function -----------------
|
||||
#[cfg(feature = "tui")]
|
||||
fn fetch_html_to_json(url: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let body = get(url)?.text()?;
|
||||
let document = Html::parse_document(&body);
|
||||
|
||||
let selector = Selector::parse("body").unwrap();
|
||||
let mut contents = vec![];
|
||||
for element in document.select(&selector) {
|
||||
contents.push(element.text().collect::<Vec<_>>().join(" "));
|
||||
}
|
||||
|
||||
Ok(serde_json::to_string_pretty(
|
||||
&json!({ "body_text": contents }),
|
||||
)?)
|
||||
}
|
||||
|
||||
// ----------------- TUI functions -----------------
|
||||
#[cfg(feature = "tui")]
|
||||
fn init_environment() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let mut rng = rand::rng();
|
||||
let mut rng = rand::thread_rng();
|
||||
let random_number: u32 = rng.gen_range(1000..=9999);
|
||||
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)?;
|
||||
|
||||
fs::create_dir_all(&lfs_sources_path)?;
|
||||
Ok(lfs_sources_path)
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +84,7 @@ fn select_mirrors_tui(mirrors: Vec<String>) -> Vec<String> {
|
|||
loop {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.area();
|
||||
let size = f.size();
|
||||
let items: Vec<ListItem> = mirrors
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
|
@ -70,8 +96,8 @@ fn select_mirrors_tui(mirrors: Vec<String>) -> Vec<String> {
|
|||
let list = List::new(items)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Select mirrors"),
|
||||
.title("Select mirrors")
|
||||
.borders(Borders::ALL),
|
||||
)
|
||||
.highlight_symbol(">> ");
|
||||
f.render_stateful_widget(list, size, &mut state);
|
||||
|
|
@ -182,7 +208,7 @@ fn download_packages_tui(lfs_sources: &PathBuf) {
|
|||
loop {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.area();
|
||||
let size = f.size();
|
||||
let items: Vec<ListItem> = {
|
||||
let state = download_state.lock().unwrap();
|
||||
state
|
||||
|
|
@ -210,6 +236,26 @@ fn download_packages_tui(lfs_sources: &PathBuf) {
|
|||
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
|
||||
}
|
||||
|
||||
// ----------------- Cleanup function -----------------
|
||||
#[cfg(feature = "tui")]
|
||||
fn cleanup_temp_directories() -> Result<usize, Box<dyn std::error::Error>> {
|
||||
let mut cleaned_count = 0;
|
||||
for entry in fs::read_dir("/tmp")? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
|
||||
if name.starts_with("lfs_") {
|
||||
fs::remove_dir_all(&path)?;
|
||||
cleaned_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(cleaned_count)
|
||||
}
|
||||
|
||||
// ----------------- Main TUI menu -----------------
|
||||
#[cfg(feature = "tui")]
|
||||
pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut stdout = stdout();
|
||||
|
|
@ -222,19 +268,20 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
|||
"🌱 Init environment",
|
||||
"📦 Download packages",
|
||||
"🔍 Check status",
|
||||
"🧹 Clean up temp directories", // Neuer Menüpunkt
|
||||
"🧹 Clean up temp directories",
|
||||
"📄 Test JSON fetch", // NEW BUTTON
|
||||
"❌ Exit",
|
||||
];
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(0));
|
||||
|
||||
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
|
||||
let mut status_message: Option<String> = None;
|
||||
let mut status_message_timer: Option<Instant> = None;
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| {
|
||||
let size = f.area();
|
||||
let size = f.size();
|
||||
let block = Block::default()
|
||||
.title("✨ lpkg TUI 🌈")
|
||||
.borders(Borders::ALL);
|
||||
|
|
@ -256,34 +303,35 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
|||
f.render_widget(list, chunks[i]);
|
||||
}
|
||||
|
||||
// Statusmeldung anzeigen
|
||||
if let Some(msg) = &status_message {
|
||||
let msg_block = Block::default()
|
||||
let paragraph = Paragraph::new(msg.as_str()).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)
|
||||
.style(Style::default().fg(Color::Yellow)),
|
||||
);
|
||||
let msg_area = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(10), Constraint::Percentage(80), Constraint::Percentage(10)])
|
||||
.split(size)[1]; // Mittlerer Bereich
|
||||
.constraints([
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(80),
|
||||
Constraint::Percentage(10),
|
||||
])
|
||||
.split(size)[1];
|
||||
let msg_area = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
||||
.split(msg_area)[1]; // Unterste Zeile
|
||||
.split(msg_area)[1];
|
||||
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
|
||||
if timer.elapsed() > Duration::from_secs(3) {
|
||||
status_message = None;
|
||||
status_message_timer = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Down => {
|
||||
|
|
@ -300,44 +348,61 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
KeyCode::Enter => match state.selected() {
|
||||
Some(0) => {
|
||||
// Init environment
|
||||
match init_environment() {
|
||||
Ok(path) => {
|
||||
lfs_sources = Some(path.clone());
|
||||
status_message = Some(format!("✅ Environment initialized: {}", path.display()));
|
||||
status_message =
|
||||
Some(format!("✅ Environment initialized: {}", path.display()));
|
||||
}
|
||||
Err(e) => {
|
||||
status_message = Some(format!("❌ Failed to initialize environment: {}", e));
|
||||
status_message =
|
||||
Some(format!("❌ Failed to init environment: {}", e));
|
||||
}
|
||||
}
|
||||
status_message_timer = Some(std::time::Instant::now());
|
||||
status_message_timer = Some(Instant::now());
|
||||
}
|
||||
Some(1) => {
|
||||
if let Some(ref path) = lfs_sources {
|
||||
// Download packages
|
||||
if let Some(path) = &lfs_sources {
|
||||
download_packages_tui(path);
|
||||
} else {
|
||||
status_message = Some("⚠️ Please initialize environment first!".to_string());
|
||||
status_message_timer = Some(std::time::Instant::now());
|
||||
}
|
||||
}
|
||||
Some(2) => {
|
||||
// Status
|
||||
status_message = Some("🔍 Status selected! (TODO)".to_string());
|
||||
status_message_timer = Some(std::time::Instant::now());
|
||||
status_message_timer = Some(Instant::now());
|
||||
}
|
||||
Some(3) => {
|
||||
// Aufruf der neuen Bereinigungsfunktion
|
||||
status_message = Some("🧹 Cleaning up temporary directories...".to_string());
|
||||
status_message_timer = Some(std::time::Instant::now());
|
||||
// Cleanup
|
||||
match cleanup_temp_directories() {
|
||||
Ok(count) => {
|
||||
status_message = Some(format!("✅ Cleaned up {} temporary directories.", count));
|
||||
status_message =
|
||||
Some(format!("✅ Cleaned {} temporary dirs", count));
|
||||
}
|
||||
Err(e) => {
|
||||
status_message = Some(format!("❌ Failed to clean up: {}", e));
|
||||
status_message = Some(format!("❌ Failed cleanup: {}", e));
|
||||
}
|
||||
}
|
||||
status_message_timer = Some(std::time::Instant::now());
|
||||
status_message_timer = Some(Instant::now());
|
||||
}
|
||||
Some(4) | _ => break,
|
||||
Some(4) => {
|
||||
// Test JSON fetch
|
||||
// Leave TUI first
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
match fetch_and_parse_html_to_json(
|
||||
"https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html",
|
||||
) {
|
||||
Ok(json) => println!("✅ JSON output:\n{}", json),
|
||||
Err(e) => eprintln!("❌ Error: {}", e),
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Some(5) | _ => break,
|
||||
},
|
||||
KeyCode::Esc => break,
|
||||
_ => {}
|
||||
|
|
@ -350,21 +415,3 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
|||
terminal.show_cursor()?;
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use reqwest;
|
||||
use scraper::{Html, Selector};
|
||||
use std::process::Command;
|
||||
|
||||
pub fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
|
||||
/// Führt ein Kommando aus und gibt die erste Zeile der Version zurück
|
||||
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())
|
||||
|
|
@ -9,7 +12,8 @@ pub fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_version(installed: &str, required: &str) -> bool {
|
||||
/// Vergleicht zwei Versionen
|
||||
fn check_version(installed: &str, required: &str) -> bool {
|
||||
let parse_ver = |v: &str| {
|
||||
v.split(['.', '-'])
|
||||
.filter_map(|s| s.parse::<u32>().ok())
|
||||
|
|
@ -29,8 +33,9 @@ pub fn check_version(installed: &str, required: &str) -> bool {
|
|||
i.len() >= r.len()
|
||||
}
|
||||
|
||||
pub fn ver_check(program: &str, arg: &str, min_version: &str) -> bool {
|
||||
match run_command(program, &[arg]) {
|
||||
/// Führt eine Version-Prüfung durch
|
||||
fn ver_check(program: &str, cmd: &str, min_version: &str) -> bool {
|
||||
match run_command(cmd, &["--version"]) {
|
||||
Some(output) => {
|
||||
let ver = output
|
||||
.lines()
|
||||
|
|
@ -57,27 +62,83 @@ pub fn ver_check(program: &str, arg: &str, min_version: &str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn run_version_checks() -> bool {
|
||||
/// Führt die Kernel-Prüfung durch
|
||||
fn ver_kernel(min_version: &str) -> bool {
|
||||
let kernel = run_command("uname", &["-r"]).unwrap_or_default();
|
||||
if check_version(&kernel, min_version) {
|
||||
println!("OK: Linux Kernel {} >= {}", kernel, min_version);
|
||||
true
|
||||
} else {
|
||||
println!(
|
||||
"ERROR: Linux Kernel {} is too old ({} required)",
|
||||
kernel, min_version
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Lädt die LFS-Seite und führt alle Versionsprüfungen aus
|
||||
pub fn run_version_checks_from_html(url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let html_text = reqwest::blocking::get(url)?.text()?;
|
||||
let document = Html::parse_document(&html_text);
|
||||
let selector = Selector::parse("pre").unwrap();
|
||||
|
||||
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");
|
||||
for element in document.select(&selector) {
|
||||
let pre_text = element.text().collect::<Vec<_>>().join("\n");
|
||||
|
||||
// Kernel check
|
||||
if let Some(kernel) = run_command("uname", &["-r"]) {
|
||||
if check_version(&kernel, "5.4") {
|
||||
println!("OK: Linux Kernel {} >= 5.4", kernel);
|
||||
for line in pre_text.lines() {
|
||||
let line = line.trim();
|
||||
if line.starts_with("ver_check") {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 4 {
|
||||
let prog = parts[1];
|
||||
let cmd = parts[2];
|
||||
let ver = parts[3];
|
||||
ok &= ver_check(prog, cmd, ver);
|
||||
}
|
||||
} else if line.starts_with("ver_kernel") {
|
||||
if let Some(ver) = line.split_whitespace().nth(1) {
|
||||
ok &= ver_kernel(ver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias-Checks
|
||||
let alias_check = |cmd: &str, expected: &str| {
|
||||
if let Some(output) = run_command(cmd, &["--version"]) {
|
||||
if output.to_lowercase().contains(&expected.to_lowercase()) {
|
||||
println!("OK: {:<4} is {}", cmd, expected);
|
||||
} else {
|
||||
println!("ERROR: Linux Kernel {} is too old (5.4 required)", kernel);
|
||||
ok = false;
|
||||
println!("ERROR: {:<4} is NOT {}", cmd, expected);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
alias_check("awk", "GNU");
|
||||
alias_check("yacc", "Bison");
|
||||
alias_check("sh", "Bash");
|
||||
|
||||
// Compiler-Test
|
||||
if run_command("g++", &["-x", "c++", "-"]).is_some() {
|
||||
println!("OK: g++ works");
|
||||
} else {
|
||||
println!("ERROR: g++ does NOT work");
|
||||
}
|
||||
|
||||
// CPU cores
|
||||
let cores = num_cpus::get();
|
||||
println!("OK: {} logical cores available", cores);
|
||||
|
||||
ok
|
||||
// nproc-Test
|
||||
let nproc = run_command("nproc", &[]).unwrap_or_default();
|
||||
if nproc.is_empty() {
|
||||
println!("ERROR: nproc is not available or empty");
|
||||
} else {
|
||||
println!("OK: nproc reports {} logical cores available", nproc);
|
||||
}
|
||||
|
||||
if !ok {
|
||||
println!("Some version checks failed.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue