chore: refactor UI to use Downloads struct
This commit is contained in:
@@ -374,6 +374,10 @@ impl Client {
|
|||||||
self.context.read().unwrap().searches.clone()
|
self.context.read().unwrap().searches.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_all_downloads(&self) -> Vec<Download> {
|
||||||
|
self.context.read().unwrap().get_downloads().clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn download(
|
pub fn download(
|
||||||
&self,
|
&self,
|
||||||
filename: String,
|
filename: String,
|
||||||
|
|||||||
@@ -98,6 +98,30 @@ pub struct Download {
|
|||||||
pub sender: Sender<DownloadStatus>,
|
pub sender: Sender<DownloadStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Download {
|
||||||
|
pub fn is_finished(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.status,
|
||||||
|
DownloadStatus::Completed | DownloadStatus::Failed | DownloadStatus::TimedOut
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes_downloaded(&self) -> u64 {
|
||||||
|
match &self.status {
|
||||||
|
DownloadStatus::InProgress { bytes_downloaded, .. } => *bytes_downloaded,
|
||||||
|
DownloadStatus::Completed => self.size,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn speed_bytes_per_sec(&self) -> f64 {
|
||||||
|
match &self.status {
|
||||||
|
DownloadStatus::InProgress { speed_bytes_per_sec, .. } => *speed_bytes_per_sec,
|
||||||
|
_ => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum DownloadStatus {
|
pub enum DownloadStatus {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::models::{FileDisplayData, FileDownloadState};
|
use crate::models::FileDisplayData;
|
||||||
use ratatui::{layout::Rect, widgets::TableState};
|
use ratatui::{layout::Rect, widgets::TableState};
|
||||||
use soulseek_rs::DownloadStatus;
|
use soulseek_rs::{types::Download, DownloadStatus};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::{mpsc::Receiver, Arc};
|
use std::sync::{mpsc::Receiver, Arc};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -22,7 +22,7 @@ pub struct SearchEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct DownloadEntry {
|
pub struct DownloadEntry {
|
||||||
pub state: FileDownloadState,
|
pub download: Download,
|
||||||
pub receiver: Option<Receiver<DownloadStatus>>,
|
pub receiver: Option<Receiver<DownloadStatus>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ pub struct AppState {
|
|||||||
pub downloads: Vec<DownloadEntry>,
|
pub downloads: Vec<DownloadEntry>,
|
||||||
pub downloads_table_state: TableState,
|
pub downloads_table_state: TableState,
|
||||||
pub downloads_receiver_channel:
|
pub downloads_receiver_channel:
|
||||||
Option<Receiver<(usize, Receiver<DownloadStatus>)>>,
|
Option<Receiver<(Download, Receiver<DownloadStatus>)>>,
|
||||||
pub active_downloads_count: usize,
|
pub active_downloads_count: usize,
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
use soulseek_rs::DownloadStatus;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FileDownloadState {
|
|
||||||
pub filename: String,
|
|
||||||
pub username: String,
|
|
||||||
pub total_bytes: u64,
|
|
||||||
pub bytes_downloaded: u64,
|
|
||||||
pub speed_bytes_per_sec: f64,
|
|
||||||
pub status: DownloadStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileDownloadState {
|
|
||||||
pub fn new(filename: String, username: String, total_bytes: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
filename,
|
|
||||||
username,
|
|
||||||
total_bytes,
|
|
||||||
bytes_downloaded: 0,
|
|
||||||
speed_bytes_per_sec: 0.0,
|
|
||||||
status: DownloadStatus::Queued,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_status(&mut self, status: DownloadStatus) {
|
|
||||||
match &status {
|
|
||||||
DownloadStatus::InProgress {
|
|
||||||
bytes_downloaded,
|
|
||||||
total_bytes,
|
|
||||||
speed_bytes_per_sec,
|
|
||||||
} => {
|
|
||||||
self.bytes_downloaded = *bytes_downloaded;
|
|
||||||
self.total_bytes = *total_bytes;
|
|
||||||
self.speed_bytes_per_sec = *speed_bytes_per_sec;
|
|
||||||
}
|
|
||||||
DownloadStatus::Completed => {
|
|
||||||
self.bytes_downloaded = self.total_bytes;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
self.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_finished(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self.status,
|
|
||||||
DownloadStatus::Completed
|
|
||||||
| DownloadStatus::Failed
|
|
||||||
| DownloadStatus::TimedOut
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
mod app_state;
|
mod app_state;
|
||||||
mod download_state;
|
|
||||||
mod file_display_data;
|
mod file_display_data;
|
||||||
|
|
||||||
pub use app_state::{
|
pub use app_state::{
|
||||||
AppState, DownloadEntry, FocusedPane, SearchEntry, SearchStatus,
|
AppState, DownloadEntry, FocusedPane, SearchEntry, SearchStatus,
|
||||||
};
|
};
|
||||||
pub use download_state::FileDownloadState;
|
|
||||||
pub use file_display_data::FileDisplayData;
|
pub use file_display_data::FileDisplayData;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::models::FileDownloadState;
|
use crate::models::DownloadEntry;
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
border_style, border_type, error_style, format_bytes_progress,
|
border_style, border_type, error_style, format_bytes_progress,
|
||||||
format_progress_bar, format_shortcuts_styled, format_speed, header_style,
|
format_progress_bar, format_shortcuts_styled, format_speed, header_style,
|
||||||
@@ -27,9 +27,8 @@ use std::{
|
|||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
pub struct MultiDownloadProgress {
|
pub struct MultiDownloadProgress {
|
||||||
downloads: Vec<FileDownloadState>,
|
downloads: Vec<DownloadEntry>,
|
||||||
receivers: Vec<Option<Receiver<DownloadStatus>>>,
|
receiver_channel: Receiver<(soulseek_rs::types::Download, Receiver<DownloadStatus>)>,
|
||||||
receiver_channel: Receiver<(usize, Receiver<DownloadStatus>)>,
|
|
||||||
list_state: TableState,
|
list_state: TableState,
|
||||||
max_concurrent: usize,
|
max_concurrent: usize,
|
||||||
active_count: usize,
|
active_count: usize,
|
||||||
@@ -39,19 +38,14 @@ pub struct MultiDownloadProgress {
|
|||||||
|
|
||||||
impl MultiDownloadProgress {
|
impl MultiDownloadProgress {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
downloads: Vec<FileDownloadState>,
|
receiver_channel: Receiver<(soulseek_rs::types::Download, Receiver<DownloadStatus>)>,
|
||||||
receiver_channel: Receiver<(usize, Receiver<DownloadStatus>)>,
|
|
||||||
max_concurrent: usize,
|
max_concurrent: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut list_state = TableState::default();
|
let mut list_state = TableState::default();
|
||||||
list_state.select(Some(0));
|
list_state.select(Some(0));
|
||||||
|
|
||||||
// Initialize all receivers as None - they'll be populated asynchronously
|
|
||||||
let receivers = (0..downloads.len()).map(|_| None).collect();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
downloads,
|
downloads: Vec::new(),
|
||||||
receivers,
|
|
||||||
receiver_channel,
|
receiver_channel,
|
||||||
list_state,
|
list_state,
|
||||||
max_concurrent,
|
max_concurrent,
|
||||||
@@ -68,38 +62,39 @@ impl MultiDownloadProgress {
|
|||||||
loop {
|
loop {
|
||||||
terminal.draw(|frame| self.render(frame))?;
|
terminal.draw(|frame| self.render(frame))?;
|
||||||
|
|
||||||
// Check for incoming download receivers from background thread
|
// Check for incoming downloads from background thread
|
||||||
while let Ok((index, receiver)) = self.receiver_channel.try_recv() {
|
while let Ok((download, receiver)) = self.receiver_channel.try_recv() {
|
||||||
self.receivers[index] = Some(receiver);
|
self.downloads.push(DownloadEntry {
|
||||||
// Update status message
|
download,
|
||||||
let queued =
|
receiver: Some(receiver),
|
||||||
self.receivers.iter().filter(|r| r.is_none()).count();
|
});
|
||||||
if queued == 0 {
|
self.queuing_status = format!("{} downloads queued", self.downloads.len());
|
||||||
self.queuing_status = String::from("All downloads queued");
|
|
||||||
} else {
|
|
||||||
self.queuing_status =
|
|
||||||
format!("Queuing... {} remaining", queued);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll all receivers for status updates
|
// Poll all receivers for status updates
|
||||||
for i in 0..self.receivers.len() {
|
let mut need_start_next = false;
|
||||||
if let Some(receiver) = &self.receivers[i] {
|
for download_entry in &mut self.downloads {
|
||||||
|
if let Some(receiver) = &download_entry.receiver {
|
||||||
if let Ok(status) = receiver.try_recv() {
|
if let Ok(status) = receiver.try_recv() {
|
||||||
let was_active = !self.downloads[i].is_finished();
|
let was_active = !download_entry.download.is_finished();
|
||||||
self.downloads[i].update_status(status);
|
download_entry.download.status = status;
|
||||||
let is_finished = self.downloads[i].is_finished();
|
let is_finished = download_entry.download.is_finished();
|
||||||
|
|
||||||
// If download just finished, decrement active count and start next
|
// If download just finished, decrement active count
|
||||||
if was_active && is_finished {
|
if was_active && is_finished {
|
||||||
self.active_count =
|
self.active_count =
|
||||||
self.active_count.saturating_sub(1);
|
self.active_count.saturating_sub(1);
|
||||||
self.start_next_batch();
|
need_start_next = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start next batch after iteration completes
|
||||||
|
if need_start_next {
|
||||||
|
self.start_next_batch();
|
||||||
|
}
|
||||||
|
|
||||||
// Handle keyboard input
|
// Handle keyboard input
|
||||||
if poll(Duration::from_millis(100))? {
|
if poll(Duration::from_millis(100))? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
@@ -118,14 +113,14 @@ impl MultiDownloadProgress {
|
|||||||
|
|
||||||
fn start_next_batch(&mut self) {
|
fn start_next_batch(&mut self) {
|
||||||
// Start queued downloads up to max_concurrent limit
|
// Start queued downloads up to max_concurrent limit
|
||||||
for i in 0..self.downloads.len() {
|
for download_entry in &self.downloads {
|
||||||
if self.active_count >= self.max_concurrent {
|
if self.active_count >= self.max_concurrent {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only start if download is queued AND receiver is available
|
// Only start if download is queued AND receiver is available
|
||||||
if matches!(self.downloads[i].status, DownloadStatus::Queued)
|
if matches!(download_entry.download.status, DownloadStatus::Queued)
|
||||||
&& self.receivers[i].is_some()
|
&& download_entry.receiver.is_some()
|
||||||
{
|
{
|
||||||
self.active_count += 1;
|
self.active_count += 1;
|
||||||
// Download will start automatically since receiver exists
|
// Download will start automatically since receiver exists
|
||||||
@@ -134,7 +129,7 @@ impl MultiDownloadProgress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn all_finished(&self) -> bool {
|
fn all_finished(&self) -> bool {
|
||||||
self.downloads.iter().all(|d| d.is_finished())
|
self.downloads.iter().all(|d| d.download.is_finished())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key(&mut self, key: KeyEvent) {
|
fn handle_key(&mut self, key: KeyEvent) {
|
||||||
@@ -223,7 +218,8 @@ impl MultiDownloadProgress {
|
|||||||
let rows: Vec<Row> = self
|
let rows: Vec<Row> = self
|
||||||
.downloads
|
.downloads
|
||||||
.iter()
|
.iter()
|
||||||
.map(|download| {
|
.map(|download_entry| {
|
||||||
|
let download = &download_entry.download;
|
||||||
let status_icon = match download.status {
|
let status_icon = match download.status {
|
||||||
DownloadStatus::Queued => "⋯",
|
DownloadStatus::Queued => "⋯",
|
||||||
DownloadStatus::InProgress { .. } => "⧗",
|
DownloadStatus::InProgress { .. } => "⧗",
|
||||||
@@ -232,9 +228,9 @@ impl MultiDownloadProgress {
|
|||||||
DownloadStatus::TimedOut => "⏱",
|
DownloadStatus::TimedOut => "⏱",
|
||||||
};
|
};
|
||||||
|
|
||||||
let progress = if download.total_bytes > 0 {
|
let progress = if download.size > 0 {
|
||||||
download.bytes_downloaded as f64
|
download.bytes_downloaded() as f64
|
||||||
/ download.total_bytes as f64
|
/ download.size as f64
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
@@ -245,13 +241,13 @@ impl MultiDownloadProgress {
|
|||||||
format_progress_bar(progress, bar_width, percent);
|
format_progress_bar(progress, bar_width, percent);
|
||||||
|
|
||||||
let size_str = format_bytes_progress(
|
let size_str = format_bytes_progress(
|
||||||
download.bytes_downloaded,
|
download.bytes_downloaded(),
|
||||||
download.total_bytes,
|
download.size,
|
||||||
);
|
);
|
||||||
|
|
||||||
let speed_str = match download.status {
|
let speed_str = match download.status {
|
||||||
DownloadStatus::InProgress { .. } => {
|
DownloadStatus::InProgress { .. } => {
|
||||||
format_speed(download.speed_bytes_per_sec)
|
format_speed(download.speed_bytes_per_sec())
|
||||||
}
|
}
|
||||||
_ => "-".to_string(),
|
_ => "-".to_string(),
|
||||||
};
|
};
|
||||||
@@ -331,30 +327,30 @@ impl MultiDownloadProgress {
|
|||||||
pub fn render_download_stats(
|
pub fn render_download_stats(
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
area: ratatui::layout::Rect,
|
area: ratatui::layout::Rect,
|
||||||
downloads: &[FileDownloadState],
|
downloads: &[DownloadEntry],
|
||||||
active_count: usize,
|
active_count: usize,
|
||||||
) {
|
) {
|
||||||
let completed = downloads
|
let completed = downloads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| matches!(d.status, DownloadStatus::Completed))
|
.filter(|d| matches!(d.download.status, DownloadStatus::Completed))
|
||||||
.count();
|
.count();
|
||||||
let failed = downloads
|
let failed = downloads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| {
|
.filter(|d| {
|
||||||
matches!(
|
matches!(
|
||||||
d.status,
|
d.download.status,
|
||||||
DownloadStatus::Failed | DownloadStatus::TimedOut
|
DownloadStatus::Failed | DownloadStatus::TimedOut
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
let queued = downloads
|
let queued = downloads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| matches!(d.status, DownloadStatus::Queued))
|
.filter(|d| matches!(d.download.status, DownloadStatus::Queued))
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let total_downloaded: u64 =
|
let total_downloaded: u64 =
|
||||||
downloads.iter().map(|d| d.bytes_downloaded).sum();
|
downloads.iter().map(|d| d.download.bytes_downloaded()).sum();
|
||||||
let total_size: u64 = downloads.iter().map(|d| d.total_bytes).sum();
|
let total_size: u64 = downloads.iter().map(|d| d.download.size).sum();
|
||||||
let overall_progress = if total_size > 0 {
|
let overall_progress = if total_size > 0 {
|
||||||
(total_downloaded as f64 / total_size as f64 * 100.0) as u8
|
(total_downloaded as f64 / total_size as f64 * 100.0) as u8
|
||||||
} else {
|
} else {
|
||||||
@@ -367,8 +363,8 @@ pub fn render_download_stats(
|
|||||||
};
|
};
|
||||||
let total_speed: f64 = downloads
|
let total_speed: f64 = downloads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| matches!(d.status, DownloadStatus::InProgress { .. }))
|
.filter(|d| matches!(d.download.status, DownloadStatus::InProgress { .. }))
|
||||||
.map(|d| d.speed_bytes_per_sec)
|
.map(|d| d.download.speed_bytes_per_sec())
|
||||||
.sum();
|
.sum();
|
||||||
let speed_mb = (total_speed / 1_048_576.0 * 100.0).round() / 100.0;
|
let speed_mb = (total_speed / 1_048_576.0 * 100.0).round() / 100.0;
|
||||||
|
|
||||||
@@ -465,45 +461,35 @@ pub fn show_multi_download_progress(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
soulseek_rs::utils::logger::enable_buffering();
|
soulseek_rs::utils::logger::enable_buffering();
|
||||||
|
|
||||||
// Create download states immediately (all with Queued status)
|
// Create channel for sending downloads and receivers from background thread
|
||||||
let downloads: Vec<FileDownloadState> = selected_files
|
|
||||||
.iter()
|
|
||||||
.map(|(filename, username, size)| {
|
|
||||||
FileDownloadState::new(filename.clone(), username.clone(), *size)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Create channel for sending receivers from background thread
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
// Spawn background thread to initialize downloads
|
// Spawn background thread to initialize downloads
|
||||||
let init_client = client.clone();
|
let init_client = client.clone();
|
||||||
let init_files = selected_files.clone();
|
|
||||||
let init_dir = download_dir.clone();
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for (index, (filename, username, size)) in
|
for (filename, username, size) in selected_files.into_iter() {
|
||||||
init_files.into_iter().enumerate()
|
|
||||||
{
|
|
||||||
// Initiate download
|
// Initiate download
|
||||||
match init_client.download(
|
match init_client.download(
|
||||||
filename,
|
filename.clone(),
|
||||||
username,
|
username.clone(),
|
||||||
size,
|
size,
|
||||||
init_dir.clone(),
|
download_dir.clone(),
|
||||||
) {
|
) {
|
||||||
Ok(receiver) => {
|
Ok(receiver) => {
|
||||||
// Send receiver back to main thread
|
// Get the download from client
|
||||||
let _ = tx.send((index, receiver));
|
if let Some(download) = init_client.get_all_downloads().iter().find(|d| d.filename == filename && d.username == username).cloned() {
|
||||||
|
let _ = tx.send((download, receiver));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to start download {}: {}", index, e);
|
eprintln!("Failed to start download for {}: {}", filename, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut progress =
|
let mut progress =
|
||||||
MultiDownloadProgress::new(downloads, rx, max_concurrent);
|
MultiDownloadProgress::new(rx, max_concurrent);
|
||||||
let result = progress.run(terminal);
|
let result = progress.run(terminal);
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::models::{
|
use crate::models::{
|
||||||
AppState, DownloadEntry, FileDisplayData, FileDownloadState, FocusedPane,
|
AppState, DownloadEntry, FileDisplayData, FocusedPane,
|
||||||
SearchEntry, SearchStatus,
|
SearchEntry, SearchStatus,
|
||||||
};
|
};
|
||||||
use crate::ui::panes::{
|
use crate::ui::panes::{
|
||||||
@@ -113,16 +113,10 @@ impl MainTui {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Render status bar
|
// Render status bar
|
||||||
let download_states: Vec<FileDownloadState> = self
|
|
||||||
.state
|
|
||||||
.downloads
|
|
||||||
.iter()
|
|
||||||
.map(|d| d.state.clone())
|
|
||||||
.collect();
|
|
||||||
render_download_stats(
|
render_download_stats(
|
||||||
frame,
|
frame,
|
||||||
main_chunks[0],
|
main_chunks[0],
|
||||||
&download_states,
|
&self.state.downloads,
|
||||||
self.state.active_downloads_count,
|
self.state.active_downloads_count,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -184,16 +178,10 @@ impl MainTui {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Render Downloads pane
|
// Render Downloads pane
|
||||||
let download_states: Vec<FileDownloadState> = self
|
|
||||||
.state
|
|
||||||
.downloads
|
|
||||||
.iter()
|
|
||||||
.map(|d| d.state.clone())
|
|
||||||
.collect();
|
|
||||||
render_downloads_pane(
|
render_downloads_pane(
|
||||||
frame,
|
frame,
|
||||||
right_chunks[1],
|
right_chunks[1],
|
||||||
&download_states,
|
&self.state.downloads,
|
||||||
&mut self.state.downloads_table_state,
|
&mut self.state.downloads_table_state,
|
||||||
self.state.focused_pane == FocusedPane::Downloads,
|
self.state.focused_pane == FocusedPane::Downloads,
|
||||||
);
|
);
|
||||||
@@ -792,27 +780,12 @@ impl MainTui {
|
|||||||
let (sender, receiver) = mpsc::channel();
|
let (sender, receiver) = mpsc::channel();
|
||||||
self.state.downloads_receiver_channel = Some(receiver);
|
self.state.downloads_receiver_channel = Some(receiver);
|
||||||
|
|
||||||
// Add downloads to state
|
|
||||||
for (filename, username, size) in &selected_files {
|
|
||||||
let download_state = FileDownloadState::new(
|
|
||||||
filename.clone(),
|
|
||||||
username.clone(),
|
|
||||||
*size,
|
|
||||||
);
|
|
||||||
self.state.downloads.push(DownloadEntry {
|
|
||||||
state: download_state,
|
|
||||||
receiver: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start downloads in background
|
// Start downloads in background
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let download_dir = self.download_dir.clone();
|
let download_dir = self.download_dir.clone();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for (idx, (filename, username, size)) in
|
for (filename, username, size) in selected_files.into_iter() {
|
||||||
selected_files.into_iter().enumerate()
|
|
||||||
{
|
|
||||||
match client.download(
|
match client.download(
|
||||||
filename.clone(),
|
filename.clone(),
|
||||||
username.clone(),
|
username.clone(),
|
||||||
@@ -820,7 +793,10 @@ impl MainTui {
|
|||||||
download_dir.clone(),
|
download_dir.clone(),
|
||||||
) {
|
) {
|
||||||
Ok(rx) => {
|
Ok(rx) => {
|
||||||
let _ = sender.send((idx, rx));
|
// Get the download from client
|
||||||
|
if let Some(download) = client.get_all_downloads().iter().find(|d| d.filename == filename && d.username == username).cloned() {
|
||||||
|
let _ = sender.send((download, rx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@@ -837,37 +813,27 @@ impl MainTui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_downloads(&mut self) {
|
fn update_downloads(&mut self) {
|
||||||
// Check for new download receivers
|
// Check for new downloads from background thread
|
||||||
if let Some(ref receiver) = self.state.downloads_receiver_channel {
|
if let Some(ref receiver) = self.state.downloads_receiver_channel {
|
||||||
while let Ok((idx, download_receiver)) = receiver.try_recv() {
|
while let Ok((download, download_receiver)) = receiver.try_recv() {
|
||||||
if let Some(download) = self.state.downloads.get_mut(idx) {
|
self.state.downloads.push(DownloadEntry {
|
||||||
download.receiver = Some(download_receiver);
|
download,
|
||||||
}
|
receiver: Some(download_receiver),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update download states
|
// Update download states
|
||||||
self.state.active_downloads_count = 0;
|
self.state.active_downloads_count = 0;
|
||||||
for download in &mut self.state.downloads {
|
for download_entry in &mut self.state.downloads {
|
||||||
if let Some(ref receiver) = download.receiver {
|
if let Some(ref receiver) = download_entry.receiver {
|
||||||
while let Ok(status) = receiver.try_recv() {
|
while let Ok(status) = receiver.try_recv() {
|
||||||
download.state.status = status.clone();
|
download_entry.download.status = status;
|
||||||
if let DownloadStatus::InProgress {
|
|
||||||
bytes_downloaded,
|
|
||||||
total_bytes,
|
|
||||||
speed_bytes_per_sec,
|
|
||||||
} = &status
|
|
||||||
{
|
|
||||||
download.state.bytes_downloaded = *bytes_downloaded;
|
|
||||||
download.state.total_bytes = *total_bytes;
|
|
||||||
download.state.speed_bytes_per_sec =
|
|
||||||
*speed_bytes_per_sec;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
download.state.status,
|
download_entry.download.status,
|
||||||
DownloadStatus::InProgress { .. }
|
DownloadStatus::InProgress { .. }
|
||||||
) {
|
) {
|
||||||
self.state.active_downloads_count += 1;
|
self.state.active_downloads_count += 1;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::models::FileDownloadState;
|
use crate::models::DownloadEntry;
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
border_style, border_type, error_style, format_bytes, format_speed,
|
border_style, border_type, error_style, format_bytes, format_speed,
|
||||||
header_style, highlight_style, inactive_style, success_style,
|
header_style, highlight_style, inactive_style, success_style,
|
||||||
@@ -17,7 +17,7 @@ use soulseek_rs::DownloadStatus;
|
|||||||
pub fn render_downloads_pane(
|
pub fn render_downloads_pane(
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
downloads: &[FileDownloadState],
|
downloads: &[DownloadEntry],
|
||||||
table_state: &mut TableState,
|
table_state: &mut TableState,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
) {
|
) {
|
||||||
@@ -47,7 +47,8 @@ pub fn render_downloads_pane(
|
|||||||
|
|
||||||
let rows: Vec<Row> = downloads
|
let rows: Vec<Row> = downloads
|
||||||
.iter()
|
.iter()
|
||||||
.map(|download| {
|
.map(|download_entry| {
|
||||||
|
let download = &download_entry.download;
|
||||||
let (status_icon, status_style) = match &download.status {
|
let (status_icon, status_style) = match &download.status {
|
||||||
DownloadStatus::Queued => ("⋯", inactive_style()),
|
DownloadStatus::Queued => ("⋯", inactive_style()),
|
||||||
DownloadStatus::InProgress { .. } => ("⧗", warning_style()),
|
DownloadStatus::InProgress { .. } => ("⧗", warning_style()),
|
||||||
@@ -59,17 +60,17 @@ pub fn render_downloads_pane(
|
|||||||
let progress_text = match &download.status {
|
let progress_text = match &download.status {
|
||||||
DownloadStatus::Queued => "Queued".to_string(),
|
DownloadStatus::Queued => "Queued".to_string(),
|
||||||
DownloadStatus::InProgress { .. } => {
|
DownloadStatus::InProgress { .. } => {
|
||||||
let percent = if download.total_bytes > 0 {
|
let percent = if download.size > 0 {
|
||||||
(download.bytes_downloaded as f64
|
(download.bytes_downloaded() as f64
|
||||||
/ download.total_bytes as f64
|
/ download.size as f64
|
||||||
* 100.0) as u8
|
* 100.0) as u8
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
"{}/{} ({}%)",
|
"{}/{} ({}%)",
|
||||||
format_bytes(download.bytes_downloaded),
|
format_bytes(download.bytes_downloaded()),
|
||||||
format_bytes(download.total_bytes),
|
format_bytes(download.size),
|
||||||
percent
|
percent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -80,7 +81,7 @@ pub fn render_downloads_pane(
|
|||||||
|
|
||||||
let speed_text = match &download.status {
|
let speed_text = match &download.status {
|
||||||
DownloadStatus::InProgress { .. } => {
|
DownloadStatus::InProgress { .. } => {
|
||||||
format_speed(download.speed_bytes_per_sec)
|
format_speed(download.speed_bytes_per_sec())
|
||||||
}
|
}
|
||||||
_ => "-".to_string(),
|
_ => "-".to_string(),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user