use crate::prelude::*;
#[injectable]
pub(crate) struct VerifyCommand {
verify_options: Ref<VerifyOptions>,
source_provider: Ref<SourceProvider>,
api_verifier: Ref<ApiVerifier>,
paths: Ref<PathManager>,
torrents: Ref<TorrentFileProvider>,
reporter: Ref<SourceReporter>,
}
impl VerifyCommand {
pub(crate) async fn execute_cli(&self) -> Result<bool, Failure<VerifyAction>> {
let source = match self.source_provider.get_from_options().await {
Ok(Ok(source)) => source,
Ok(Err(issue)) => {
let status = VerifyStatus::from_issue(issue);
warn!("{} for transcoding unknown", "Unsuitable".bold());
if let Some(issues) = &status.issues {
for issue in issues {
warn!("{issue}");
}
}
return Ok(false);
}
Err(e) => return Err(Failure::new(VerifyAction::GetSource, e)),
};
let result = self.execute(&source).await?;
let id = source.to_string();
if result.verified() {
info!("{} {id}", "Verified".bold());
} else {
let issues = SourceIssuesRenderer::render(&result.issues, &source.directory);
warn!("{} for transcoding {id}\n{issues}", "Unsuitable".bold());
}
Ok(result.verified())
}
pub(crate) async fn execute(
&self,
source: &Source,
) -> Result<VerifySuccess, Failure<VerifyAction>> {
debug!("{} {}", "Verifying".bold(), source);
let mut issues: Vec<SourceIssue> = Vec::new();
issues.append(&mut self.api_verifier.execute(source));
issues.append(&mut self.flac_checks(source)?);
if let Some(issue) = self.hash_check(source).await? {
issues.push(issue);
}
if let Err(failure) = self.reporter.execute(source, &issues) {
warn!("{}", failure.render());
}
Ok(VerifySuccess { issues })
}
fn flac_checks(&self, source: &Source) -> Result<Vec<SourceIssue>, Failure<VerifyAction>> {
if let Some(issue) = check_directory_exists(source) {
return Ok(vec![issue]);
}
trace!("Collecting FLACs from {}", source.directory.display());
let flacs = Collector::get_flacs_with_context(&source.directory);
if flacs.is_empty() {
return Ok(vec![SourceIssue::NoFlacs {
path: source.directory.clone(),
}]);
}
let mut issues: Vec<SourceIssue> = Vec::new();
issues.extend(check_flac_count(source, flacs.len()));
issues.append(&mut VerifyCommand::subdirectory_checks(&flacs));
let max_target = get_max_path_length_target(source);
let output_dir = self.paths.get_output_dir();
for flac in flacs {
trace!("Verifying FLAC {}", flac.path.display());
if let Some(max_target) = max_target {
let path = self
.paths
.get_transcode_path(source, max_target, &flac)
.strip_prefix(output_dir.clone())
.expect("should be able to strip prefix from transcode path")
.to_path_buf();
issues.extend(check_path_length(&path));
}
let tag_issues = TagVerifier::execute(&flac, source)
.map_err(Failure::wrap(VerifyAction::VerifyTags))?;
issues.extend(tag_issues);
for error in StreamVerifier::execute(&flac) {
issues.push(error);
}
}
Ok(issues)
}
pub(crate) async fn hash_check(
&self,
source: &Source,
) -> Result<Option<SourceIssue>, Failure<VerifyAction>> {
if self.verify_options.no_hash_check {
debug!("{} hash check due to settings", "Skipped".bold());
return Ok(None);
}
let torrent_path = self.get_source_torrent(source).await?;
trace!(
"Verifying torrent hash against {}",
source.directory.display()
);
TorrentVerifier::execute(&torrent_path, &source.directory)
.await
.map_err(Failure::wrap(VerifyAction::VerifyHash))
}
pub(crate) async fn get_source_torrent(
&self,
source: &Source,
) -> Result<PathBuf, Failure<VerifyAction>> {
trace!("Fetching torrent file for {}", source.torrent.id);
self.torrents
.get(source.torrent.id)
.await
.map_err(Failure::wrap(VerifyAction::GetSourceTorrent))
}
pub fn subdirectory_checks(flacs: &[FlacFile]) -> Vec<SourceIssue> {
let flac_sub_dirs: Vec<_> = flacs.iter().map(|x| &x.sub_dir).collect();
if let Some(prefix) = Shortener::longest_common_prefix(&flac_sub_dirs) {
return vec![SourceIssue::UnnecessaryDirectory { prefix }];
}
vec![]
}
}
pub(crate) fn check_directory_exists(source: &Source) -> Option<SourceIssue> {
if !source.directory.is_dir() {
return Some(SourceIssue::MissingDirectory {
path: source.directory.clone(),
});
}
None
}
pub(crate) fn check_flac_count(source: &Source, actual: usize) -> Option<SourceIssue> {
let expected = source.torrent.get_flacs().len();
if actual != expected {
return Some(SourceIssue::FlacCount { expected, actual });
}
None
}
fn get_max_path_length_target(source: &Source) -> Option<TargetFormat> {
source.targets.first().copied()
}
#[allow(
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::as_conversions
)]
pub(crate) fn check_path_length(path: &Path) -> Option<SourceIssue> {
let length = path.to_string_lossy().chars().count() as isize;
let excess = length - MAX_PATH_LENGTH;
if excess > 0 {
return Some(SourceIssue::Length {
path: path.to_path_buf(),
excess: excess as usize,
});
}
None
}