mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-05 10:19:23 +02:00
Implement progress bar both for upload and download
This commit is contained in:
parent
2d1c669cef
commit
d382a015c5
5 changed files with 178 additions and 20 deletions
|
@ -1,5 +1,6 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use openssl::symm::decrypt_aead;
|
use openssl::symm::decrypt_aead;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
|
@ -15,7 +16,7 @@ use crypto::key_set::KeySet;
|
||||||
use crypto::sign::signature_encoded;
|
use crypto::sign::signature_encoded;
|
||||||
use file::file::DownloadFile;
|
use file::file::DownloadFile;
|
||||||
use file::metadata::Metadata;
|
use file::metadata::Metadata;
|
||||||
use reader::EncryptedFileWriter;
|
use reader::{EncryptedFileWriter, ProgressReporter, ProgressWriter};
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ impl<'a> Download<'a> {
|
||||||
pub fn invoke(
|
pub fn invoke(
|
||||||
self,
|
self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Create a key set for the file
|
// Create a key set for the file
|
||||||
let mut key = KeySet::from(self.file);
|
let mut key = KeySet::from(self.file);
|
||||||
|
@ -166,25 +168,38 @@ impl<'a> Download<'a> {
|
||||||
.expect("failed to fetch file, missing content length header")
|
.expect("failed to fetch file, missing content length header")
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
// Open a file to write to
|
// Open a file to write to, and build an encrypted writer
|
||||||
// TODO: this should become a temporary file first
|
// TODO: this should become a temporary file first
|
||||||
let out = File::create("downloaded.toml")
|
let out = File::create("downloaded.zip")
|
||||||
.expect("failed to open file");
|
.expect("failed to open file");
|
||||||
let mut writer = EncryptedFileWriter::new(
|
let writer = EncryptedFileWriter::new(
|
||||||
out,
|
out,
|
||||||
response_len as usize,
|
response_len as usize,
|
||||||
KeySet::cipher(),
|
KeySet::cipher(),
|
||||||
key.file_key().unwrap(),
|
key.file_key().unwrap(),
|
||||||
key.iv(),
|
key.iv(),
|
||||||
).expect("failed to create encrypted writer");
|
).expect("failed to create encrypted writer");
|
||||||
|
let mut writer = ProgressWriter::new(writer)
|
||||||
|
.expect("failed to create encrypted writer");
|
||||||
|
writer.set_reporter(reporter.clone());
|
||||||
|
|
||||||
|
// Start the writer
|
||||||
|
reporter.lock()
|
||||||
|
.expect("unable to start progress, failed to get lock")
|
||||||
|
.start(response_len);
|
||||||
|
|
||||||
// Write to the output file
|
// Write to the output file
|
||||||
io::copy(&mut response, &mut writer)
|
io::copy(&mut response, &mut writer)
|
||||||
.expect("failed to download and decrypt file");
|
.expect("failed to download and decrypt file");
|
||||||
|
|
||||||
|
// Finish
|
||||||
|
reporter.lock()
|
||||||
|
.expect("unable to finish progress, failed to get lock")
|
||||||
|
.finish();
|
||||||
|
|
||||||
// Verify the writer
|
// Verify the writer
|
||||||
// TODO: delete the file if verification failed, show a proper error
|
// TODO: delete the file if verification failed, show a proper error
|
||||||
assert!(writer.verified(), "downloaded and decrypted file could not be verified");
|
assert!(writer.unwrap().verified(), "downloaded and decrypted file could not be verified");
|
||||||
|
|
||||||
// // Crpate metadata and a file reader
|
// // Crpate metadata and a file reader
|
||||||
// let metadata = self.create_metadata(&key, &file)?;
|
// let metadata = self.create_metadata(&key, &file)?;
|
||||||
|
|
|
@ -304,9 +304,8 @@ impl<R: Read> Read for ProgressReader<R> {
|
||||||
|
|
||||||
// Report
|
// Report
|
||||||
if let Some(reporter) = self.reporter.as_mut() {
|
if let Some(reporter) = self.reporter.as_mut() {
|
||||||
reporter.lock()
|
let progress = self.progress;
|
||||||
.expect("failed to update progress, unable to lock reproter")
|
let _ = reporter.lock().map(|mut r| r.progress(progress));
|
||||||
.progress(self.progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(len)
|
Ok(len)
|
||||||
|
@ -333,12 +332,12 @@ pub trait ProgressReporter: Send {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for readers, to get the exact length of a reader.
|
/// A trait for readers, to get the exact length of a reader.
|
||||||
pub trait ExactLengthReader: Read {
|
pub trait ExactLengthReader {
|
||||||
/// Get the exact length of the reader in bytes.
|
/// Get the exact length of the reader in bytes.
|
||||||
fn len(&self) -> Result<u64, io::Error>;
|
fn len(&self) -> Result<u64, io::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: ExactLengthReader> ExactLengthReader for BufReader<R> {
|
impl<R: ExactLengthReader + Read> ExactLengthReader for BufReader<R> {
|
||||||
fn len(&self) -> Result<u64, io::Error> {
|
fn len(&self) -> Result<u64, io::Error> {
|
||||||
self.get_ref().len()
|
self.get_ref().len()
|
||||||
}
|
}
|
||||||
|
@ -444,6 +443,12 @@ impl EncryptedFileWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ExactLengthReader for EncryptedFileWriter {
|
||||||
|
fn len(&self) -> Result<u64, IoError> {
|
||||||
|
Ok(self.len as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The writer trait implementation.
|
/// The writer trait implementation.
|
||||||
impl Write for EncryptedFileWriter {
|
impl Write for EncryptedFileWriter {
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||||
|
@ -508,3 +513,115 @@ impl Write for EncryptedFileWriter {
|
||||||
self.file.flush()
|
self.file.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// A writer wrapper, that measures the reading process for a writer with a
|
||||||
|
/// known length.
|
||||||
|
///
|
||||||
|
/// If the writer exceeds the initially specified length,
|
||||||
|
/// the writer will continue to allow reads.
|
||||||
|
/// The length property will grow accordingly.
|
||||||
|
///
|
||||||
|
/// The writer will only start producing `None` if the wrapped writer is doing
|
||||||
|
/// so.
|
||||||
|
pub struct ProgressWriter<W> {
|
||||||
|
/// The wrapped writer.
|
||||||
|
inner: W,
|
||||||
|
|
||||||
|
/// The total length of the writer.
|
||||||
|
len: u64,
|
||||||
|
|
||||||
|
/// The current reading progress.
|
||||||
|
progress: u64,
|
||||||
|
|
||||||
|
/// A reporter, to report the progress status to.
|
||||||
|
reporter: Option<Arc<Mutex<ProgressReporter>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> ProgressWriter<W> {
|
||||||
|
/// Wrap the given writer with an exact length, in a progress writer.
|
||||||
|
pub fn new(inner: W) -> Result<Self, IoError>
|
||||||
|
where
|
||||||
|
W: ExactLengthReader
|
||||||
|
{
|
||||||
|
Ok(
|
||||||
|
Self {
|
||||||
|
len: inner.len()?,
|
||||||
|
inner,
|
||||||
|
progress: 0,
|
||||||
|
reporter: None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrap the given writer with the given length in a progress writer.
|
||||||
|
pub fn from(inner: W, len: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
len,
|
||||||
|
progress: 0,
|
||||||
|
reporter: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the reporter to report the status to.
|
||||||
|
pub fn set_reporter(&mut self, reporter: Arc<Mutex<ProgressReporter>>) {
|
||||||
|
self.reporter = Some(reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current progress.
|
||||||
|
pub fn progress(&self) -> u64 {
|
||||||
|
self.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwrap the inner from the progress writer.
|
||||||
|
pub fn unwrap(self) -> W {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> Write for ProgressWriter<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||||
|
// Write from the wrapped writer, increase the progress
|
||||||
|
let len = self.inner.write(buf)?;
|
||||||
|
self.progress += len as u64;
|
||||||
|
|
||||||
|
// Keep the specified length in-bound
|
||||||
|
if self.progress > self.len {
|
||||||
|
self.len = self.progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report
|
||||||
|
if let Some(reporter) = self.reporter.as_mut() {
|
||||||
|
let progress = self.progress;
|
||||||
|
let _ = reporter.lock().map(|mut r| r.progress(progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), IoError> {
|
||||||
|
self.inner.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> ExactLengthReader for ProgressWriter<W> {
|
||||||
|
// Return the specified length.
|
||||||
|
fn len(&self) -> Result<u64, io::Error> {
|
||||||
|
Ok(self.len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use ffsend_api::action::download::Download as ApiDownload;
|
use ffsend_api::action::download::Download as ApiDownload;
|
||||||
use ffsend_api::file::file::DownloadFile;
|
use ffsend_api::file::file::DownloadFile;
|
||||||
use ffsend_api::reqwest::Client;
|
use ffsend_api::reqwest::Client;
|
||||||
|
|
||||||
use cmd::cmd_download::CmdDownload;
|
use cmd::cmd_download::CmdDownload;
|
||||||
|
use progress::ProgressBar;
|
||||||
|
|
||||||
/// A file download action.
|
/// A file download action.
|
||||||
pub struct Download<'a> {
|
pub struct Download<'a> {
|
||||||
|
@ -30,13 +33,14 @@ impl<'a> Download<'a> {
|
||||||
let file = DownloadFile::parse_url(url)
|
let file = DownloadFile::parse_url(url)
|
||||||
.expect("invalid download URL, could not parse file data");
|
.expect("invalid download URL, could not parse file data");
|
||||||
|
|
||||||
|
// Create a progress bar reporter
|
||||||
|
let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
|
||||||
|
|
||||||
// Execute an download action
|
// Execute an download action
|
||||||
// TODO: do not unwrap, but return an error
|
// TODO: do not unwrap, but return an error
|
||||||
ApiDownload::new(&file).invoke(&client).unwrap();
|
ApiDownload::new(&file).invoke(&client, bar).unwrap();
|
||||||
|
|
||||||
// TODO: open the file, or it's location
|
// TODO: open the file, or it's location
|
||||||
// TODO: copy the file location
|
// TODO: copy the file location
|
||||||
|
|
||||||
println!("Download complete");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl<'a> Upload<'a> {
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
|
||||||
// Create a progress bar reporter
|
// Create a progress bar reporter
|
||||||
let bar = Arc::new(Mutex::new(ProgressBar::new()));
|
let bar = Arc::new(Mutex::new(ProgressBar::new_upload()));
|
||||||
|
|
||||||
// Execute an upload action
|
// Execute an upload action
|
||||||
// TODO: do not unwrap, but return an error
|
// TODO: do not unwrap, but return an error
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
extern crate pbr;
|
extern crate pbr;
|
||||||
|
|
||||||
use std::io::Stdout;
|
use std::io::Stdout;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use ffsend_api::reader::ProgressReporter;
|
use ffsend_api::reader::ProgressReporter;
|
||||||
use self::pbr::{
|
use self::pbr::{
|
||||||
|
@ -8,26 +9,47 @@ use self::pbr::{
|
||||||
Units,
|
Units,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The refresh rate of the progress bar, in milliseconds.
|
||||||
|
const PROGRESS_BAR_FPS_MILLIS: u64 = 500;
|
||||||
|
|
||||||
/// A progress bar reporter.
|
/// A progress bar reporter.
|
||||||
pub struct ProgressBar {
|
pub struct ProgressBar<'a> {
|
||||||
bar: Option<Pbr<Stdout>>,
|
bar: Option<Pbr<Stdout>>,
|
||||||
|
msg_progress: &'a str,
|
||||||
|
msg_finish: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgressBar {
|
impl<'a> ProgressBar<'a> {
|
||||||
pub fn new() -> Self {
|
/// Construct a new progress bar, with the given messages.
|
||||||
|
pub fn new(msg_progress: &'a str, msg_finish: &'a str) -> ProgressBar<'a> {
|
||||||
Self {
|
Self {
|
||||||
bar: None,
|
bar: None,
|
||||||
}
|
msg_progress,
|
||||||
|
msg_finish,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgressReporter for ProgressBar {
|
/// Construct a new progress bar for uploading.
|
||||||
|
pub fn new_upload() -> ProgressBar<'a> {
|
||||||
|
Self::new("Encrypt & Upload ", "Upload complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new progress bar for downloading.
|
||||||
|
pub fn new_download() -> ProgressBar<'a> {
|
||||||
|
Self::new("Download & Decrypt ", "Download complete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ProgressReporter for ProgressBar<'a> {
|
||||||
/// Start the progress with the given total.
|
/// Start the progress with the given total.
|
||||||
fn start(&mut self, total: u64) {
|
fn start(&mut self, total: u64) {
|
||||||
// Initialize the progress bar
|
// Initialize the progress bar
|
||||||
let mut bar = Pbr::new(total);
|
let mut bar = Pbr::new(total);
|
||||||
|
bar.set_max_refresh_rate(
|
||||||
|
Some(Duration::from_millis(PROGRESS_BAR_FPS_MILLIS))
|
||||||
|
);
|
||||||
bar.set_units(Units::Bytes);
|
bar.set_units(Units::Bytes);
|
||||||
bar.message("Encrypt & Upload ");
|
bar.message(self.msg_progress);
|
||||||
|
|
||||||
self.bar = Some(bar);
|
self.bar = Some(bar);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +65,6 @@ impl ProgressReporter for ProgressBar {
|
||||||
fn finish(&mut self) {
|
fn finish(&mut self) {
|
||||||
self.bar.as_mut()
|
self.bar.as_mut()
|
||||||
.expect("progress bar not yet instantiated")
|
.expect("progress bar not yet instantiated")
|
||||||
.finish_print("Upload complete");
|
.finish_print(self.msg_finish);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue