Compare commits

...

2 commits

Author SHA1 Message Date
39560eeeed mark and eliminate some unwrap 2025-06-05 22:08:24 +00:00
faea74241d progress bar refactoring 2025-06-05 21:37:52 +00:00
6 changed files with 59 additions and 135 deletions

View file

@ -42,7 +42,8 @@
"terminal.integrated.defaultProfile.linux": "zsh" "terminal.integrated.defaultProfile.linux": "zsh"
}, },
"extensions": [ "extensions": [
"mhutchie.git-graph" "mhutchie.git-graph",
"Gruntfuggly.todo-tree"
] ]
} }
}, },

View file

@ -1,87 +0,0 @@
Diese Konversation wurde mit Duck.ai (https://duck.ai) unter Verwendung des o3-mini-Modells von OpenAI generiert. KI-Chats zeigen möglicherweise falsche oder anstößige Informationen an (weitere Informationen siehe https://duckduckgo.com/duckai/privacy-terms).
====================
Benutzereingabe 1 von 2 - 5.6.2025, 02:27:36:
in Rust, what can I use to display multiple progress bars in the terminal?
o3-mini:
You can use the indicatif crate, which provides a MultiProgress type that lets you manage and display multiple progress bars concurrently in a terminal.
Below is a simple example of how to use indicatifs MultiProgress to display multiple progress bars:
```rust
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::{thread, time::Duration};
fn main() {
// Create a MultiProgress container.
let m = MultiProgress::new();
// Create several progress bars.
let pb1 = m.add(ProgressBar::new(100));
pb1.set_style(
ProgressStyle::default_bar()
.template("{msg} {bar:40.cyan/blue} {pos:>3}/{len:3}")
.expect("Failed to set progress bar style"),
);
pb1.set_message("Downloading");
let pb2 = m.add(ProgressBar::new(50));
pb2.set_style(
ProgressStyle::default_bar()
.template("{msg} {bar:40.green/black} {pos:>3}/{len:3}")
.expect("Failed to set progress bar style"),
);
pb2.set_message("Processing");
// Optionally, spawn threads to update the progress bars concurrently.
let h1 = std::thread::spawn(move || {
for i in 0..=100 {
pb1.set_position(i);
thread::sleep(Duration::from_millis(50));
}
pb1.finish_with_message("Download complete");
});
let h2 = std::thread::spawn(move || {
for i in 0..=50 {
pb2.set_position(i);
thread::sleep(Duration::from_millis(100));
}
pb2.finish_with_message("Processing complete");
});
// Wait for threads to complete. MultiProgress takes care of rendering.
h1.join().expect("Thread 1 panicked");
h2.join().expect("Thread 2 panicked");
// You may also iterate on the multi-progress which blocks until all bars finish:
// m.join().unwrap();
}
```
### Steps Explained
1. **MultiProgress Container**:
Create a `MultiProgress` instance which is used to manage multiple progress bars.
2. **Progress Bars**:
Create individual progress bars using `MultiProgress::add()`. You can customize each progress bar with a message, style, and length.
3. **Concurrent Updates**:
The example spawns two threads one for each progress bar. Each thread updates its progress bar independently using `set_position()` and finishes the progress bar with `finish_with_message()`.
4. **Thread Joining**:
Ensure the main thread waits for the worker threads to complete to prevent the program from exiting prematurely.
### Installation
Make sure to add indicatif to your Cargo.toml:
```toml
[dependencies]
indicatif = "0.17" # or the latest version available on crates.io
```
This crate provides many options for customizing and using progress bars in a concurrent and effective manner. The documentation is available at [indicatif on docs.rs](https://docs.rs/indicatif) for further details and advanced usage.

View file

@ -3,6 +3,7 @@ use std::{
fs, fs,
io::{self, Write}, io::{self, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
time::Duration,
}; };
use console::style; use console::style;
@ -40,6 +41,18 @@ impl FileState {
FileState::U(uploading) => uploading.file_name(), FileState::U(uploading) => uploading.file_name(),
} }
} }
fn start_upload(
self,
http: &ureq::Agent,
alias: &Alias,
share: &Share,
) -> io::Result<FileUploading> {
match self {
FileState::C(checked) => checked.start_upload(http, alias, share),
FileState::U(uploading) => Ok(uploading),
}
}
} }
impl AppState { impl AppState {
@ -109,44 +122,39 @@ impl AppState {
http: &ureq::Agent, http: &ureq::Agent,
chunk_size: usize, chunk_size: usize,
) -> Result<Option<()>, UploadError> { ) -> Result<Option<()>, UploadError> {
let uploading = match self.files.pop_front() { let uploading = if let Some(state) = self.files.pop_front() {
Some(FileState::C(checked)) => checked state.start_upload(http, &self.alias, &self.share).unwrap() // HACK unwrap
.start_upload(http, &self.alias, &self.share) } else {
.unwrap(), return Ok(None);
Some(FileState::U(uploading)) => uploading,
None => return Ok(None),
}; };
debug!("{uploading} chunk {chunk_size}"); debug!("{uploading} chunk {chunk_size}");
let pb = match self.progress { // Initialize or fetch the existing ProgressBar in one call:
Some(ref pb) => pb, let bar = &*self.progress.get_or_insert_with(|| {
None => { // Create a new bar with style
self.progress = Some({ let bar = ProgressBar::new(uploading.size())
let pb = { .with_style(
let ps = ProgressStyle::with_template(&format!( ProgressStyle::with_template(&format!(
"{{msg:.yellow}}: {{bar:50.cyan/blue}} {{binary_bytes:.magenta}}{}{{binary_total_bytes:.magenta}} ({{elapsed}})", concat!(
style("/").magenta(), "{{msg:.yellow}}: {{bar:50.cyan/blue}} ",
)) "{{binary_bytes:.magenta}}{}{{binary_total_bytes:.magenta}} ",
.unwrap(); "({{eta}})",
),
style("/").magenta(),
))
.unwrap(),
)
.with_message(uploading.file_name().to_owned())
.with_position(uploading.offset());
ProgressBar::new(uploading.size()) bar.enable_steady_tick(Duration::from_millis(100));
.with_style(ps) bar
.with_message(uploading.file_name().to_owned()) });
.with_position(uploading.offset())
};
pb.tick();
pb
});
self.progress.as_ref().unwrap()
}
};
pb.tick();
match uploading.upload_chunk(http, &self.alias, chunk_size) { match uploading.upload_chunk(http, &self.alias, chunk_size) {
ChunkState::Ok(upl) => { ChunkState::Ok(upl) => {
pb.set_position(upl.offset()); bar.set_position(upl.offset());
self.files.push_front(FileState::U(upl)); self.files.push_front(FileState::U(upl));
Ok(Some(())) Ok(Some(()))
} }
@ -156,9 +164,9 @@ impl AppState {
} }
ChunkState::Finished(path) => { ChunkState::Finished(path) => {
debug!("Finished {:?}!", path.display()); debug!("Finished {:?}!", path.display());
pb.finish(); bar.finish();
self.progress = None; self.progress = None;
self.share.notify(http, &self.alias).unwrap(); self.share.notify(http, &self.alias).unwrap(); // HACK unwrap
Ok(self.files.front().map(drop)) Ok(self.files.front().map(drop))
} }

View file

@ -59,7 +59,7 @@ fn main() {
match AppState::from_args(&args, &agent) { match AppState::from_args(&args, &agent) {
Ok(state) => { Ok(state) => {
state.save().unwrap(); state.save().unwrap(); // HACK unwrap
state state
} }
Err(e) => { Err(e) => {
@ -98,13 +98,13 @@ fn main() {
Err(e) => error!("error: {e:?}"), Err(e) => error!("error: {e:?}"),
Ok(None) => { Ok(None) => {
info!("all uploads done"); info!("all uploads done");
state.clear().unwrap(); state.clear().unwrap(); // HACK unwrap
break; break;
} }
_ => (), _ => (),
} }
state.save().unwrap(); state.save().unwrap(); // HACK unwrap
stop.load(Ordering::SeqCst).then(|| exit(0)); stop.load(Ordering::SeqCst).then(|| exit(0));
} }
} }

View file

@ -1,7 +1,6 @@
use std::{ use std::{
ffi::OsStr, ffi::OsStr,
fs, fs, io,
io::{self, ErrorKind},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -25,14 +24,17 @@ impl FileChecked {
}) })
} else { } else {
Err(io::Error::new( Err(io::Error::new(
ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Not a regular file", "Not a regular file",
)) ))
} }
} }
pub fn file_name(&self) -> &str { pub fn file_name(&self) -> &str {
self.path.file_name().unwrap().to_str().unwrap() self.path
.file_name()
.and_then(OsStr::to_str)
.expect("bad file name")
} }
pub fn start_upload( pub fn start_upload(
@ -46,13 +48,9 @@ impl FileChecked {
let res = { let res = {
let endpoint = alias.get_endpoint(format!("alias/upload/{}/files/tus", share.id)); let endpoint = alias.get_endpoint(format!("alias/upload/{}/files/tus", share.id));
let name = (self.path.file_name().and_then(OsStr::to_str))
.ok_or_else(|| io::Error::new(ErrorKind::NotFound, "bad file name"))?
.to_string();
(http.post(endpoint)) (http.post(endpoint))
.sharry_header(alias) .sharry_header(alias)
.header("Sharry-File-Name", &name) .header("Sharry-File-Name", self.file_name())
.header("Upload-Length", size) .header("Upload-Length", size)
.send_empty() .send_empty()
.map_err(io::Error::other)? .map_err(io::Error::other)?

View file

@ -1,6 +1,7 @@
use std::{ use std::{
ffi::OsStr,
fmt::{Debug, Display}, fmt::{Debug, Display},
fs::File, fs,
io::{self, Read, Seek, SeekFrom}, io::{self, Read, Seek, SeekFrom},
path::PathBuf, path::PathBuf,
}; };
@ -58,7 +59,7 @@ impl FileUploading {
fn read_chunk(&self, chunk_size: usize) -> io::Result<Vec<u8>> { fn read_chunk(&self, chunk_size: usize) -> io::Result<Vec<u8>> {
let offset = u64::try_from(self.offset).map_err(io::Error::other)?; let offset = u64::try_from(self.offset).map_err(io::Error::other)?;
let mut f = File::open(&self.path)?; let mut f = fs::File::open(&self.path)?;
f.seek(SeekFrom::Start(offset))?; f.seek(SeekFrom::Start(offset))?;
let mut bytes = vec![0; chunk_size]; let mut bytes = vec![0; chunk_size];
@ -69,7 +70,10 @@ impl FileUploading {
} }
pub fn file_name(&self) -> &str { pub fn file_name(&self) -> &str {
self.path.file_name().unwrap().to_str().unwrap() self.path
.file_name()
.and_then(OsStr::to_str)
.expect("bad file name")
} }
pub fn offset<T>(&self) -> T pub fn offset<T>(&self) -> T