mirror of
https://github.com/veeso/termscp.git
synced 2026-09-27 06:21:22 -07:00
build(remotefs): migrate to version 1 (#449)
Install.sh / build (macos-latest) (push) Has been cancelled
Install.sh / build (ubuntu-latest) (push) Has been cancelled
CI / toolchain (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / install-scripts (push) Has been cancelled
Site / build-site (push) Has been cancelled
CI / crates-macos-latest (push) Has been cancelled
CI / crates-ubuntu-latest (push) Has been cancelled
CI / crates-windows-latest (push) Has been cancelled
CI / doc (push) Has been cancelled
CI / deny (push) Has been cancelled
Install.sh / build (macos-latest) (push) Has been cancelled
Install.sh / build (ubuntu-latest) (push) Has been cancelled
CI / toolchain (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / install-scripts (push) Has been cancelled
Site / build-site (push) Has been cancelled
CI / crates-macos-latest (push) Has been cancelled
CI / crates-ubuntu-latest (push) Has been cancelled
CI / crates-windows-latest (push) Has been cancelled
CI / doc (push) Has been cancelled
CI / deny (push) Has been cancelled
* build(remotefs): migrate to version 1 * fix(remote): preserve POSIX paths on Windows
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
//! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
@@ -107,10 +107,12 @@ impl FileTransferActivity {
|
||||
}
|
||||
};
|
||||
|
||||
let new_file_size = match std::io::copy(&mut reader, &mut writer) {
|
||||
Err(err) => return Err(format!("Could not write file: {err}")),
|
||||
Ok(size) => size,
|
||||
};
|
||||
if let Err(err) = std::io::copy(&mut reader, &mut writer) {
|
||||
return Err(format!("Could not write file: {err}"));
|
||||
}
|
||||
reader
|
||||
.finish()
|
||||
.map_err(|err| format!("Could not finish file read: {err}"))?;
|
||||
|
||||
// edit file
|
||||
|
||||
@@ -126,10 +128,11 @@ impl FileTransferActivity {
|
||||
};
|
||||
let mut writer = match self.browser.local_pane_mut().fs.create_file(
|
||||
entry.path(),
|
||||
&Metadata {
|
||||
size: new_file_size,
|
||||
..Default::default()
|
||||
},
|
||||
&Metadata::default().size(
|
||||
std::fs::metadata(tempfile.as_path())
|
||||
.map_err(|err| format!("Could not stat edited file: {err}"))?
|
||||
.len(),
|
||||
),
|
||||
) {
|
||||
Ok(writer) => writer,
|
||||
Err(err) => {
|
||||
@@ -141,6 +144,10 @@ impl FileTransferActivity {
|
||||
return Err(format!("Could not write file: {err}"));
|
||||
}
|
||||
|
||||
writer
|
||||
.flush()
|
||||
.map_err(|err| format!("Could not write file: {err}"))?;
|
||||
|
||||
self.browser
|
||||
.local_pane_mut()
|
||||
.fs
|
||||
|
||||
@@ -2,6 +2,12 @@ use remotefs::File;
|
||||
|
||||
use super::{FileTransferActivity, LogLevel};
|
||||
|
||||
fn aggregate_sizes(sizes: impl IntoIterator<Item = Option<u64>>) -> Option<u64> {
|
||||
sizes
|
||||
.into_iter()
|
||||
.try_fold(0_u64, |total, size| total.checked_add(size?))
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// Calculate and display the total size of the selected file(s) via the active tab's pane.
|
||||
pub(crate) fn action_get_file_size(&mut self) {
|
||||
@@ -11,17 +17,23 @@ impl FileTransferActivity {
|
||||
let total_size = self.get_files_size(files);
|
||||
|
||||
self.umount_wait();
|
||||
self.mount_info(format!(
|
||||
"Total file size: {size}",
|
||||
size = bytesize::ByteSize::b(total_size)
|
||||
));
|
||||
let message = total_size.map_or_else(
|
||||
|| String::from("Total file size: Unknown"),
|
||||
|size| {
|
||||
format!(
|
||||
"Total file size: {size}",
|
||||
size = bytesize::ByteSize::b(size)
|
||||
)
|
||||
},
|
||||
);
|
||||
self.mount_info(message);
|
||||
}
|
||||
|
||||
fn get_files_size(&mut self, files: Vec<File>) -> u64 {
|
||||
files.into_iter().map(|f| self.get_file_size(f)).sum()
|
||||
fn get_files_size(&mut self, files: Vec<File>) -> Option<u64> {
|
||||
aggregate_sizes(files.into_iter().map(|file| self.get_file_size(file)))
|
||||
}
|
||||
|
||||
fn get_file_size(&mut self, file: File) -> u64 {
|
||||
fn get_file_size(&mut self, file: File) -> Option<u64> {
|
||||
if let Some(symlink) = &file.metadata().symlink {
|
||||
match self.browser.fs_pane_mut().fs.stat(symlink) {
|
||||
Ok(stat) => stat.metadata().size,
|
||||
@@ -33,12 +45,12 @@ impl FileTransferActivity {
|
||||
path = symlink.display(),
|
||||
),
|
||||
);
|
||||
0
|
||||
None
|
||||
}
|
||||
}
|
||||
} else if file.is_dir() {
|
||||
match self.browser.fs_pane_mut().fs.list_dir(&file.path) {
|
||||
Ok(list) => list.into_iter().map(|f| self.get_file_size(f)).sum(),
|
||||
Ok(list) => self.get_files_size(list),
|
||||
Err(err) => {
|
||||
self.log(
|
||||
LogLevel::Error,
|
||||
@@ -47,7 +59,7 @@ impl FileTransferActivity {
|
||||
path = file.path.display(),
|
||||
),
|
||||
);
|
||||
0
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -55,3 +67,14 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::aggregate_sizes;
|
||||
|
||||
#[test]
|
||||
fn aggregates_only_known_sizes() {
|
||||
assert_eq!(aggregate_sizes([Some(2), Some(3)]), Some(5));
|
||||
assert_eq!(aggregate_sizes([Some(2), None, Some(3)]), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ impl FileTransferActivity {
|
||||
.browser
|
||||
.fs_pane_mut()
|
||||
.fs
|
||||
.create_file(file_path.as_path(), &Metadata::default())
|
||||
.create_file(file_path.as_path(), &Metadata::default().size(0))
|
||||
{
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
|
||||
@@ -131,6 +131,14 @@ impl FileTransferActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = reader.finish() {
|
||||
self.log(
|
||||
LogLevel::Error,
|
||||
format!("Failed to finish bridged file read: {err}"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if tmpfile.exists() {
|
||||
self.open_path_with(tmpfile.as_path(), open_with);
|
||||
}
|
||||
|
||||
@@ -43,11 +43,10 @@ impl FileInfoPopup {
|
||||
.add_col(SpanStatic::from("File type: "))
|
||||
.add_col(SpanStatic::raw(filetype.clone()).fg(Color::LightGreen));
|
||||
}
|
||||
let (bsize, size): (ByteSize, u64) = (ByteSize(file.metadata().size), file.metadata().size);
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(SpanStatic::from("Size: "))
|
||||
.add_col(SpanStatic::raw(format!("{bsize} ({size})")).fg(Color::Cyan));
|
||||
.add_col(SpanStatic::raw(format_file_size(file)).fg(Color::Cyan));
|
||||
let atime: String = fmt_time(
|
||||
file.metadata().accessed.unwrap_or(UNIX_EPOCH),
|
||||
"%b %d %Y %H:%M:%S",
|
||||
@@ -117,6 +116,13 @@ impl FileInfoPopup {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_file_size(file: &File) -> String {
|
||||
match file.metadata().size {
|
||||
Some(size) => format!("{bsize} ({size})", bsize = ByteSize(size), size = size),
|
||||
None => String::from("Unknown"),
|
||||
}
|
||||
}
|
||||
|
||||
impl AppComponent<Msg, NoUserEvent> for FileInfoPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
@@ -128,3 +134,17 @@ impl AppComponent<Msg, NoUserEvent> for FileInfoPopup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use remotefs::fs::Metadata;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn formats_unknown_file_size_as_unknown() {
|
||||
let file = File::new("/tmp/unknown", Metadata::default());
|
||||
|
||||
assert_eq!(format_file_size(&file), "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use bytesize::ByteSize;
|
||||
pub struct TransferProgress {
|
||||
files_total: usize,
|
||||
files_completed: usize,
|
||||
cur_file_size: usize,
|
||||
cur_file_size: Option<usize>,
|
||||
cur_file_written: usize,
|
||||
total_bytes_written: usize,
|
||||
pub(crate) started: Instant,
|
||||
@@ -27,7 +27,7 @@ impl Default for TransferProgress {
|
||||
Self {
|
||||
files_total: 0,
|
||||
files_completed: 0,
|
||||
cur_file_size: 0,
|
||||
cur_file_size: None,
|
||||
cur_file_written: 0,
|
||||
total_bytes_written: 0,
|
||||
started: Instant::now(),
|
||||
@@ -41,13 +41,15 @@ impl fmt::Display for TransferProgress {
|
||||
0 => String::from("--:--"),
|
||||
seconds => format!("{:02}:{:02}", seconds / 60, seconds % 60),
|
||||
};
|
||||
let total = self.cur_file_size.map_or_else(
|
||||
|| String::from("?"),
|
||||
|size| ByteSize(size as u64).to_string(),
|
||||
);
|
||||
write!(
|
||||
f,
|
||||
"{} / {} — {:.1}% — ETA {} ({}/s)",
|
||||
"{} / {total} — {:.1}% — ETA {eta} ({}/s)",
|
||||
ByteSize(self.cur_file_written as u64),
|
||||
ByteSize(self.cur_file_size as u64),
|
||||
self.calc_partial_progress() * 100.0,
|
||||
eta,
|
||||
ByteSize(self.calc_bytes_per_second()),
|
||||
)
|
||||
}
|
||||
@@ -58,15 +60,15 @@ impl TransferProgress {
|
||||
pub fn init(&mut self, files_total: usize) {
|
||||
self.files_total = files_total;
|
||||
self.files_completed = 0;
|
||||
self.cur_file_size = 0;
|
||||
self.cur_file_size = None;
|
||||
self.cur_file_written = 0;
|
||||
self.total_bytes_written = 0;
|
||||
self.started = Instant::now();
|
||||
}
|
||||
|
||||
/// Begin a new file with a known size.
|
||||
pub fn start_file(&mut self, size: usize) {
|
||||
self.cur_file_size = size;
|
||||
/// Begin a new file with an optional advertised size.
|
||||
pub fn start_file(&mut self, size: Option<u64>) {
|
||||
self.cur_file_size = size.and_then(|size| usize::try_from(size).ok());
|
||||
self.cur_file_written = 0;
|
||||
}
|
||||
|
||||
@@ -83,7 +85,7 @@ impl TransferProgress {
|
||||
/// fraction in [`Self::calc_full_progress`].
|
||||
pub fn finish_file(&mut self) {
|
||||
self.files_completed += 1;
|
||||
self.cur_file_size = 0;
|
||||
self.cur_file_size = None;
|
||||
self.cur_file_written = 0;
|
||||
}
|
||||
|
||||
@@ -92,25 +94,26 @@ impl TransferProgress {
|
||||
self.files_completed += 1;
|
||||
}
|
||||
|
||||
/// Fraction of the current file written (0.0..=1.0). Zero-byte file => 1.0.
|
||||
/// Fraction of the current file written (0.0..=1.0).
|
||||
pub fn calc_partial_progress(&self) -> f64 {
|
||||
if self.cur_file_size == 0 {
|
||||
return 1.0;
|
||||
match self.cur_file_size {
|
||||
None => 0.0,
|
||||
Some(0) => 1.0,
|
||||
Some(size) => (self.cur_file_written as f64 / size as f64).min(1.0),
|
||||
}
|
||||
(self.cur_file_written as f64 / self.cur_file_size as f64).min(1.0)
|
||||
}
|
||||
|
||||
/// Overall progress (0.0..=1.0): file-weighted with intra-file interpolation.
|
||||
///
|
||||
/// The current file only contributes a fraction while it is genuinely in
|
||||
/// progress (`cur_file_size > 0` and not all files completed). A finished
|
||||
/// file clears `cur_file_size` (see [`Self::finish_file`]) so it is counted
|
||||
/// progress (a known size and not all files completed). A finished file
|
||||
/// clears `cur_file_size` (see [`Self::finish_file`]) so it is counted
|
||||
/// exactly once via `files_completed`.
|
||||
pub fn calc_full_progress(&self) -> f64 {
|
||||
if self.files_total == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
let cur_fraction = if self.cur_file_size == 0 || self.files_completed >= self.files_total {
|
||||
let cur_fraction = if self.files_completed >= self.files_total {
|
||||
0.0
|
||||
} else {
|
||||
self.calc_partial_progress()
|
||||
@@ -234,7 +237,7 @@ mod test {
|
||||
assert!(progress.is_single_file());
|
||||
assert_eq!(progress.calc_full_progress(), 0.0);
|
||||
|
||||
progress.start_file(1024);
|
||||
progress.start_file(Some(1024));
|
||||
assert_eq!(progress.calc_partial_progress(), 0.0);
|
||||
assert_eq!(progress.calc_full_progress(), 0.0);
|
||||
|
||||
@@ -258,13 +261,13 @@ mod test {
|
||||
assert!(!progress.is_single_file());
|
||||
|
||||
// File 1 fully transferred => full ≈ 0.25
|
||||
progress.start_file(1000);
|
||||
progress.start_file(Some(1000));
|
||||
progress.add_bytes(1000);
|
||||
progress.finish_file();
|
||||
assert!((progress.calc_full_progress() - 0.25).abs() < 1e-9);
|
||||
|
||||
// File 2 half transferred => partial ≈ 0.5, full ≈ 0.375
|
||||
progress.start_file(1000);
|
||||
progress.start_file(Some(1000));
|
||||
progress.add_bytes(500);
|
||||
assert!((progress.calc_partial_progress() - 0.5).abs() < 1e-9);
|
||||
assert!((progress.calc_full_progress() - 0.375).abs() < 1e-9);
|
||||
@@ -276,7 +279,7 @@ mod test {
|
||||
progress.init(2);
|
||||
|
||||
// One file actually transferred.
|
||||
progress.start_file(100);
|
||||
progress.start_file(Some(100));
|
||||
progress.add_bytes(100);
|
||||
progress.finish_file();
|
||||
|
||||
@@ -294,15 +297,34 @@ mod test {
|
||||
let mut progress = TransferProgress::default();
|
||||
progress.init(1);
|
||||
|
||||
progress.start_file(0);
|
||||
progress.start_file(Some(0));
|
||||
assert!((progress.calc_partial_progress() - 1.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_progress_unknown_size() {
|
||||
let mut progress = TransferProgress::default();
|
||||
progress.init(1);
|
||||
progress.start_file(None);
|
||||
progress.add_bytes(256);
|
||||
|
||||
assert_eq!(progress.calc_partial_progress(), 0.0);
|
||||
assert_eq!(progress.calc_full_progress(), 0.0);
|
||||
assert_eq!(progress.calc_eta(), 0);
|
||||
assert_eq!(progress.total_bytes_written(), 256);
|
||||
assert!(progress.to_string().contains("?"));
|
||||
assert!(!progress.to_string().contains("0 B"));
|
||||
|
||||
progress.finish_file();
|
||||
assert_eq!(progress.files_completed(), 1);
|
||||
assert_eq!(progress.calc_full_progress(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_progress_timing() {
|
||||
let mut progress = TransferProgress::default();
|
||||
progress.init(1);
|
||||
progress.start_file(1024);
|
||||
progress.start_file(Some(1024));
|
||||
|
||||
progress.started = progress
|
||||
.started
|
||||
|
||||
@@ -10,7 +10,7 @@ use bytesize::ByteSize;
|
||||
use remotefs::fs::File;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::host::HostError;
|
||||
use crate::host::{HostError, HostReader, HostWriter};
|
||||
use crate::ui::activities::filetransfer::{FileTransferActivity, LogLevel};
|
||||
use crate::utils::fmt::fmt_millis;
|
||||
|
||||
@@ -457,8 +457,8 @@ impl FileTransferActivity {
|
||||
host: &File,
|
||||
remote: &Path,
|
||||
file_name: String,
|
||||
mut reader: Box<dyn Read + Send>,
|
||||
mut writer: Box<dyn Write + Send>,
|
||||
mut reader: HostReader,
|
||||
mut writer: HostWriter,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// Write file
|
||||
let file_size = self
|
||||
@@ -467,18 +467,16 @@ impl FileTransferActivity {
|
||||
.fs
|
||||
.stat(host.path())
|
||||
.map_err(TransferErrorReason::HostError)
|
||||
.map(|x| x.metadata().size as usize)?;
|
||||
.map(|x| x.metadata().size)?;
|
||||
// Init transfer
|
||||
self.transfer.progress.start_file(file_size);
|
||||
let file_started = Instant::now();
|
||||
|
||||
// Write remote file
|
||||
let mut total_bytes_written: usize = 0;
|
||||
let mut last_redraw: Instant = Instant::now();
|
||||
let mut last_input_event_fetch: Option<Instant> = None;
|
||||
// While the entire file hasn't been completely written,
|
||||
// Or filetransfer has been aborted
|
||||
while total_bytes_written < file_size && !self.transfer.aborted() {
|
||||
// Read until EOF or until the transfer is aborted.
|
||||
while !self.transfer.aborted() {
|
||||
// Handle input events (each 500ms) or if never fetched before
|
||||
if last_input_event_fetch.is_none()
|
||||
|| last_input_event_fetch
|
||||
@@ -494,33 +492,17 @@ impl FileTransferActivity {
|
||||
}
|
||||
// Read till you can
|
||||
let mut buffer: [u8; BUFSIZE] = [0; BUFSIZE];
|
||||
let delta: usize = match reader.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 writer.write(&buffer[delta..bytes_read]) {
|
||||
Ok(bytes) => {
|
||||
delta += bytes;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::RemoteIoError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
delta
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::HostIoError(err));
|
||||
}
|
||||
};
|
||||
let bytes_read = reader
|
||||
.read(&mut buffer)
|
||||
.map_err(TransferErrorReason::HostIoError)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
writer
|
||||
.write_all(&buffer[..bytes_read])
|
||||
.map_err(TransferErrorReason::RemoteIoError)?;
|
||||
// Increase progress
|
||||
self.transfer.progress.add_bytes(delta);
|
||||
self.transfer.progress.add_bytes(bytes_read);
|
||||
// Redraw at most every 100ms to keep UI responsive for large files
|
||||
if last_redraw.elapsed().as_millis() >= 100 {
|
||||
self.update_progress_bar(format!("Uploading \"{file_name}\"…"));
|
||||
@@ -528,12 +510,14 @@ impl FileTransferActivity {
|
||||
last_redraw = Instant::now();
|
||||
}
|
||||
}
|
||||
// Finalize stream
|
||||
handle_remote_finalize_result(self.browser.remote_pane_mut().fs.finalize_write(writer))?;
|
||||
// if upload was abrupted, return error
|
||||
if self.transfer.aborted() {
|
||||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
writer.flush().map_err(TransferErrorReason::RemoteIoError)?;
|
||||
reader.finish().map_err(TransferErrorReason::HostError)?;
|
||||
// Finalize stream
|
||||
handle_remote_finalize_result(self.browser.remote_pane_mut().fs.finalize_write(writer))?;
|
||||
// set stat
|
||||
if let Err(err) = self
|
||||
.browser
|
||||
@@ -812,21 +796,17 @@ impl FileTransferActivity {
|
||||
host_bridge: &Path,
|
||||
remote: &File,
|
||||
file_name: String,
|
||||
mut reader: Box<dyn Read + Send>,
|
||||
mut writer: Box<dyn Write + Send>,
|
||||
mut reader: HostReader,
|
||||
mut writer: HostWriter,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
let mut total_bytes_written: usize = 0;
|
||||
// Init transfer
|
||||
self.transfer
|
||||
.progress
|
||||
.start_file(remote.metadata.size as usize);
|
||||
self.transfer.progress.start_file(remote.metadata.size);
|
||||
let file_started = Instant::now();
|
||||
// Write host_bridge file
|
||||
let mut last_redraw: Instant = Instant::now();
|
||||
let mut last_input_event_fetch: Option<Instant> = None;
|
||||
// While the entire file hasn't been completely read,
|
||||
// Or filetransfer has been aborted
|
||||
while total_bytes_written < remote.metadata.size as usize && !self.transfer.aborted() {
|
||||
// Read until EOF or until the transfer is aborted.
|
||||
while !self.transfer.aborted() {
|
||||
// Handle input events (each 500 ms) or is None
|
||||
if last_input_event_fetch.is_none()
|
||||
|| last_input_event_fetch
|
||||
@@ -842,31 +822,17 @@ impl FileTransferActivity {
|
||||
}
|
||||
// Read till you can
|
||||
let mut buffer: [u8; BUFSIZE] = [0; BUFSIZE];
|
||||
let delta: usize = match reader.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 writer.write(&buffer[delta..bytes_read]) {
|
||||
Ok(bytes) => delta += bytes,
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::HostIoError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
delta
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::RemoteIoError(err));
|
||||
}
|
||||
};
|
||||
let bytes_read = reader
|
||||
.read(&mut buffer)
|
||||
.map_err(TransferErrorReason::RemoteIoError)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
writer
|
||||
.write_all(&buffer[..bytes_read])
|
||||
.map_err(TransferErrorReason::HostIoError)?;
|
||||
// Set progress
|
||||
self.transfer.progress.add_bytes(delta);
|
||||
self.transfer.progress.add_bytes(bytes_read);
|
||||
// Redraw at most every 100ms to keep UI responsive for large files
|
||||
if last_redraw.elapsed().as_millis() >= 100 {
|
||||
self.update_progress_bar(format!("Downloading \"{file_name}\""));
|
||||
@@ -879,6 +845,10 @@ impl FileTransferActivity {
|
||||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
|
||||
writer.flush().map_err(TransferErrorReason::HostIoError)?;
|
||||
reader
|
||||
.finish()
|
||||
.map_err(TransferErrorReason::RemoteHostError)?;
|
||||
// Finalize write
|
||||
self.browser
|
||||
.local_pane_mut()
|
||||
@@ -953,25 +923,20 @@ mod worklist_test {
|
||||
// equal (`File` derives `PartialEq` over its full metadata, timestamps
|
||||
// included), allowing direct equality assertions against the worklist.
|
||||
let t = SystemTime::UNIX_EPOCH;
|
||||
let metadata = Metadata {
|
||||
accessed: Some(t),
|
||||
created: Some(t),
|
||||
modified: Some(t),
|
||||
file_type: if is_dir {
|
||||
let metadata = Metadata::default()
|
||||
.accessed(t)
|
||||
.created(t)
|
||||
.modified(t)
|
||||
.file_type(if is_dir {
|
||||
FileType::Directory
|
||||
} else {
|
||||
FileType::File
|
||||
},
|
||||
symlink: None,
|
||||
gid: Some(0),
|
||||
uid: Some(0),
|
||||
mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })),
|
||||
size: 64,
|
||||
};
|
||||
File {
|
||||
path: PathBuf::from(path),
|
||||
metadata,
|
||||
}
|
||||
})
|
||||
.gid(0)
|
||||
.uid(0)
|
||||
.mode(UnixPex::from(if is_dir { 0o755 } else { 0o644 }))
|
||||
.size(64);
|
||||
File::new(path, metadata)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user