Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c2ecece
feat: add auth_store for UploadOpts and update get_auth_store
magentaqin Jul 31, 2025
7796ddd
fix: fix linting
magentaqin Jul 31, 2025
d05406c
feat: add regex package
magentaqin Aug 1, 2025
a490759
feat: check server type by host
magentaqin Aug 1, 2025
9e1a01a
feat: extract Quetz and Artifactory info from host
magentaqin Aug 1, 2025
676a6f7
feat: extract prefix base_url and channel from host
magentaqin Aug 4, 2025
63f33ee
feat: move pixi_progress to rattler
magentaqin Aug 4, 2025
29a1f5c
feat: extract s3 info
magentaqin Aug 4, 2025
fc4a790
feat: make host and server_type optional in UploadOpts
magentaqin Aug 4, 2025
96ea212
feat: prefix upload supports rattler_progress
magentaqin Aug 4, 2025
7718b02
fix: update rattler_progress name
magentaqin Aug 4, 2025
cfc2690
feat: detect server type and extract components from host
magentaqin Aug 4, 2025
9f3b4bb
feat: update Cargo.toml
magentaqin Aug 4, 2025
05e9c12
fix: fix Cargo.toml
magentaqin Aug 4, 2025
00d0a45
fix: fix s3 type
magentaqin Aug 4, 2025
0bf614c
fix: fix import error
magentaqin Aug 4, 2025
af31a6a
chore: cargo fmt
magentaqin Aug 4, 2025
9c8c525
fix: fix linting error
magentaqin Aug 5, 2025
1c0fdd8
fix: fix 'auth_store' type
magentaqin Aug 7, 2025
09777b0
fix: fix linting issue
magentaqin Aug 7, 2025
4573261
Merge branch 'feature/upload-auth-store' into feature/upload-url
magentaqin Aug 8, 2025
1ead736
chore: sync cargo.lock
magentaqin Aug 12, 2025
5f71202
Merge branch 'main' into feature/upload-url
magentaqin Aug 12, 2025
d23039e
refactor: replace RegExp with URL parsing
magentaqin Aug 12, 2025
0958d9f
chore: update default region
magentaqin Aug 12, 2025
db59292
Merge main
magentaqin Aug 21, 2025
8b5e6e8
fmt
magentaqin Aug 21, 2025
99dd9e7
Merge main
magentaqin Aug 26, 2025
9eadb87
refactor: remove unecessary 'rattler_progress'
magentaqin Aug 26, 2025
bb464fb
Merge main and update s3 api
magentaqin Sep 9, 2025
c709b62
fmt
magentaqin Sep 9, 2025
5a824b4
fix: fix the region not working
magentaqin Sep 17, 2025
3ae4aef
Merge main and solve conflicts
magentaqin Sep 17, 2025
d18ba47
feat: optimize progressbar for S3 uploading and responds when package…
magentaqin Sep 23, 2025
09ddbb4
fix: fix lint err
magentaqin Sep 25, 2025
059db57
Merge branch 'feature/upload-url' of github.com:magentaqin/rattler in…
magentaqin Sep 25, 2025
16d1fd0
fix: fix S3 force option
magentaqin Sep 25, 2025
9d62564
feat: add addressing_style for 'S3Opts'
magentaqin Sep 25, 2025
e3fa364
Merge main
magentaqin Sep 29, 2025
54c5c18
Merge main
magentaqin Sep 30, 2025
9bf9bb2
Merge main
magentaqin Oct 6, 2025
5fc6195
refactor: reuse the S3CredentialsOpts
magentaqin Oct 6, 2025
14a0e82
chore: remove crate 'regex'
magentaqin Oct 6, 2025
7650fa2
remove S3 related changes for now
wolfv Oct 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ rattler_sandbox = { path = "crates/rattler_sandbox", version = "=0.1.10", defaul
rattler_shell = { path = "crates/rattler_shell", version = "=0.24.7", default-features = false }
rattler_solve = { path = "crates/rattler_solve", version = "=2.1.8", default-features = false }
rattler_virtual_packages = { path = "crates/rattler_virtual_packages", version = "=2.1.1", default-features = false }
rattler_upload = { path = "crates/rattler_upload", version = "=0.1.3", default-features = false }
rattler_progress = { path = "crates/rattler_progress", version = "=0.1.0", default-features = false }

# This is also a rattler crate, but we only pin it to minor version
simple_spawn_blocking = { path = "crates/simple_spawn_blocking", version = "1.1", default-features = false }
16 changes: 16 additions & 0 deletions crates/rattler_progress/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "rattler_progress"
version = "0.1.0"
edition.workspace = true
authors = ["Bas Zalmstra <[email protected]>", "Magenta Qin <[email protected]>"]
description = "A crate to show progress bar"
categories.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
readme.workspace = true


[dependencies]
indicatif = { workspace = true }
parking_lot = { workspace = true }
247 changes: 247 additions & 0 deletions crates/rattler_progress/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
mod placement;
pub mod style;

use std::{
borrow::Cow,
fmt::Write,
future::Future,
sync::{Arc, LazyLock},
time::Duration,
};

use indicatif::{HumanBytes, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState};
use parking_lot::Mutex;
pub use placement::ProgressBarPlacement;

/// A helper macro to print a message to the console. If a multi-progress bar
/// is currently active, this macro will suspend the progress bar, print the
/// message and continue the progress bar. This ensures the output does not
/// interfere with the progress bar.
///
/// If the progress bar is hidden, the message will be printed to `stderr`
/// instead.
#[macro_export]
macro_rules! println {
() => {
let mp = $crate::global_multi_progress();
if mp.is_hidden() {
eprintln!();
} else {
// Ignore any error
let _err = mp.println("");
}
};
($($arg:tt)*) => {
let mp = $crate::global_multi_progress();
if mp.is_hidden() {
eprintln!($($arg)*);
} else {
// Ignore any error
let _err = mp.println(format!($($arg)*));
}
}
}

/// Returns a global instance of [`indicatif::MultiProgress`].
///
/// Although you can always create an instance yourself any logging will
/// interrupt pending progressbars. To fix this issue, logging has been
/// configured in such a way to it will not interfere if you use the
/// [`indicatif::MultiProgress`] returning by this function.
pub fn global_multi_progress() -> MultiProgress {
static GLOBAL_MP: LazyLock<MultiProgress> = LazyLock::new(|| {
let mp = MultiProgress::new();
mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(20));
mp
});
GLOBAL_MP.clone()
}

/// Returns the style to use for a progressbar that is currently in progress.
pub fn default_bytes_style() -> indicatif::ProgressStyle {
indicatif::ProgressStyle::default_bar()
.template(" {spinner:.dim} {prefix:20!} [{elapsed_precise}] [{bar:20!.bright.yellow/dim.white}] {bytes:>8} @ {smoothed_bytes_per_sec:8}").unwrap()
.progress_chars("━━╾─")
.with_key(
"smoothed_bytes_per_sec",
|s: &ProgressState, w: &mut dyn Write| match (s.pos(), s.elapsed().as_millis()) {
(pos, elapsed_ms) if elapsed_ms > 0 => {
write!(w, "{}/s", HumanBytes((pos as f64 * 1000_f64 / elapsed_ms as f64) as u64)).unwrap();
}
_ => write!(w, "-").unwrap(),
},
)
}

/// Returns the style to use for a progressbar that is currently in progress.
pub fn default_progress_style() -> indicatif::ProgressStyle {
indicatif::ProgressStyle::default_bar()
.template(" {spinner:.dim} {prefix:20!} [{elapsed_precise}] [{bar:20!.bright.yellow/dim.white}] {pos:>4}/{len:4} {wide_msg:.dim}").unwrap()
.progress_chars("━━╾─")
}

/// Returns the style to use for a progressbar that is indeterminate and simply
/// shows a spinner.
pub fn long_running_progress_style() -> indicatif::ProgressStyle {
indicatif::ProgressStyle::with_template("{prefix}{spinner:.green} {msg}").unwrap()
}

/// Displays a spinner with the given message while running the specified
/// function to completion.
pub fn wrap_in_progress<T, F: FnOnce() -> T>(msg: impl Into<Cow<'static, str>>, func: F) -> T {
let pb = global_multi_progress().add(ProgressBar::new_spinner());
pb.enable_steady_tick(Duration::from_millis(100));
pb.set_style(long_running_progress_style());
pb.set_message(msg);
let result = func();
pb.finish_and_clear();
result
}

/// Displays a spinner with the given message while running the specified
/// function to completion.
pub async fn await_in_progress<T, F: FnOnce(ProgressBar) -> Fut, Fut: Future<Output = T>>(
msg: impl Into<Cow<'static, str>>,
future: F,
) -> T {
let msg = msg.into();
let (prefix, msg) = match msg.find(|c: char| !c.is_whitespace()) {
Some(idx) if idx > 0 => msg.split_at(idx),
_ => ("", msg.as_ref()),
};

let pb = global_multi_progress().add(ProgressBar::new_spinner());
pb.enable_steady_tick(Duration::from_millis(100));
pb.set_style(long_running_progress_style());
pb.set_prefix(prefix.to_string());
pb.set_message(msg.to_string());
let result = future(pb.clone()).await;
pb.finish_and_clear();
result
}

/// Style an existing progress bar with a warning style and the given message.
pub fn style_warning_pb(pb: ProgressBar, warning_msg: String) -> ProgressBar {
pb.set_style(
indicatif::ProgressStyle::default_spinner() // Or default_bar() if you used ProgressBar::new(length)
.template(" {spinner:.yellow} {wide_msg:.yellow}") // Yellow spinner, clear message
.expect("failed to set a progress bar template"),
);
pb.set_message(warning_msg);
pb.enable_steady_tick(Duration::from_millis(100));
pb
}

/// A struct that can be used to format the message part of a progress bar.
///
/// It's primary usecase is when you have a single progress bar but multiple
/// tasks that are running and which you want to communicate to the user. This
/// struct will set the message part of the passed progress bar to the oldest
/// unfinished task and include a the number of pending tasks.
#[derive(Debug)]
pub struct ProgressBarMessageFormatter {
state: Arc<Mutex<State>>,
}

/// Internal state kept by the [`ProgressBarMessageFormatter`] and derived
/// state.
///
/// This contains the state of the formatter and allows updating the progress
/// bar.
#[derive(Debug)]
struct State {
pb: ProgressBar,
pending: Vec<String>,
}

impl State {
/// Notify the state that a certain operation happened.
fn notify(&mut self, msg: Operation) {
match msg {
Operation::Started(op) => self.pending.push(op),
Operation::Finished(op) => {
let Some(idx) = self.pending.iter().position(|p| p == &op) else {
panic!("operation {op} was never started");
};
self.pending.remove(idx);
}
}

if self.pending.is_empty() {
self.pb.set_message("");
} else if self.pending.len() == 1 {
self.pb.set_message(self.pending[0].clone());
} else {
self.pb.set_message(format!(
"{} (+{})",
self.pending.last().unwrap(),
self.pending.len() - 1
));
}
}
}

#[derive(Debug)]
enum Operation {
Started(String),
Finished(String),
}

pub struct ScopedTask {
state: Option<Arc<Mutex<State>>>,
name: String,
}

impl ScopedTask {
fn start(name: String, state: Arc<Mutex<State>>) -> Self {
state.lock().notify(Operation::Started(name.clone()));
Self {
state: Some(state),
name,
}
}

/// Finishes the execution of the task.
pub fn finish(self) {
drop(self);
}
}

impl Drop for ScopedTask {
fn drop(&mut self) {
if let Some(state) = self.state.take() {
state
.lock()
.notify(Operation::Finished(std::mem::take(&mut self.name)));
}
}
}

impl ProgressBarMessageFormatter {
/// Allows the user to specify a custom capacity for the internal channel.
pub fn new(pb: ProgressBar) -> Self {
Self {
state: Arc::new(Mutex::new(State {
pb,
pending: Vec::new(),
})),
}
}

/// Adds the start of another task to the progress bar and returns an object
/// that is used to mark the lifetime of the task. If the object is
/// dropped the task is considered finished.
#[must_use]
pub fn start(&self, op: String) -> ScopedTask {
ScopedTask::start(op, self.state.clone())
}

/// Wraps an future into a task which starts when the task starts and ends
/// when the future returns.
pub async fn wrap<T, F: Future<Output = T>>(&self, name: impl Into<String>, fut: F) -> T {
let task = self.start(name.into());
let result = fut.await;
task.finish();
result
}
}
28 changes: 28 additions & 0 deletions crates/rattler_progress/src/placement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use indicatif::{MultiProgress, ProgressBar};

#[derive(Default, Clone)]
pub enum ProgressBarPlacement {
/// The progress bar is placed as the first progress bar.
Top,
/// The progress bar is placed as the last progress bar.
#[default]
Bottom,
/// The progress bar is placed after the given progress bar.
After(ProgressBar),
/// The progress bar is placed before the given progress bar.
Before(ProgressBar),
}

impl ProgressBarPlacement {
/// Add the specified progress bar to the given multi progress instance
pub fn insert(&self, multi_progress: MultiProgress, progress_bar: ProgressBar) -> ProgressBar {
match self {
ProgressBarPlacement::After(after) => multi_progress.insert_after(after, progress_bar),
ProgressBarPlacement::Before(before) => {
multi_progress.insert_before(before, progress_bar)
}
ProgressBarPlacement::Top => multi_progress.insert(0, progress_bar),
ProgressBarPlacement::Bottom => multi_progress.add(progress_bar),
}
}
}
27 changes: 27 additions & 0 deletions crates/rattler_progress/src/style.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Defines some functions that can be used to style indicatif progress bars.

/// The characters to use to show progress in the progress bar.
const DEFAULT_PROGRESS_CHARS: &str = "━━╾─";

/// The characters that make up animation of a spinner that should be used when
/// the progress bar is currently making progress.
const DEFAULT_RUNNING_SPINNER_CHARS: &str = "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ ";

/// The characters that make up an animation of a spinner that should be used
/// when the progress bar is currently paused or not making progress.
const DEFAULT_PAUSED_SPINNER_CHARS: &str = "▪▪";

/// Returns the "tick chars" that are used to represent a spinner animation of a
/// progress bar.
pub fn tick_chars(active: bool) -> &'static str {
if active {
DEFAULT_RUNNING_SPINNER_CHARS
} else {
DEFAULT_PAUSED_SPINNER_CHARS
}
}

/// Returns the "progress chars" that are used to render a progress bar.
pub fn progress_chars(_active: bool) -> &'static str {
DEFAULT_PROGRESS_CHARS
}
4 changes: 3 additions & 1 deletion crates/rattler_upload/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ rattler_networking = { workspace = true, features = ["rattler_config" ]}
rattler_redaction = { workspace = true, default-features = false }
rattler_package_streaming = { workspace = true, default-features = false }
rattler_config = { workspace = true, default-features = false }
rattler_solve = { workspace = true }
rattler_solve = { workspace = true, default-features = false }
rattler_progress = { workspace = true, default-features = false }
miette = { version = "7.6.0", features = ["fancy"] }
clap = { version = "4.5.37", features = ["derive", "env", "cargo"] }
fs-err = { workspace = true, features = ["tokio"] }
Expand Down Expand Up @@ -49,6 +50,7 @@ tokio = { version = "1.44.2", features = [
"rt-multi-thread",
"process",
] }
regex = "1.10"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it. It was used before refactor.


[target.'cfg(not(target_os = "windows"))'.dependencies]
sha2 = { version = "0.10.8", features = ["asm"] }
Expand Down
Loading
Loading