This commit is contained in:
Lucy 2025-09-30 19:25:53 +02:00
parent 002cca571c
commit bc3560f1f8

View file

@ -1,4 +1,6 @@
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
use crate::{downloader, md5_utils, mirrors, wget_list};
#[cfg(feature = "tui")]
use crossterm::{ use crossterm::{
event::{self, Event, KeyCode}, event::{self, Event, KeyCode},
execute, execute,
@ -13,16 +15,14 @@ use ratatui::{
widgets::{Block, Borders, List, ListItem, ListState}, widgets::{Block, Borders, List, ListItem, ListState},
}; };
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
use std::collections::HashMap; use std::{
#[cfg(feature = "tui")] collections::HashMap,
use std::io::{self, stdout}; io::{self, stdout},
#[cfg(feature = "tui")] path::PathBuf,
use std::path::PathBuf; sync::{Arc, Mutex},
#[cfg(feature = "tui")] thread,
use std::time::{Duration, Instant}; time::Duration,
};
#[cfg(feature = "tui")]
use crate::{downloader, mirrors, wget_list};
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
fn init_environment() -> PathBuf { fn init_environment() -> PathBuf {
@ -51,28 +51,22 @@ fn select_mirrors_tui(mirrors: Vec<String>) -> Vec<String> {
terminal terminal
.draw(|f| { .draw(|f| {
let size = f.size(); let size = f.size();
let block = Block::default() let items: Vec<ListItem> = mirrors
.title("Select mirrors (space to toggle, Enter to confirm)") .iter()
.borders(Borders::ALL); .enumerate()
f.render_widget(block, size); .map(|(i, mirror)| {
let prefix = if selected[i] { "[x] " } else { "[ ] " };
let chunks = Layout::default() ListItem::new(format!("{}{}", prefix, mirror))
.direction(Direction::Vertical) })
.margin(2) .collect();
.constraints(vec![Constraint::Length(3); mirrors.len()]) let list = List::new(items)
.split(size); .block(
Block::default()
for (i, mirror) in mirrors.iter().enumerate() { .borders(Borders::ALL)
let mut style = Style::default(); .title("Select mirrors"),
if Some(i) == state.selected() { )
style = style.bg(Color::Red).fg(Color::White); .highlight_symbol(">> ");
} f.render_stateful_widget(list, size, &mut state);
let prefix = if selected[i] { "[x] " } else { "[ ] " };
let list_item = ListItem::new(format!("{}{}", prefix, mirror)).style(style);
let list =
List::new(vec![list_item]).block(Block::default().borders(Borders::ALL));
f.render_widget(list, chunks[i]);
}
}) })
.unwrap(); .unwrap();
@ -115,8 +109,8 @@ fn select_mirrors_tui(mirrors: Vec<String>) -> Vec<String> {
} }
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
fn download_packages(lfs_sources: &PathBuf) { fn download_packages_tui(lfs_sources: &PathBuf) {
let mirrors_list = mirrors::fetch_mirrors().unwrap_or_else(|_| vec![]); 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!"); println!("⚠️ No mirrors selected!");
@ -130,7 +124,7 @@ fn download_packages(lfs_sources: &PathBuf) {
} }
let mut md5_map = HashMap::new(); let mut md5_map = HashMap::new();
if let Ok(md5_content) = crate::md5_utils::get_md5sums() { if let Ok(md5_content) = md5_utils::get_md5sums() {
for line in md5_content.lines() { for line in md5_content.lines() {
if let Some((hash, filename)) = line.split_once(' ') { if let Some((hash, filename)) = line.split_once(' ') {
md5_map.insert(filename.to_string(), hash.to_string()); md5_map.insert(filename.to_string(), hash.to_string());
@ -138,28 +132,75 @@ fn download_packages(lfs_sources: &PathBuf) {
} }
} }
for file in wget_list { let download_state: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(
let mut downloaded = false; wget_list
for mirror in &selected_mirrors { .iter()
print!("⬇️ Downloading {} from {} ... ", file, mirror); .map(|f| (f.clone(), "Pending".into()))
io::stdout().flush().unwrap(); .collect(),
));
let start = Instant::now(); let download_state_clone = Arc::clone(&download_state);
if downloader::download_files(&file, lfs_sources, Some(mirror.clone()), Some(&md5_map)) let mirrors_clone = selected_mirrors.clone();
let lfs_sources = lfs_sources.clone();
thread::spawn(move || {
for file in &wget_list {
let mut status = "Failed".to_string();
for mirror in &mirrors_clone {
if downloader::download_files(
file,
&lfs_sources,
Some(mirror.clone()),
Some(&md5_map),
)
.is_ok() .is_ok()
{ {
println!("✅ done in {:?}", start.elapsed()); status = format!("Downloaded from {}", mirror);
downloaded = true; break;
break; }
} else { }
println!("⚠️ failed, trying next mirror..."); let mut state = download_state_clone.lock().unwrap();
if let Some(entry) = state.iter_mut().find(|(f, _)| f == file) {
entry.1 = status.clone();
} }
} }
if !downloaded { });
println!("❌ Failed to download {}", file);
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen).unwrap();
enable_raw_mode().unwrap();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).unwrap();
loop {
terminal
.draw(|f| {
let size = f.size();
let items: Vec<ListItem> = {
let state = download_state.lock().unwrap();
state
.iter()
.map(|(f, s)| ListItem::new(format!("{}: {}", f, s)))
.collect()
};
let list = List::new(items).block(
Block::default()
.title("Downloading Packages")
.borders(Borders::ALL),
);
f.render_widget(list, size);
})
.unwrap();
let state = download_state.lock().unwrap();
if state.iter().all(|(_, s)| s != "Pending") {
break;
} }
thread::sleep(Duration::from_millis(100));
} }
disable_raw_mode().unwrap();
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
println!("🎉 All downloads finished!"); println!("🎉 All downloads finished!");
} }
@ -222,15 +263,19 @@ pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
KeyCode::Enter => match state.selected() { KeyCode::Enter => match state.selected() {
Some(0) => lfs_sources = Some(init_environment()), Some(0) => {
lfs_sources = Some(init_environment());
}
Some(1) => { Some(1) => {
if let Some(ref path) = lfs_sources { if let Some(ref path) = lfs_sources {
download_packages(path); download_packages_tui(path);
} else { } else {
println!("⚠️ Please initialize environment first!"); println!("⚠️ Please initialize environment first!");
} }
} }
Some(2) => println!("🔍 Status selected! (TODO)"), Some(2) => {
println!("🔍 Status selected! (TODO)");
}
Some(3) | _ => break, Some(3) | _ => break,
}, },
KeyCode::Esc => break, KeyCode::Esc => break,