Skip to content

Commit dd11ab9

Browse files
authored
fix: improve message displayed in transfer progress bars. (#23)
* fix: improve message displayed in transfer progress bars. This commit improves the message displayed in transfer progress bars to show the source file for uploads and source URL for downloads. Additionally, this shortens the progress bar so that the message isn't too far to the right given the rest of the information displayed. Fixes #22. * chore: update CHANGELOG.
1 parent 1034447 commit dd11ab9

7 files changed

Lines changed: 230 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
#### Fixed
11+
12+
* Improved progress bar message ([#23](https://github.com/stjude-rust-labs/cloud-copy/pull/23)).
13+
1014
## 0.3.0 - 09-15-2025
1115

1216
#### Added

Cargo.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ walkdir = "2.5.0"
6565

6666
[dev-dependencies]
6767
anyhow = { version = "1.0.99" }
68+
pretty_assertions = "1.4.1"
6869

6970
[lints.rust]
7071
missing_docs = "warn"

src/cli.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use tracing::warn_span;
1212
use tracing_indicatif::span_ext::IndicatifSpanExt;
1313
use tracing_indicatif::style::ProgressStyle;
1414

15+
use crate::Location;
1516
use crate::TransferEvent;
17+
use crate::UrlExt;
1618

1719
/// Extension methods for [`TimeDelta`].
1820
#[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
@@ -135,14 +137,14 @@ pub async fn handle_events(
135137
_ = cancel.cancelled() => break,
136138
event = events.recv() => match event {
137139
Ok(event) if indeterminate.is_none() => match event {
138-
TransferEvent::TransferStarted { id, path, size, .. } => {
140+
TransferEvent::TransferStarted { id, source, destination, size, .. } => {
139141
let bar = warn_span!("progress");
140142

141143
let style = match size {
142144
Some(size) => {
143145
bar.pb_set_length(size);
144146
ProgressStyle::with_template(
145-
"[{elapsed_precise:.cyan/blue}] {bar:40.cyan/blue} \
147+
"[{elapsed_precise:.cyan/blue}] {bar:20.cyan/blue} \
146148
{bytes:.cyan/blue} / {total_bytes:.cyan/blue} \
147149
({bytes_per_sec:.cyan/blue}) [ETA {eta_precise:.cyan/blue}]: \
148150
{msg}",
@@ -156,8 +158,17 @@ pub async fn handle_events(
156158
.unwrap(),
157159
};
158160

161+
let message = match (&source, &destination) {
162+
// Use the source path for local file copies
163+
(Location::Path(path), Location::Path(_)) => format!("copying `{path}`", path = path.to_str().unwrap_or("<path not UTF-8>")),
164+
// Use the source path for uploads
165+
(Location::Path(path), _) => format!("uploading `{path}`", path = path.to_str().unwrap_or("<path not UTF-8>")),
166+
// Use the remote URL for downloads
167+
(Location::Url(url), _) => format!("downloading `{url}`", url = url.display())
168+
};
169+
159170
bar.pb_set_style(&style);
160-
bar.pb_set_message(path.to_str().unwrap_or("<path not UTF-8>"));
171+
bar.pb_set_message(&message);
161172
bar.pb_start();
162173
transfers.insert(
163174
id,

src/lib.rs

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,24 @@ fn notify_retry(e: &Error, duration: Duration) {
9494

9595
/// Represents either a local or remote location.
9696
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
97-
pub enum Location<'a> {
97+
pub enum Location {
9898
/// The location is a local path.
99-
Path(&'a Path),
99+
Path(PathBuf),
100100
/// The location is a URL.
101-
Url(Cow<'a, Url>),
101+
Url(Url),
102102
}
103103

104-
impl<'a> Location<'a> {
104+
impl Location {
105105
/// Constructs a new location from a string.
106-
pub fn new(s: &'a str) -> Self {
106+
pub fn new(s: &str) -> Self {
107107
match s.parse::<Url>() {
108-
Ok(url) => Self::Url(Cow::Owned(url)),
109-
Err(_) => Self::Path(Path::new(s)),
108+
Ok(url) => Self::Url(url),
109+
Err(_) => Self::Path(s.into()),
110110
}
111111
}
112112
}
113113

114-
impl fmt::Display for Location<'_> {
114+
impl fmt::Display for Location {
115115
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116116
match self {
117117
Self::Path(path) => write!(f, "{path}", path = path.display()),
@@ -120,39 +120,45 @@ impl fmt::Display for Location<'_> {
120120
}
121121
}
122122

123-
impl<'a> From<&'a str> for Location<'a> {
124-
fn from(value: &'a str) -> Self {
123+
impl From<&str> for Location {
124+
fn from(value: &str) -> Self {
125125
Self::new(value)
126126
}
127127
}
128128

129-
impl<'a> From<&'a String> for Location<'a> {
130-
fn from(value: &'a String) -> Self {
129+
impl From<&String> for Location {
130+
fn from(value: &String) -> Self {
131131
Self::new(value)
132132
}
133133
}
134134

135-
impl<'a> From<&'a Path> for Location<'a> {
136-
fn from(value: &'a Path) -> Self {
137-
Self::Path(value)
135+
impl From<&Path> for Location {
136+
fn from(value: &Path) -> Self {
137+
Self::Path(value.to_path_buf())
138138
}
139139
}
140140

141-
impl<'a> From<&'a PathBuf> for Location<'a> {
142-
fn from(value: &'a PathBuf) -> Self {
143-
Self::Path(value.as_path())
141+
impl From<&PathBuf> for Location {
142+
fn from(value: &PathBuf) -> Self {
143+
Self::Path(value.clone())
144+
}
145+
}
146+
147+
impl From<PathBuf> for Location {
148+
fn from(value: PathBuf) -> Self {
149+
Self::Path(value)
144150
}
145151
}
146152

147-
impl<'a> From<&'a Url> for Location<'a> {
148-
fn from(value: &'a Url) -> Self {
149-
Self::Url(Cow::Borrowed(value))
153+
impl From<&Url> for Location {
154+
fn from(value: &Url) -> Self {
155+
Self::Url(value.clone())
150156
}
151157
}
152158

153-
impl From<Url> for Location<'_> {
159+
impl From<Url> for Location {
154160
fn from(value: Url) -> Self {
155-
Self::Url(Cow::Owned(value))
161+
Self::Url(value)
156162
}
157163
}
158164

@@ -440,8 +446,10 @@ pub enum TransferEvent {
440446
///
441447
/// This is a monotonic counter that is increased every transfer.
442448
id: u64,
443-
/// The path of the file being transferred.
444-
path: PathBuf,
449+
/// The location of the source.
450+
source: Location,
451+
/// The location of the destination.
452+
destination: Location,
445453
/// The number of blocks in the file.
446454
blocks: u64,
447455
/// The size of the file being transferred.
@@ -527,7 +535,8 @@ async fn copy_local(
527535
events
528536
.send(TransferEvent::TransferStarted {
529537
id: ID,
530-
path: destination.to_path_buf(),
538+
source: Location::Path(source.to_path_buf()),
539+
destination: Location::Path(destination.to_path_buf()),
531540
blocks: 1,
532541
size: Some(size),
533542
})
@@ -622,8 +631,8 @@ async fn copy_local(
622631
pub async fn copy(
623632
config: Config,
624633
client: HttpClient,
625-
source: impl Into<Location<'_>>,
626-
destination: impl Into<Location<'_>>,
634+
source: impl Into<Location>,
635+
destination: impl Into<Location>,
627636
cancel: CancellationToken,
628637
events: Option<broadcast::Sender<TransferEvent>>,
629638
) -> Result<()> {
@@ -637,12 +646,12 @@ pub async fn copy(
637646
}
638647

639648
// Two local locations, just perform a copy
640-
Ok(copy_local(source, destination, cancel, events).await?)
649+
Ok(copy_local(&source, &destination, cancel, events).await?)
641650
}
642651
(Location::Path(source), Location::Url(destination)) => {
643652
// Perform a copy if the the destination is a local path
644653
if let Some(destination) = destination.to_local_path()? {
645-
return copy_local(source, &destination, cancel, events).await;
654+
return copy_local(&source, &destination, cancel, events).await;
646655
}
647656

648657
if AzureBlobStorageBackend::is_supported_url(&config, &destination) {
@@ -661,7 +670,7 @@ pub async fn copy(
661670
FileTransfer::new(GoogleStorageBackend::new(config, client, events), cancel);
662671
transfer.upload(source, destination.into_owned()).await
663672
} else {
664-
Err(Error::UnsupportedUrl(destination.into_owned()))
673+
Err(Error::UnsupportedUrl(destination))
665674
}
666675
}
667676
(Location::Url(source), Location::Path(destination)) => {
@@ -671,7 +680,7 @@ pub async fn copy(
671680

672681
// Perform a copy if the the source is a local path
673682
if let Some(source) = source.to_local_path()? {
674-
return copy_local(&source, destination, cancel, events).await;
683+
return copy_local(&source, &destination, cancel, events).await;
675684
}
676685

677686
if AzureBlobStorageBackend::is_supported_url(&config, &source) {
@@ -692,7 +701,7 @@ pub async fn copy(
692701
} else {
693702
let transfer =
694703
FileTransfer::new(GenericStorageBackend::new(config, client, events), cancel);
695-
transfer.download(source.into_owned(), destination).await
704+
transfer.download(source, destination).await
696705
}
697706
}
698707
(Location::Url(source), Location::Url(destination)) => {

src/transfer.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use url::Url;
3131
use walkdir::WalkDir;
3232

3333
use crate::Error;
34+
use crate::Location;
3435
use crate::Result;
3536
use crate::TransferEvent;
3637
use crate::UrlExt;
@@ -124,6 +125,7 @@ where
124125
.get(header::CONTENT_LENGTH)
125126
.and_then(|v| v.to_str().ok()?.parse().ok());
126127

128+
let download_source = source.clone();
127129
let transfer = async {
128130
// Start by creating the destination's parent directory
129131
let parent = destination.parent().ok_or(Error::InvalidPath)?;
@@ -140,8 +142,15 @@ where
140142
.into_temp_path();
141143

142144
// Download the file with resumable retries
143-
self.download_with_resume(id, source, &temp, content_length, etag, cancel.clone())
144-
.await?;
145+
self.download_with_resume(
146+
id,
147+
download_source,
148+
&temp,
149+
content_length,
150+
etag,
151+
cancel.clone(),
152+
)
153+
.await?;
145154

146155
// Persist the temp file to the destination
147156
temp.persist_noclobber(destination)
@@ -155,7 +164,8 @@ where
155164
events
156165
.send(TransferEvent::TransferStarted {
157166
id,
158-
path: destination.to_path_buf(),
167+
source: Location::Url(source),
168+
destination: Location::Path(destination.to_path_buf()),
159169
blocks: 1,
160170
size: content_length,
161171
})
@@ -669,7 +679,8 @@ where
669679
events
670680
.send(TransferEvent::TransferStarted {
671681
id,
672-
path: source.to_path_buf(),
682+
source: Location::Path(source.to_path_buf()),
683+
destination: Location::Url(destination),
673684
blocks: num_blocks,
674685
size: Some(file_size),
675686
})

0 commit comments

Comments
 (0)