diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 8ebdccc..5cedb60 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -70,7 +70,8 @@ const COMPONENT_EXPLORER_LOCAL: &str = "EXPLORER_LOCAL"; const COMPONENT_EXPLORER_REMOTE: &str = "EXPLORER_REMOTE"; const COMPONENT_EXPLORER_FIND: &str = "EXPLORER_FIND"; const COMPONENT_LOG_BOX: &str = "LOG_BOX"; -const COMPONENT_PROGRESS_BAR: &str = "PROGRESS_BAR"; +const COMPONENT_PROGRESS_BAR_FULL: &str = "PROGRESS_BAR_FULL"; +const COMPONENT_PROGRESS_BAR_PARTIAL: &str = "PROGRESS_BAR_PARTIAL"; const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR"; const COMPONENT_TEXT_FATAL: &str = "TEXT_FATAL"; const COMPONENT_TEXT_HELP: &str = "TEXT_HELP"; diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 38a5804..d9aa06d 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -163,6 +163,23 @@ impl FileTransferActivity { ) { // Reset states self.transfer.reset(); + // Calculate total size of transfer + let total_transfer_size: usize = self.get_total_transfer_size_local(entry); + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Uploading {}...", entry.get_abs_path().display())); + // Send recurse + self.filetransfer_send_recurse(entry, curr_remote_path, dst_name); + // Umount progress bar + self.umount_progress_bar(); + } + + fn filetransfer_send_recurse( + &mut self, + entry: &FsEntry, + curr_remote_path: &Path, + dst_name: Option, + ) { // Write popup let file_name: String = match entry { FsEntry::Directory(dir) => dir.name.clone(), @@ -235,7 +252,11 @@ impl FileTransferActivity { break; } // Send entry; name is always None after first call - self.filetransfer_send(&entry, remote_path.as_path(), None); + self.filetransfer_send_recurse( + &entry, + remote_path.as_path(), + None, + ); } } Err(err) => { @@ -272,13 +293,115 @@ impl FileTransferActivity { LogLevel::Warn, format!("Upload aborted for \"{}\"!", entry.get_abs_path().display()), ); - } else { - // @! Successful - // Eventually, Remove progress bar - self.umount_progress_bar(); } } + /// ### filetransfer_send_file + /// + /// Send local file and write it to remote path + fn filetransfer_send_file( + &mut self, + local: &FsFile, + remote: &Path, + file_name: String, + ) -> Result<(), TransferErrorReason> { + // Upload file + // Try to open local file + match self.host.open_file_read(local.abs_path.as_path()) { + Ok(mut fhnd) => match self.client.send_file(local, remote) { + Ok(mut rhnd) => { + // Write file + let file_size: usize = + fhnd.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize; + // Init transfer + self.transfer.partial.init(file_size); + // rewind + if let Err(err) = fhnd.seek(std::io::SeekFrom::Start(0)) { + return Err(TransferErrorReason::CouldNotRewind(err)); + } + // Write remote file + let mut total_bytes_written: usize = 0; + let mut last_progress_val: f64 = 0.0; + let mut last_input_event_fetch: Instant = Instant::now(); + // While the entire file hasn't been completely written, + // Or filetransfer has been aborted + while total_bytes_written < file_size && !self.transfer.aborted() { + // Handle input events (each 500ms) + if last_input_event_fetch.elapsed().as_millis() >= 500 { + // Read events + self.read_input_event(); + // Reset instant + last_input_event_fetch = Instant::now(); + } + // Read till you can + let mut buffer: [u8; 65536] = [0; 65536]; + let delta: usize = match fhnd.read(&mut buffer) { + Ok(bytes_read) => { + total_bytes_written += bytes_read; + if bytes_read == 0 { + continue; + } else { + let mut delta: usize = 0; + while delta < bytes_read { + // Write bytes + match rhnd.write(&buffer[delta..bytes_read]) { + Ok(bytes) => { + delta += bytes; + } + Err(err) => { + return Err(TransferErrorReason::RemoteIoError( + err, + )); + } + } + } + delta + } + } + Err(err) => { + return Err(TransferErrorReason::LocalIoError(err)); + } + }; + // Increase progress + self.transfer.partial.update_progress(delta); + self.transfer.full.update_progress(delta); + // Draw only if a significant progress has been made (performance improvement) + if last_progress_val < self.transfer.partial.calc_progress() - 0.01 { + // Draw + self.update_progress_bar(format!("Uploading \"{}\"...", file_name)); + self.view(); + last_progress_val = self.transfer.partial.calc_progress(); + } + } + // Finalize stream + if let Err(err) = self.client.on_sent(rhnd) { + self.log( + LogLevel::Warn, + format!("Could not finalize remote stream: \"{}\"", err), + ); + } + // if upload was abrupted, return error + if self.transfer.aborted() { + return Err(TransferErrorReason::Abrupted); + } + self.log( + LogLevel::Info, + format!( + "Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)", + local.abs_path.display(), + remote.display(), + fmt_millis(self.transfer.partial.started().elapsed()), + ByteSize(self.transfer.partial.calc_bytes_per_second()), + ), + ); + } + Err(err) => return Err(TransferErrorReason::FileTransferError(err)), + }, + Err(err) => return Err(TransferErrorReason::HostError(err)), + } + Ok(()) + } + /// ### filetransfer_recv /// /// Recv fs entry from remote. @@ -292,6 +415,23 @@ impl FileTransferActivity { ) { // Reset states self.transfer.reset(); + // Calculate total transfer size + let total_transfer_size: usize = self.get_total_transfer_size_remote(entry); + self.transfer.full.init(total_transfer_size); + // Mount progress bar + self.mount_progress_bar(format!("Downloading {}...", entry.get_abs_path().display())); + // Receive + self.filetransfer_recv_recurse(entry, local_path, dst_name); + // Umount progress bar + self.umount_progress_bar(); + } + + fn filetransfer_recv_recurse( + &mut self, + entry: &FsEntry, + local_path: &Path, + dst_name: Option, + ) { // Write popup let file_name: String = match entry { FsEntry::Directory(dir) => dir.name.clone(), @@ -386,7 +526,11 @@ impl FileTransferActivity { } // Receive entry; name is always None after first call // Local path becomes local_dir_path - self.filetransfer_recv(&entry, local_dir_path.as_path(), None); + self.filetransfer_recv_recurse( + &entry, + local_dir_path.as_path(), + None, + ); } } Err(err) => { @@ -426,123 +570,9 @@ impl FileTransferActivity { entry.get_abs_path().display() ), ); - } else { - // Eventually, Reset input mode to explorer - self.umount_progress_bar(); } } - /// ### filetransfer_send_file - /// - /// Send local file and write it to remote path - fn filetransfer_send_file( - &mut self, - local: &FsFile, - remote: &Path, - file_name: String, - ) -> Result<(), TransferErrorReason> { - // Upload file - // Try to open local file - match self.host.open_file_read(local.abs_path.as_path()) { - Ok(mut fhnd) => match self.client.send_file(local, remote) { - Ok(mut rhnd) => { - // Write file - let file_size: usize = - fhnd.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize; - // Init transfer - self.transfer.partial.init(file_size); - // rewind - if let Err(err) = fhnd.seek(std::io::SeekFrom::Start(0)) { - return Err(TransferErrorReason::CouldNotRewind(err)); - } - // Write remote file - let mut total_bytes_written: usize = 0; - let mut last_progress_val: f64 = 0.0; - let mut last_input_event_fetch: Instant = Instant::now(); - // Mount progress bar - self.mount_progress_bar(); - // While the entire file hasn't been completely written, - // Or filetransfer has been aborted - while total_bytes_written < file_size && !self.transfer.aborted() { - // Handle input events (each 500ms) - if last_input_event_fetch.elapsed().as_millis() >= 500 { - // Read events - self.read_input_event(); - // Reset instant - last_input_event_fetch = Instant::now(); - } - // Read till you can - let mut buffer: [u8; 65536] = [0; 65536]; - let delta: usize = match fhnd.read(&mut buffer) { - Ok(bytes_read) => { - total_bytes_written += bytes_read; - if bytes_read == 0 { - continue; - } else { - let mut delta: usize = 0; - while delta < bytes_read { - // Write bytes - match rhnd.write(&buffer[delta..bytes_read]) { - Ok(bytes) => { - delta += bytes; - } - Err(err) => { - self.umount_progress_bar(); - return Err(TransferErrorReason::RemoteIoError( - err, - )); - } - } - } - delta - } - } - Err(err) => { - self.umount_progress_bar(); - return Err(TransferErrorReason::LocalIoError(err)); - } - }; - // Increase progress - self.transfer.partial.update_progress(delta); - // Draw only if a significant progress has been made (performance improvement) - if last_progress_val < self.transfer.partial.calc_progress() - 0.01 { - // Draw - self.update_progress_bar(format!("Uploading \"{}\"...", file_name)); - self.view(); - last_progress_val = self.transfer.partial.calc_progress(); - } - } - // Umount progress bar - self.umount_progress_bar(); - // Finalize stream - if let Err(err) = self.client.on_sent(rhnd) { - self.log( - LogLevel::Warn, - format!("Could not finalize remote stream: \"{}\"", err), - ); - } - // if upload was abrupted, return error - if self.transfer.aborted() { - return Err(TransferErrorReason::Abrupted); - } - self.log( - LogLevel::Info, - format!( - "Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)", - local.abs_path.display(), - remote.display(), - fmt_millis(self.transfer.partial.started().elapsed()), - ByteSize(self.transfer.partial.calc_bytes_per_second()), - ), - ); - } - Err(err) => return Err(TransferErrorReason::FileTransferError(err)), - }, - Err(err) => return Err(TransferErrorReason::HostError(err)), - } - Ok(()) - } - /// ### filetransfer_recv_file /// /// Receive file from remote and write it to local path @@ -564,8 +594,6 @@ impl FileTransferActivity { // Write local file let mut last_progress_val: f64 = 0.0; let mut last_input_event_fetch: Instant = Instant::now(); - // Mount progress bar - self.mount_progress_bar(); // While the entire file hasn't been completely read, // Or filetransfer has been aborted while total_bytes_written < remote.size && !self.transfer.aborted() { @@ -590,7 +618,6 @@ impl FileTransferActivity { match local_file.write(&buffer[delta..bytes_read]) { Ok(bytes) => delta += bytes, Err(err) => { - self.umount_progress_bar(); return Err(TransferErrorReason::LocalIoError( err, )); @@ -601,12 +628,12 @@ impl FileTransferActivity { } } Err(err) => { - self.umount_progress_bar(); return Err(TransferErrorReason::RemoteIoError(err)); } }; // Set progress self.transfer.partial.update_progress(delta); + self.transfer.full.update_progress(delta); // Draw only if a significant progress has been made (performance improvement) if last_progress_val < self.transfer.partial.calc_progress() - 0.01 { // Draw @@ -615,8 +642,6 @@ impl FileTransferActivity { last_progress_val = self.transfer.partial.calc_progress(); } } - // Umount progress bar - self.umount_progress_bar(); // Finalize stream if let Err(err) = self.client.on_recv(rhnd) { self.log( @@ -1009,4 +1034,64 @@ impl FileTransferActivity { } } } + + // -- transfer sizes + + /// ### get_total_transfer_size_local + /// + /// Get total size of transfer for localhost + fn get_total_transfer_size_local(&mut self, entry: &FsEntry) -> usize { + match entry { + FsEntry::File(file) => file.size, + FsEntry::Directory(dir) => { + // List dir + match self.host.scan_dir(dir.abs_path.as_path()) { + Ok(files) => files + .iter() + .map(|x| self.get_total_transfer_size_local(x)) + .sum(), + Err(err) => { + self.log( + LogLevel::Error, + format!( + "Could not list directory {}: {}", + dir.abs_path.display(), + err + ), + ); + 0 + } + } + } + } + } + + /// ### get_total_transfer_size_remote + /// + /// Get total size of transfer for remote host + fn get_total_transfer_size_remote(&mut self, entry: &FsEntry) -> usize { + match entry { + FsEntry::File(file) => file.size, + FsEntry::Directory(dir) => { + // List directory + match self.client.list_dir(dir.abs_path.as_path()) { + Ok(files) => files + .iter() + .map(|x| self.get_total_transfer_size_remote(x)) + .sum(), + Err(err) => { + self.log( + LogLevel::Error, + format!( + "Could not list directory {}: {}", + dir.abs_path.display(), + err + ), + ); + 0 + } + } + } + } + } } diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index c6e4f9e..ecb9292 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -33,9 +33,10 @@ use super::{ COMPONENT_EXPLORER_FIND, COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE, COMPONENT_INPUT_COPY, COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO, COMPONENT_INPUT_MKDIR, COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, - COMPONENT_LIST_FILEINFO, COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR, COMPONENT_RADIO_DELETE, - COMPONENT_RADIO_DISCONNECT, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SORTING, - COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, COMPONENT_TEXT_HELP, + COMPONENT_LIST_FILEINFO, COMPONENT_LOG_BOX, COMPONENT_PROGRESS_BAR_FULL, + COMPONENT_PROGRESS_BAR_PARTIAL, COMPONENT_RADIO_DELETE, COMPONENT_RADIO_DISCONNECT, + COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SORTING, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_FATAL, + COMPONENT_TEXT_HELP, }; use crate::fs::explorer::FileSorting; use crate::fs::FsEntry; @@ -641,7 +642,7 @@ impl FileTransferActivity { None } // -- progress bar - (COMPONENT_PROGRESS_BAR, &MSG_KEY_CTRL_C) => { + (COMPONENT_PROGRESS_BAR_PARTIAL, &MSG_KEY_CTRL_C) => { // Set transfer aborted to True self.transfer.abort(); None @@ -792,14 +793,22 @@ impl FileTransferActivity { } } - pub(super) fn update_progress_bar(&mut self, text: String) -> Option<(String, Msg)> { - match self.view.get_props(COMPONENT_PROGRESS_BAR) { + pub(super) fn update_progress_bar(&mut self, filename: String) -> Option<(String, Msg)> { + if let Some(props) = self.view.get_props(COMPONENT_PROGRESS_BAR_FULL) { + let root_name: String = props.texts.title.as_deref().unwrap_or("").to_string(); + let props = ProgressBarPropsBuilder::from(props) + .with_texts(Some(root_name), self.transfer.full.to_string()) + .with_progress(self.transfer.full.calc_progress()) + .build(); + let _ = self.view.update(COMPONENT_PROGRESS_BAR_FULL, props); + } + match self.view.get_props(COMPONENT_PROGRESS_BAR_PARTIAL) { Some(props) => { let props = ProgressBarPropsBuilder::from(props) - .with_texts(Some(text), self.transfer.partial.to_string()) + .with_texts(Some(filename), self.transfer.partial.to_string()) .with_progress(self.transfer.partial.calc_progress()) .build(); - self.view.update(COMPONENT_PROGRESS_BAR, props) + self.view.update(COMPONENT_PROGRESS_BAR_PARTIAL, props) } None => None, } diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index d32c35b..bab6290 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -247,12 +247,25 @@ impl FileTransferActivity { self.view.render(super::COMPONENT_LIST_FILEINFO, f, popup); } } - if let Some(props) = self.view.get_props(super::COMPONENT_PROGRESS_BAR) { + if let Some(props) = self.view.get_props(super::COMPONENT_PROGRESS_BAR_PARTIAL) { if props.visible { - let popup = draw_area_in(f.size(), 40, 10); + let popup = draw_area_in(f.size(), 50, 20); f.render_widget(Clear, popup); // make popup - self.view.render(super::COMPONENT_PROGRESS_BAR, f, popup); + let popup_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(50), // Full + Constraint::Percentage(50), // Partial + ] + .as_ref(), + ) + .split(popup); + self.view + .render(super::COMPONENT_PROGRESS_BAR_FULL, f, popup_chunks[0]); + self.view + .render(super::COMPONENT_PROGRESS_BAR_PARTIAL, f, popup_chunks[1]); } } if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_DELETE) { @@ -614,23 +627,43 @@ impl FileTransferActivity { self.view.umount(super::COMPONENT_INPUT_SAVEAS); } - pub(super) fn mount_progress_bar(&mut self) { + pub(super) fn mount_progress_bar(&mut self, root_name: String) { self.view.mount( - super::COMPONENT_PROGRESS_BAR, + super::COMPONENT_PROGRESS_BAR_FULL, Box::new(ProgressBar::new( ProgressBarPropsBuilder::default() - .with_progbar_color(Color::LightGreen) + .with_progbar_color(Color::Green) .with_background(Color::Black) - .with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen) + .with_borders( + Borders::TOP | Borders::RIGHT | Borders::LEFT, + BorderType::Rounded, + Color::Reset, + ) + .with_texts(Some(root_name), String::new()) + .build(), + )), + ); + self.view.mount( + super::COMPONENT_PROGRESS_BAR_PARTIAL, + Box::new(ProgressBar::new( + ProgressBarPropsBuilder::default() + .with_progbar_color(Color::Green) + .with_background(Color::Black) + .with_borders( + Borders::BOTTOM | Borders::RIGHT | Borders::LEFT, + BorderType::Rounded, + Color::Reset, + ) .with_texts(Some(String::from("Please wait")), String::new()) .build(), )), ); - self.view.active(super::COMPONENT_PROGRESS_BAR); + self.view.active(super::COMPONENT_PROGRESS_BAR_PARTIAL); } pub(super) fn umount_progress_bar(&mut self) { - self.view.umount(super::COMPONENT_PROGRESS_BAR); + self.view.umount(super::COMPONENT_PROGRESS_BAR_PARTIAL); + self.view.umount(super::COMPONENT_PROGRESS_BAR_FULL); } pub(super) fn mount_file_sorting(&mut self) {