diff options
Diffstat (limited to 'src/commands/source/mod.rs')
-rw-r--r-- | src/commands/source/mod.rs | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/commands/source/mod.rs b/src/commands/source/mod.rs new file mode 100644 index 0000000..8d11099 --- /dev/null +++ b/src/commands/source/mod.rs @@ -0,0 +1,264 @@ +// +// Copyright (c) 2020-2022 science+computing ag and other contributors +// +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// + +//! Implementation of the 'source' subcommand + +use std::convert::TryFrom; +use std::io::Write; +use std::path::PathBuf; + +use anyhow::Context; +use anyhow::Error; +use anyhow::Result; +use anyhow::anyhow; +use clap::ArgMatches; +use colored::Colorize; +use log::{info, trace}; +use tokio_stream::StreamExt; + +use crate::config::*; +use crate::package::Package; +use crate::package::PackageName; +use crate::package::PackageVersionConstraint; +use crate::repository::Repository; +use crate::source::*; +use crate::util::progress::ProgressBars; + +mod download; + +/// Implementation of the "source" subcommand +pub async fn source( + matches: &ArgMatches, + config: &Configuration, + repo: Repository, + progressbars: ProgressBars, +) -> Result<()> { + match matches.subcommand() { + Some(("verify", matches)) => verify(matches, config, repo, progressbars).await, + Some(("list-missing", matches)) => list_missing(matches, config, repo).await, + Some(("url", matches)) => url(matches, repo).await, + Some(("download", matches)) => crate::commands::source::download::download(matches, config, repo, progressbars).await, + Some(("of", matches)) => of(matches, config, repo).await, + Some((other, _)) => return Err(anyhow!("Unknown subcommand: {}", other)), + None => Err(anyhow!("No subcommand")), + } +} + +pub async fn verify( + matches: &ArgMatches, + config: &Configuration, + repo: Repository, + progressbars: ProgressBars, +) -> Result<()> { + let sc = SourceCache::new(config.source_cache_root().clone()); + let pname = matches + .value_of("package_name") + .map(String::from) + .map(PackageName::from); + let pvers = matches + .value_of("package_version") + .map(PackageVersionConstraint::try_from) + .transpose()?; + + let matching_regexp = matches.value_of("matching") + .map(crate::commands::util::mk_package_name_regex) + .transpose()?; + + let packages = repo + .packages() + .filter(|p| { + match (pname.as_ref(), pvers.as_ref(), matching_regexp.as_ref()) { + (None, None, None) => true, + (Some(pname), None, None) => p.name() == pname, + (Some(pname), Some(vers), None) => p.name() == pname && vers.matches(p.version()), + (None, None, Some(regex)) => regex.is_match(p.name()), + + (_, _, _) => { + panic!("This should not be possible, either we select packages by name and (optionally) version, or by regex.") + }, + } + }) + .inspect(|p| trace!("Found for verification: {} {}", p.name(), p.version())); + + verify_impl(packages, &sc, &progressbars).await +} + +pub(in crate::commands) async fn verify_impl<'a, I>( + packages: I, + sc: &SourceCache, + progressbars: &ProgressBars, +) -> Result<()> +where + I: Iterator<Item = &'a Package> + 'a, +{ + let sources = packages + .map(|p| sc.sources_for(p).into_iter()) + .flatten() + .collect::<Vec<_>>(); + + let bar = progressbars.bar(); + bar.set_message("Verifying sources"); + bar.set_length(sources.len() as u64); + + let results = sources.into_iter() + .map(|src| (bar.clone(), src)) + .map(|(bar, source)| async move { + trace!("Verifying: {}", source.path().display()); + if source.path().exists() { + trace!("Exists: {}", source.path().display()); + source.verify_hash().await.with_context(|| { + anyhow!("Hash verification failed for: {}", source.path().display()) + })?; + + trace!("Success verifying: {}", source.path().display()); + bar.inc(1); + Ok(()) + } else { + trace!("Failed verifying: {}", source.path().display()); + bar.inc(1); + Err(anyhow!("Source missing: {}", source.path().display())) + } + }) + .collect::<futures::stream::FuturesUnordered<_>>() + .collect::<Vec<Result<_>>>() + .await; + + info!("Verification processes finished"); + + if results.iter().any(Result::is_err) { + bar.finish_with_message("Source verification failed"); + } else { + bar.finish_with_message("Source verification successfull"); + } + + let out = std::io::stdout(); + let mut any_error = false; + for result in results { + if let Err(e) = result { + let mut outlock = out.lock(); + any_error = true; + for cause in e.chain() { + let _ = writeln!(outlock, "Error: {}", cause.to_string().red()); + } + let _ = writeln!(outlock); + } + } + + if any_error { + Err(anyhow!( + "At least one package failed with source verification" + )) + } else { + Ok(()) + } +} + +pub async fn list_missing(_: &ArgMatches, config: &Configuration, repo: Repository) -> Result<()> { + let sc = SourceCache::new(config.source_cache_root().clone()); + let out = std::io::stdout(); + let mut outlock = out.lock(); + + repo.packages().try_for_each(|p| { + for source in sc.sources_for(p) { + if !source.path().exists() { + writeln!( + outlock, + "{} {} -> {}", + p.name(), + p.version(), + source.path().display() + )?; + } + } + + Ok(()) + }) +} + +pub async fn url(matches: &ArgMatches, repo: Repository) -> Result<()> { + let out = std::io::stdout(); + let mut outlock = out.lock(); + + let pname = matches + .value_of("package_name") + .map(String::from) + .map(PackageName::from); + let pvers = matches + .value_of("package_version") + .map(PackageVersionConstraint::try_from) + .transpose()?; + + repo.packages() + .filter(|p| pname.as_ref().map(|n| p.name() == n).unwrap_or(true)) + .filter(|p| { + pvers + .as_ref() + .map(|v| v.matches(p.version())) + .unwrap_or(true) + }) + .try_for_each(|p| { + p.sources().iter().try_for_each(|(source_name, source)| { + writeln!( + outlock, + "{} {} -> {} = {}", + p.name(), + p.version(), + source_name, + source.url() + ) + .map_err(Error::from) + }) + }) +} + +async fn of( + matches: &ArgMatches, + config: &Configuration, + repo: Repository, +) -> Result<()> { + let cache = PathBuf::from(config.source_cache_root()); + let sc = SourceCache::new(cache); + let pname = matches + .value_of("package_name") + .map(String::from) + .map(PackageName::from); + let pvers = matches + .value_of("package_version") + .map(PackageVersionConstraint::try_from) + .transpose()?; + + repo.packages() + .filter(|p| pname.as_ref().map(|n| p.name() == n).unwrap_or(true)) + .filter(|p| { + pvers + .as_ref() + .map(|v| v.matches(p.version())) + .unwrap_or(true) + }) + .map(|p| { + let pathes = sc.sources_for(p) + .into_iter() + .map(|source| source.path()) + .collect::<Vec<PathBuf>>(); + + (p, pathes) + }) + .fold(Ok(std::io::stdout()), |out, (package, pathes)| { + out.and_then(|mut out| { + writeln!(out, "{} {}", package.name(), package.version())?; + for path in pathes { + writeln!(out, "\t{}", path.display())?; + } + + Ok(out) + }) + }) + .map(|_| ()) +} |