pull,package_info,ui,cli: report the pull flow via BuildView
Retire the ProgressCallback option-of-closure, the one pre-port reporting idiom left in the tree: pull and package_info::lookup now take &dyn BuildView like every other core flow, and the announcements map onto message (unsized work), progress (item counts: series probes, git objects, extraction) and progress again for byte downloads, which the old callback carried in the same 4-argument shape. The terminal side swaps the (ProgressBar, closure) pair for PullView, a BuildView implementation owning one bar of the shared MultiProgress with the same spinner/transfer style switching; it releases the line on drop, which the CLI triggers explicitly on both outcomes since exit(1) skips destructors. Headless callers (tests, internal lookups) pass &report::Quiet. git clone sideband parsing is skipped entirely when the view is not enabled, matching the old None-callback shortcut.
This commit is contained in:
+2
-2
@@ -2284,13 +2284,13 @@ Provides: virtual-thing (= 2.0), plain-virtual
|
||||
Some(dist),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&crate::report::Quiet,
|
||||
))
|
||||
.unwrap_or_else(|e| panic!("package lookup failed for {}: {}", package, e));
|
||||
rt.block_on(crate::pull::pull(
|
||||
&package_info,
|
||||
Some(fetch_dir.path()),
|
||||
None,
|
||||
&crate::report::Quiet,
|
||||
true,
|
||||
))
|
||||
.unwrap_or_else(|e| panic!("pull failed for {}: {}", package, e));
|
||||
|
||||
+11
-3
@@ -1323,11 +1323,19 @@ mod tests {
|
||||
log::debug!("Created temporary directory: {}", cwd.display());
|
||||
|
||||
log::info!("Pulling package {} from {}...", package, series);
|
||||
let package_info =
|
||||
crate::package_info::lookup(package, None, Some(series), "", dist, None, None, None)
|
||||
let package_info = crate::package_info::lookup(
|
||||
package,
|
||||
None,
|
||||
Some(series),
|
||||
"",
|
||||
dist,
|
||||
None,
|
||||
None,
|
||||
&crate::report::Quiet,
|
||||
)
|
||||
.await
|
||||
.expect("Cannot lookup package information");
|
||||
crate::pull::pull(&package_info, Some(cwd), None, true)
|
||||
crate::pull::pull(&package_info, Some(cwd), &crate::report::Quiet, true)
|
||||
.await
|
||||
.expect("Cannot pull package");
|
||||
log::info!("Successfully pulled package {}", package);
|
||||
|
||||
@@ -63,13 +63,6 @@ pub(crate) mod test_support;
|
||||
/// Utility functions
|
||||
pub(crate) mod utils;
|
||||
|
||||
/// Optional callback function (taking 4 arguments)
|
||||
/// - Name of the current main operation (e.g. pulling package)
|
||||
/// - Name of the current nested operation (e.g. cloning git repo)
|
||||
/// - Progress, position, index of current operation (e.g. amount of data downloaded)
|
||||
/// - Total amount for current operation (e.g. size of the file to download)
|
||||
pub type ProgressCallback<'a> = Option<&'a dyn Fn(&str, &str, usize, usize)>;
|
||||
|
||||
/// Returns the architecture of current CPU, debian-compatible
|
||||
pub fn get_current_arch() -> String {
|
||||
match std::env::consts::ARCH {
|
||||
|
||||
+6
-6
@@ -670,7 +670,7 @@ fn main() {
|
||||
.unwrap_or("");
|
||||
let archive = sub_matches.get_one::<bool>("archive").unwrap_or(&false);
|
||||
|
||||
let (pb, progress_callback) = pkh::ui::create_progress_bar(&multi);
|
||||
let view = pkh::ui::PullView::new(&multi);
|
||||
|
||||
// Convert PPA to base URL if provided
|
||||
let base_url = match ppa.map(pkh::package_info::split_ppa) {
|
||||
@@ -692,17 +692,17 @@ fn main() {
|
||||
dist,
|
||||
base_url.as_deref(),
|
||||
repository,
|
||||
Some(&progress_callback),
|
||||
&view,
|
||||
)
|
||||
.await?;
|
||||
pkh::pull::pull(&package_info, None, Some(&progress_callback), *archive).await
|
||||
pkh::pull::pull(&package_info, None, &view, *archive).await
|
||||
}) {
|
||||
pb.finish_and_clear();
|
||||
// exit(1) below would skip the view's Drop
|
||||
drop(view);
|
||||
error!("{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
pb.finish_and_clear();
|
||||
multi.remove(&pb);
|
||||
drop(view);
|
||||
info!("Done.");
|
||||
}
|
||||
Some(("chlog", sub_matches)) => {
|
||||
|
||||
+32
-48
@@ -4,8 +4,8 @@ use std::error::Error;
|
||||
use std::io::Read;
|
||||
use xz2::read::XzDecoder;
|
||||
|
||||
use crate::ProgressCallback;
|
||||
use crate::apt::release::{self, VerifiedRelease};
|
||||
use crate::report::BuildView;
|
||||
use crossterm::style::Stylize;
|
||||
use log::{debug, warn};
|
||||
|
||||
@@ -490,7 +490,7 @@ async fn find_package(
|
||||
version: Option<&str>,
|
||||
base_url: Option<&str>,
|
||||
launchpad_vcs: Option<String>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<PackageInfo, Box<dyn Error>> {
|
||||
let series_list = crate::distro_info::get_ordered_series_name(dist).await?;
|
||||
|
||||
@@ -500,9 +500,7 @@ async fn find_package(
|
||||
let mut attempt_errors: Vec<String> = Vec::new();
|
||||
|
||||
for (i, series) in series_list.iter().enumerate() {
|
||||
if let Some(cb) = progress {
|
||||
cb("", &format!("Checking {}...", series), i, series_list.len());
|
||||
}
|
||||
view.progress(&format!("Checking {series}..."), i, series_list.len());
|
||||
|
||||
let pockets = if pocket.is_empty() {
|
||||
crate::distro_info::get_dist_pockets(dist)?
|
||||
@@ -770,16 +768,9 @@ pub async fn lookup_repository(
|
||||
version: Option<&str>,
|
||||
repo_url: &str,
|
||||
series: Option<&str>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<PackageInfo, Box<dyn Error>> {
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Fetching repository info from {}...", repo_url),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
view.message(&format!("Fetching repository info from {repo_url}..."));
|
||||
|
||||
// Attempt to authenticate the repository. In non-strict mode only the
|
||||
// absence of a Release file (or of a way to verify it) is tolerated:
|
||||
@@ -814,14 +805,7 @@ pub async fn lookup_repository(
|
||||
get_flat_repo_series(repo_url).await?
|
||||
};
|
||||
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Resolving package info for {}...", package),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
view.message(&format!("Resolving package info for {package}..."));
|
||||
|
||||
let sources = get_flat_repo_sources(repo_url, verified.as_ref()).await?;
|
||||
let stanza = parse_sources(&sources, package, version)?
|
||||
@@ -855,7 +839,7 @@ pub async fn lookup_repository(
|
||||
/// * `dist` - Optional distribution name (e.g., "ubuntu", "debian")
|
||||
/// * `base_url` - Optional base URL for the package archive (e.g., "https://ppa.launchpadcontent.net/user/ppa/ubuntu/")
|
||||
/// * `repository` - Optional URL of an external flat repository suite (e.g., "https://pkg.noctalia.dev/deb/resolute/")
|
||||
/// * `progress` - Optional progress callback
|
||||
/// * `view` - Build view receiving the lookup progress
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn lookup(
|
||||
package: &str,
|
||||
@@ -865,12 +849,12 @@ pub async fn lookup(
|
||||
dist: Option<&str>,
|
||||
base_url: Option<&str>,
|
||||
repository: Option<&str>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<PackageInfo, Box<dyn Error>> {
|
||||
// External flat repository: the URL is the suite root, distribution
|
||||
// resolution and pocket logic do not apply
|
||||
if let Some(repo_url) = repository {
|
||||
return lookup_repository(package, version, repo_url, series, progress).await;
|
||||
return lookup_repository(package, version, repo_url, series, view).await;
|
||||
}
|
||||
|
||||
// Resolve the distribution early so we can check Launchpad once
|
||||
@@ -887,9 +871,7 @@ pub async fn lookup(
|
||||
// Check Launchpad repository once for Ubuntu packages, rather than
|
||||
// once per series/pocket combination inside get().
|
||||
let launchpad_vcs = if resolved_dist == "ubuntu" && base_url.is_none() {
|
||||
if let Some(cb) = progress {
|
||||
cb("", &format!("Checking Launchpad for {}...", package), 0, 0);
|
||||
}
|
||||
view.message(&format!("Checking Launchpad for {package}..."));
|
||||
match check_launchpad_repo(package).await {
|
||||
Ok(Some(lp_url)) => {
|
||||
debug!("Found Launchpad URL: {}", lp_url);
|
||||
@@ -910,21 +892,12 @@ pub async fn lookup(
|
||||
|
||||
// Obtain the package information, either directly in a series or with a search in all series
|
||||
let package_info = if let Some(s) = series {
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Resolving package info for {}...", package),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
view.message(&format!("Resolving package info for {package}..."));
|
||||
|
||||
// Get the package information from that series and pocket
|
||||
get(package, s, pocket, version, base_url, launchpad_vcs).await?
|
||||
} else {
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!(
|
||||
view.message(&format!(
|
||||
"Searching for package {} in {}...",
|
||||
package,
|
||||
if base_url.is_none() {
|
||||
@@ -932,12 +905,7 @@ pub async fn lookup(
|
||||
} else {
|
||||
"ppa"
|
||||
}
|
||||
),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
// Try to find the package in all series from that dist
|
||||
find_package(
|
||||
@@ -947,7 +915,7 @@ pub async fn lookup(
|
||||
version,
|
||||
base_url,
|
||||
launchpad_vcs,
|
||||
progress,
|
||||
view,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
@@ -1142,7 +1110,15 @@ Directory: pool/main/h/hello
|
||||
#[tokio::test]
|
||||
async fn test_find_package_fallback() {
|
||||
// python2.7 is in bullseye but not above
|
||||
let info = find_package("python2.7", "debian", "", None, None, None, None)
|
||||
let info = find_package(
|
||||
"python2.7",
|
||||
"debian",
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&crate::report::Quiet,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(info.stanza.package, "python2.7");
|
||||
@@ -1152,7 +1128,15 @@ Directory: pool/main/h/hello
|
||||
#[tokio::test]
|
||||
async fn test_find_package_devel() {
|
||||
// hello is in sid
|
||||
let info = find_package("hello", "debian", "", None, None, None, None)
|
||||
let info = find_package(
|
||||
"hello",
|
||||
"debian",
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&crate::report::Quiet,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(info.stanza.package, "hello");
|
||||
|
||||
+76
-73
@@ -13,14 +13,14 @@ use log::debug;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::ProgressCallback;
|
||||
use crate::report::BuildView;
|
||||
|
||||
fn clone_repo(
|
||||
url: &str,
|
||||
package: &str,
|
||||
branch: Option<&str>,
|
||||
cwd: Option<&Path>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let target_path = if let Some(path) = cwd {
|
||||
path.join(package)
|
||||
@@ -29,11 +29,11 @@ fn clone_repo(
|
||||
};
|
||||
|
||||
let mut callbacks = git2::RemoteCallbacks::new();
|
||||
if let Some(ref progress_cb) = progress {
|
||||
// The sideband parser is only worth running when something listens
|
||||
if view.is_enabled() {
|
||||
// Download progress
|
||||
callbacks.transfer_progress(move |stats| {
|
||||
(progress_cb)(
|
||||
"",
|
||||
view.progress(
|
||||
"Receiving objects...",
|
||||
stats.received_objects(),
|
||||
stats.total_objects(),
|
||||
@@ -42,7 +42,7 @@ fn clone_repo(
|
||||
});
|
||||
|
||||
// Remote progress: messages 'Remote: compressing objects 10% (34/340)'
|
||||
// Parse progress informations to display them in callbacks
|
||||
// Parse progress informations to display them in the view
|
||||
callbacks.sideband_progress(move |data| {
|
||||
let msg = String::from_utf8_lossy(data);
|
||||
let re = Regex::new(r"(.*):[ ]*([0-9]*)% \(([0-9]*)/([0-9]*)\)").unwrap();
|
||||
@@ -61,7 +61,7 @@ fn clone_repo(
|
||||
.parse::<usize>()
|
||||
.unwrap_or(0);
|
||||
|
||||
(progress_cb)("", msg.as_str(), objects, total);
|
||||
view.progress(msg.as_str(), objects, total);
|
||||
}
|
||||
|
||||
true
|
||||
@@ -232,7 +232,7 @@ struct ExtractedArchive {
|
||||
fn extract_tar_archive<D, F>(
|
||||
file_path: &Path,
|
||||
dest: &Path,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
decoder_factory: F,
|
||||
) -> Result<ExtractedArchive, Box<dyn Error>>
|
||||
where
|
||||
@@ -373,9 +373,7 @@ where
|
||||
current_entry += 1;
|
||||
|
||||
// Report progress
|
||||
if let Some(cb) = progress {
|
||||
cb("", "Extracting...", current_entry, total_entries);
|
||||
}
|
||||
view.progress("Extracting...", current_entry, total_entries);
|
||||
}
|
||||
|
||||
let wrapper = if multiple_roots {
|
||||
@@ -389,16 +387,16 @@ where
|
||||
fn extract_archive(
|
||||
path: &Path,
|
||||
dest: &Path,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<ExtractedArchive, Box<dyn Error>> {
|
||||
let filename = path.file_name().unwrap().to_string_lossy();
|
||||
|
||||
if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") {
|
||||
extract_tar_archive(path, dest, progress, GzDecoder::new)
|
||||
extract_tar_archive(path, dest, view, GzDecoder::new)
|
||||
} else if filename.ends_with(".tar.xz") || filename.ends_with(".txz") {
|
||||
extract_tar_archive(path, dest, progress, XzDecoder::new)
|
||||
extract_tar_archive(path, dest, view, XzDecoder::new)
|
||||
} else if filename.ends_with(".tar.bz2") || filename.ends_with(".tbz2") {
|
||||
extract_tar_archive(path, dest, progress, bzip2_rs::DecoderReader::new)
|
||||
extract_tar_archive(path, dest, view, bzip2_rs::DecoderReader::new)
|
||||
} else {
|
||||
Err(format!("Unsupported archive format: {}", filename).into())
|
||||
}
|
||||
@@ -439,7 +437,7 @@ async fn download_file_checksum(
|
||||
checksum: &str,
|
||||
algo: crate::package_info::ChecksumAlgo,
|
||||
target_dir: &Path,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// Archive mirrors and CDNs are busy enough that single attempts fail
|
||||
// spuriously (dropped connections, truncated bodies, index generations
|
||||
@@ -448,7 +446,7 @@ async fn download_file_checksum(
|
||||
const ATTEMPTS: u32 = 3;
|
||||
let mut last_error: Box<dyn Error> = String::new().into();
|
||||
for attempt in 1..=ATTEMPTS {
|
||||
match download_file_checksum_once(url, checksum, algo, target_dir, progress).await {
|
||||
match download_file_checksum_once(url, checksum, algo, target_dir, view).await {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) => {
|
||||
log::warn!("download of '{url}' failed (attempt {attempt}/{ATTEMPTS}): {e}");
|
||||
@@ -468,7 +466,7 @@ async fn download_file_checksum_once(
|
||||
checksum: &str,
|
||||
algo: crate::package_info::ChecksumAlgo,
|
||||
target_dir: &Path,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// Download with the shared client (connect timeout). Large orig tarballs
|
||||
// can legitimately take longer than the client's default total timeout,
|
||||
@@ -504,9 +502,9 @@ async fn download_file_checksum_once(
|
||||
file.write_all(&chunk)?;
|
||||
buffer.extend_from_slice(&chunk);
|
||||
|
||||
if let (Some(cb), Some(total)) = (progress, total_size) {
|
||||
if let Some(total) = total_size {
|
||||
let index = min(buffer.len(), total as usize);
|
||||
cb("", "Downloading...", index, total as usize);
|
||||
view.progress("Downloading...", index, total as usize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,7 +573,7 @@ fn setup_pristine_tar_branch(package_dir: &Path, dist: &str) -> Result<(), Box<d
|
||||
async fn fetch_orig_tarball(
|
||||
info: &PackageInfo,
|
||||
cwd: Option<&Path>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let package_dir = if let Some(path) = cwd {
|
||||
path.join(&info.stanza.package)
|
||||
@@ -630,7 +628,7 @@ async fn fetch_orig_tarball(
|
||||
&orig_file.checksum,
|
||||
orig_file.checksum_algo,
|
||||
target_dir,
|
||||
progress,
|
||||
view,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -642,7 +640,7 @@ async fn fetch_orig_tarball(
|
||||
async fn fetch_dsc_file(
|
||||
info: &PackageInfo,
|
||||
cwd: Option<&Path>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let target_dir = cwd.unwrap_or_else(|| Path::new("."));
|
||||
|
||||
@@ -662,7 +660,7 @@ async fn fetch_dsc_file(
|
||||
&dsc_file.checksum,
|
||||
dsc_file.checksum_algo,
|
||||
target_dir,
|
||||
progress,
|
||||
view,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -672,7 +670,7 @@ async fn fetch_dsc_file(
|
||||
async fn fetch_archive_sources(
|
||||
info: &PackageInfo,
|
||||
cwd: Option<&Path>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let package_dir = if let Some(path) = cwd {
|
||||
path
|
||||
@@ -702,14 +700,7 @@ async fn fetch_archive_sources(
|
||||
|
||||
for file in &files {
|
||||
let url = format!("{}/{}", info.archive_url, file.name);
|
||||
download_file_checksum(
|
||||
&url,
|
||||
&file.checksum,
|
||||
file.checksum_algo,
|
||||
package_dir,
|
||||
progress,
|
||||
)
|
||||
.await?;
|
||||
download_file_checksum(&url, &file.checksum, file.checksum_algo, package_dir, view).await?;
|
||||
|
||||
// Extract all tar archives. Every tar of the source package matches
|
||||
// (orig, debian tar, native tar, extra components); a tarball in an
|
||||
@@ -725,7 +716,7 @@ async fn fetch_archive_sources(
|
||||
let path = package_dir.join(&file.name);
|
||||
let extract_dir = package_dir.join(&info.stanza.package);
|
||||
|
||||
let extracted = extract_archive(&path, &extract_dir, progress)?;
|
||||
let extracted = extract_archive(&path, &extract_dir, view)?;
|
||||
|
||||
// Special case: the debian tar does only contain 'debian'
|
||||
if file.name.contains("debian.tar.") {
|
||||
@@ -807,7 +798,7 @@ async fn fetch_archive_sources(
|
||||
pub async fn pull(
|
||||
package_info: &PackageInfo,
|
||||
cwd: Option<&Path>,
|
||||
progress: ProgressCallback<'_>,
|
||||
view: &dyn BuildView,
|
||||
force_archive: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let package = &package_info.stanza.package;
|
||||
@@ -861,9 +852,7 @@ pub async fn pull(
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!(
|
||||
view.message(&format!(
|
||||
"Cloning {}{}...",
|
||||
url,
|
||||
if let Some(b) = &branch_name {
|
||||
@@ -871,40 +860,29 @@ pub async fn pull(
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
clone_repo(
|
||||
url.as_str(),
|
||||
package,
|
||||
branch_name.as_deref(),
|
||||
Some(&package_dir),
|
||||
progress,
|
||||
view,
|
||||
)?;
|
||||
|
||||
if !package_info.is_native() {
|
||||
if let Some(cb) = progress {
|
||||
cb("Fetching orig tarball...", "", 0, 0);
|
||||
}
|
||||
fetch_orig_tarball(package_info, Some(&package_dir), progress).await?;
|
||||
view.message("Fetching orig tarball...");
|
||||
fetch_orig_tarball(package_info, Some(&package_dir), view).await?;
|
||||
} else {
|
||||
debug!("Native package, skipping orig tarball fetch.");
|
||||
}
|
||||
|
||||
if let Some(cb) = progress {
|
||||
cb("Fetching dsc file...", "", 0, 0);
|
||||
}
|
||||
fetch_dsc_file(package_info, Some(&package_dir), progress).await?;
|
||||
view.message("Fetching dsc file...");
|
||||
fetch_dsc_file(package_info, Some(&package_dir), view).await?;
|
||||
} else {
|
||||
// Fallback to archive fetching
|
||||
if let Some(cb) = progress {
|
||||
cb("Downloading from archive...", "", 0, 0);
|
||||
}
|
||||
fetch_archive_sources(package_info, Some(&package_dir), progress).await?;
|
||||
view.message("Downloading from archive...");
|
||||
fetch_archive_sources(package_info, Some(&package_dir), view).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -931,10 +909,24 @@ mod tests {
|
||||
let cwd = temp_dir.path();
|
||||
|
||||
// Main 'pull' command: the one we want to test
|
||||
let info = crate::package_info::lookup(package, None, series, "", dist, None, None, None)
|
||||
let info = crate::package_info::lookup(
|
||||
package,
|
||||
None,
|
||||
series,
|
||||
"",
|
||||
dist,
|
||||
None,
|
||||
None,
|
||||
&crate::report::Quiet,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
pull(&info, Some(cwd), None, archive.unwrap_or(false))
|
||||
pull(
|
||||
&info,
|
||||
Some(cwd),
|
||||
&crate::report::Quiet,
|
||||
archive.unwrap_or(false),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1136,7 +1128,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let dest = temp_dir.path().join("dest");
|
||||
let result = extract_tar_archive(&tar_path, &dest, None, |f| f);
|
||||
let result = extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f);
|
||||
|
||||
let err = match result {
|
||||
Ok(_) => panic!("extraction of a traversal archive should fail"),
|
||||
@@ -1169,7 +1161,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let dest = temp_dir.path().join("dest");
|
||||
let result = extract_tar_archive(&tar_path, &dest, None, |f| f);
|
||||
let result = extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f);
|
||||
|
||||
let err = match result {
|
||||
Ok(_) => panic!("extraction of an absolute-path archive should fail"),
|
||||
@@ -1202,7 +1194,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let dest = temp_dir.path().join("dest");
|
||||
extract_tar_archive(&tar_path, &dest, None, |f| f).unwrap();
|
||||
extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(dest.join("hello-1.0/file.txt")).unwrap(),
|
||||
@@ -1235,7 +1227,7 @@ mod tests {
|
||||
std::fs::write(&tar_path, tar_bytes).unwrap();
|
||||
|
||||
let dest = temp_dir.path().join("dest");
|
||||
extract_tar_archive(&tar_path, &dest, None, |f| f).unwrap();
|
||||
extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(dest.join("pkg-1.0/docs/v1/manual.txt")).unwrap(),
|
||||
@@ -1276,7 +1268,8 @@ mod tests {
|
||||
std::fs::write(&tar_path, tar_bytes).unwrap();
|
||||
|
||||
let tree = temp_dir.path().join("tree");
|
||||
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
|
||||
let extracted =
|
||||
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
|
||||
|
||||
assert!(!tree.join("pax_global_header").exists());
|
||||
@@ -1297,7 +1290,7 @@ mod tests {
|
||||
std::fs::write(&tar_path, tar_bytes).unwrap();
|
||||
|
||||
let dest = temp_dir.path().join("dest");
|
||||
let result = extract_tar_archive(&tar_path, &dest, None, |f| f);
|
||||
let result = extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f);
|
||||
|
||||
let err = match result {
|
||||
Ok(_) => panic!("extraction of an escaping hard link should fail"),
|
||||
@@ -1332,7 +1325,8 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let tree = temp_dir.path().join("tree");
|
||||
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
|
||||
let extracted =
|
||||
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -1366,7 +1360,8 @@ mod tests {
|
||||
std::fs::write(&tar_path, tar_bytes).unwrap();
|
||||
|
||||
let tree = temp_dir.path().join("tree");
|
||||
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
|
||||
let extracted =
|
||||
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
|
||||
|
||||
let meta = std::fs::symlink_metadata(tree.join("README")).unwrap();
|
||||
@@ -1392,7 +1387,8 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let tree = temp_dir.path().join("tree");
|
||||
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
|
||||
let extracted =
|
||||
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
assert!(extracted.wrapper.is_none());
|
||||
|
||||
assert!(tree.join("Makefile").exists());
|
||||
@@ -1413,7 +1409,7 @@ mod tests {
|
||||
|
||||
let main = temp_dir.path().join("main.orig.tar");
|
||||
std::fs::write(&main, build_tar(&[("dovecot-2.4.5/README", b"main")])).unwrap();
|
||||
let extracted = extract_tar_archive(&main, &tree, None, |f| f).unwrap();
|
||||
let extracted = extract_tar_archive(&main, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
unwrap_into(
|
||||
&tree,
|
||||
&extracted.wrapper.unwrap(),
|
||||
@@ -1430,7 +1426,8 @@ mod tests {
|
||||
]),
|
||||
)
|
||||
.unwrap();
|
||||
let extracted = extract_tar_archive(&component, &tree, None, |f| f).unwrap();
|
||||
let extracted =
|
||||
extract_tar_archive(&component, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
unwrap_into(
|
||||
&tree,
|
||||
&extracted.wrapper.unwrap(),
|
||||
@@ -1472,7 +1469,8 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let tree = temp_dir.path().join("tree");
|
||||
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
|
||||
let extracted =
|
||||
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
|
||||
unwrap_into(
|
||||
&tree,
|
||||
&extracted.wrapper.unwrap(),
|
||||
@@ -1512,8 +1510,13 @@ mod tests {
|
||||
std::fs::write(&tar_path, BZ2_TAR_WRAPPED).unwrap();
|
||||
|
||||
let tree = temp_dir.path().join("tree");
|
||||
let extracted =
|
||||
extract_tar_archive(&tar_path, &tree, None, bzip2_rs::DecoderReader::new).unwrap();
|
||||
let extracted = extract_tar_archive(
|
||||
&tar_path,
|
||||
&tree,
|
||||
&crate::report::Quiet,
|
||||
bzip2_rs::DecoderReader::new,
|
||||
)
|
||||
.unwrap();
|
||||
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
+6
-4
@@ -68,10 +68,12 @@ pub struct BuildTarget<'a> {
|
||||
/// Observer of a running build: target identification, phases, status
|
||||
/// messages, progress and the final outcome.
|
||||
///
|
||||
/// Implement this to observe the [`crate::build`] and [`crate::deb`] flows
|
||||
/// from any frontend. All events arrive in order from the build thread;
|
||||
/// long-lived views are expected to be `Send + Sync` because builds may run
|
||||
/// inside async tasks.
|
||||
/// Implement this to observe the [`crate::build`], [`crate::deb`] and
|
||||
/// [`crate::pull`] flows from any frontend. All events arrive in order from
|
||||
/// the build thread; long-lived views are expected to be `Send + Sync`
|
||||
/// because builds may run inside async tasks. The pull flow only emits the
|
||||
/// [`BuildView::message`], [`BuildView::progress`] and
|
||||
/// [`BuildView::progress`] events — no target, phases or outcome.
|
||||
pub trait BuildView: Send + Sync {
|
||||
/// The build target was identified; the events that follow belong to it
|
||||
/// (including the earliest subprocess output, e.g. a chroot download).
|
||||
|
||||
@@ -51,34 +51,56 @@ pub fn wait_spinner(msg: &str) -> ProgressBar {
|
||||
pb
|
||||
}
|
||||
|
||||
/// Create a spinner-style progress bar attached to `multi`, returning the bar
|
||||
/// and a callback compatible with [`crate::ProgressCallback`]
|
||||
pub fn create_progress_bar(
|
||||
multi: &MultiProgress,
|
||||
) -> (ProgressBar, impl Fn(&str, &str, usize, usize) + '_) {
|
||||
/// Terminal build view for the pull flow: one bar line attached to the
|
||||
/// shared [`MultiProgress`], spinner-style while the work is unsized,
|
||||
/// transfer-style once a byte or item total is known. The bar is created
|
||||
/// eagerly (an empty spinner, so the line exists from the first moment)
|
||||
/// and released on drop.
|
||||
pub struct PullView {
|
||||
multi: MultiProgress,
|
||||
pb: ProgressBar,
|
||||
}
|
||||
|
||||
impl PullView {
|
||||
/// Attach a pull view to `multi`
|
||||
pub fn new(multi: &MultiProgress) -> Self {
|
||||
let pb = multi.add(ProgressBar::new(0));
|
||||
pb.enable_steady_tick(Duration::from_millis(50));
|
||||
pb.set_style(spinner_style());
|
||||
Self {
|
||||
multi: multi.clone(),
|
||||
pb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pb_clone = pb.clone();
|
||||
let callback = move |prefix: &str, msg: &str, progress: usize, total: usize| {
|
||||
let pb = &pb_clone;
|
||||
if progress != 0 && total != 0 {
|
||||
pb.set_style(transfer_style());
|
||||
impl Drop for PullView {
|
||||
fn drop(&mut self) {
|
||||
self.pb.finish_and_clear();
|
||||
self.multi.remove(&self.pb);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::report::BuildView for PullView {
|
||||
fn message(&self, text: &str) {
|
||||
self.pb.set_style(spinner_style());
|
||||
self.pb.set_message(text.to_string());
|
||||
}
|
||||
|
||||
fn progress(&self, label: &str, pos: usize, total: usize) {
|
||||
if pos != 0 && total != 0 {
|
||||
self.pb.set_style(transfer_style());
|
||||
} else {
|
||||
pb.set_style(spinner_style());
|
||||
self.pb.set_style(spinner_style());
|
||||
}
|
||||
self.pb.set_message(label.to_string());
|
||||
self.pb.set_length(total as u64);
|
||||
self.pb.set_position(pos as u64);
|
||||
}
|
||||
|
||||
if !prefix.is_empty() {
|
||||
pb.set_prefix(prefix.to_string());
|
||||
fn is_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pb.set_message(msg.to_string());
|
||||
pb.set_length(total as u64);
|
||||
pb.set_position(progress as u64);
|
||||
};
|
||||
|
||||
(pb, callback)
|
||||
}
|
||||
|
||||
/// Label of the interactive series selector, rendered by [`prompt::select`]
|
||||
|
||||
Reference in New Issue
Block a user