use std::{fmt::Display, fs, path::PathBuf};
use soar_core::{
database::{connection::DieselDatabase, models::Package},
error::ErrorContext,
package::query::PackageQuery,
SoarResult,
};
use soar_db::repository::{
core::{CoreRepository, SortDirection},
metadata::MetadataRepository,
};
use soar_dl::http_client::SHARED_AGENT;
use soar_operations::search;
use soar_utils::bytes::format_bytes;
use tracing::{error, info};
use ureq::http::header::CONTENT_LENGTH;
use crate::{
progress::create_spinner_job,
utils::{display_settings, interactive_ask, select_package_interactively},
};
pub enum InspectType {
BuildLog,
BuildScript,
}
impl Display for InspectType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InspectType::BuildLog => write!(f, "log"),
InspectType::BuildScript => write!(f, "script"),
}
}
}
fn get_installed_path(
diesel_db: &DieselDatabase,
package: &Package,
) -> SoarResult<Option<PathBuf>> {
let installed_pkg = diesel_db.with_conn(|conn| {
CoreRepository::find_exact(
conn,
&package.repo_name,
&package.pkg_name,
&package.pkg_id,
&package.version,
)
})?;
if let Some(pkg) = installed_pkg {
if pkg.is_installed {
return Ok(Some(PathBuf::from(pkg.installed_path)));
}
}
Ok(None)
}
pub async fn inspect_log(package: &str, inspect_type: InspectType) -> SoarResult<()> {
let (ctx, _) = crate::create_context();
let metadata_mgr = ctx.metadata_manager().await?;
let diesel_db = ctx.diesel_core_db()?;
let query = PackageQuery::try_from(package)?;
let packages: Vec<Package> = if let Some(ref repo_name) = query.repo_name {
metadata_mgr
.query_repo(repo_name, |conn| {
MetadataRepository::find_filtered(
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
None,
None,
Some(SortDirection::Asc),
)
})?
.unwrap_or_default()
.into_iter()
.map(|p| {
let mut pkg: Package = p.into();
pkg.repo_name = repo_name.clone();
pkg
})
.collect()
} else {
metadata_mgr.query_all_flat(|repo_name, conn| {
let pkgs = MetadataRepository::find_filtered(
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
None,
None,
Some(SortDirection::Asc),
)?;
Ok(pkgs
.into_iter()
.map(|p| {
let mut pkg: Package = p.into();
pkg.repo_name = repo_name.to_string();
pkg
})
.collect())
})?
};
let packages: Vec<Package> = if let Some(ref version) = query.version {
packages
.into_iter()
.filter(|p| p.has_version(version))
.collect()
} else {
packages
};
if packages.is_empty() {
error!("Package {} not found", package);
if let Ok(suggestions) = search::suggest_similar(&ctx, package, 3).await {
if !suggestions.is_empty() {
info!("Did you mean: {}?", suggestions.join(", "));
}
}
} else {
let selected_pkg = if packages.len() > 1 {
&select_package_interactively(packages, &query.name.unwrap_or(package.to_string()))?
.unwrap()
} else {
packages.first().unwrap()
};
if let Some(installed_path) = get_installed_path(diesel_db, selected_pkg)? {
let file = if matches!(inspect_type, InspectType::BuildLog) {
installed_path.join(format!("{}.log", selected_pkg.pkg_name))
} else {
installed_path.join("SBUILD")
};
if file.exists() && file.is_file() {
info!(
"Reading build {inspect_type} from {} [{}]",
file.display(),
format_bytes(
file.metadata()
.with_context(|| format!("reading file metadata {}", file.display()))?
.len(),
2
)
);
let output = fs::read_to_string(&file)
.with_context(|| format!("reading file content from {}", file.display()))?
.replace("\r", "\n");
info!("\n{}", output);
return Ok(());
}
};
let url = if matches!(inspect_type, InspectType::BuildLog) {
&selected_pkg.build_log
} else {
&selected_pkg.build_script
};
let Some(url) = url else {
error!(
"No build {} found for {}",
inspect_type, selected_pkg.pkg_name
);
return Ok(());
};
let url = if url.starts_with("https://github.com") {
&url.replacen("/tree/", "/raw/refs/heads/", 1)
.replacen("/blob/", "/raw/refs/heads/", 1)
} else {
url
};
let settings = display_settings();
let spinner = if settings.spinners() {
Some(create_spinner_job(&format!(
"Fetching build {inspect_type}..."
)))
} else {
None
};
let resp = SHARED_AGENT.get(url).call()?;
if let Some(ref s) = spinner {
s.finish_and_clear();
}
if !resp.status().is_success() {
error!(
"Error fetching build {inspect_type} from {} [{}]",
url,
resp.status()
);
return Ok(());
}
let content_length = resp
.headers()
.get(CONTENT_LENGTH)
.and_then(|h| h.to_str().ok())
.and_then(|len| len.parse::<u64>().ok())
.unwrap_or(0);
if content_length > 1_048_576 {
let response = interactive_ask(
"The {inspect_type} file is too large. Do you really want to view it (y/N)?",
)?;
if !response.starts_with('y') {
return Ok(());
}
}
info!(
"Fetching build {inspect_type} from {} [{}]",
url,
format_bytes(content_length, 2)
);
let content = resp.into_body().read_to_vec()?;
let output = String::from_utf8_lossy(&content).replace("\r", "\n");
info!("\n{}", output);
}
Ok(())
}