pub use super::lock_error::ProtoLockError;
use crate::lockfile::LockRecord;
use crate::tool::Tool;
use crate::tool_spec::ToolSpec;
use tracing::{debug, instrument};
use version_spec::VersionSpec;
pub struct Locker<'tool> {
tool: &'tool Tool,
}
impl<'tool> Locker<'tool> {
pub fn new(tool: &'tool Tool) -> Self {
Self { tool }
}
pub fn get_resolved_locked_record<'a>(&'a self, spec: &'a ToolSpec) -> Option<&'a LockRecord> {
if let Some(record) = &spec.version_locked {
return Some(record);
}
if let Some(version) = &spec.version {
return self.tool.inventory.get_locked_record(version);
}
None
}
#[instrument(skip(self))]
pub fn insert_record_into_lockfile(&self, record: &LockRecord) -> Result<(), ProtoLockError> {
if self.tool.metadata.lock_options.no_record {
return Ok(());
}
let proto = &self.tool.proto;
let Some(mut lock) = proto.load_lock_mut()? else {
return Ok(());
};
let record = record.for_lockfile();
let records = lock.tools.entry(self.tool.get_id().to_owned()).or_default();
match records
.iter_mut()
.find(|existing| existing.is_match(&record, &self.tool.metadata.lock_options))
{
Some(existing) => {
if existing
.version
.as_ref()
.is_none_or(|exv| record.version.as_ref().unwrap() >= exv)
{
*existing = record;
}
if self.tool.metadata.lock_options.ignore_os_arch {
existing.os = None;
existing.arch = None;
} else {
existing.os.get_or_insert(proto.os);
existing.arch.get_or_insert(proto.arch);
}
}
None => {
records.push(record);
}
};
lock.sort_records();
lock.save()?;
Ok(())
}
pub fn remove_from_lockfile(&self) -> Result<(), ProtoLockError> {
let Some(mut lock) = self.tool.proto.load_lock_mut()? else {
return Ok(());
};
lock.tools.remove(self.tool.get_id());
lock.save()?;
Ok(())
}
pub fn remove_version_from_lockfile(
&self,
version: &VersionSpec,
) -> Result<(), ProtoLockError> {
let proto = &self.tool.proto;
let Some(mut lock) = proto.load_lock_mut()? else {
return Ok(());
};
if let Some(records) = lock.tools.get_mut(self.tool.get_id()) {
let spec = version.to_unresolved_spec();
records.retain(|record| {
let matched = record.is_match_with(
self.tool.context.backend.as_ref(),
Some(&spec),
Some(&proto.os),
Some(&proto.arch),
&self.tool.metadata.lock_options,
);
!(matched && record.version.as_ref().is_some_and(|ver| ver == version))
});
}
if lock
.tools
.get(self.tool.get_id())
.is_none_or(|records| records.is_empty())
{
lock.tools.remove(self.tool.get_id());
}
lock.sort_records();
lock.save()?;
Ok(())
}
#[instrument(skip(self))]
pub fn resolve_locked_record(
&self,
spec: &ToolSpec,
) -> Result<Option<LockRecord>, ProtoLockError> {
let proto = &self.tool.proto;
let Some(lock) = proto.load_lock()? else {
return Ok(None);
};
if let Some(records) = lock.tools.get(self.tool.get_id()) {
for record in records {
let matched = record.is_match_with(
self.tool.context.backend.as_ref(),
Some(&spec.req),
Some(&proto.os),
Some(&proto.arch),
&self.tool.metadata.lock_options,
);
if matched && record.version.is_some() {
return Ok(Some(record.clone()));
}
}
}
Ok(None)
}
#[instrument(skip(self))]
pub fn verify_locked_record(
&self,
spec: &ToolSpec,
install_record: &LockRecord,
) -> Result<(), ProtoLockError> {
let Some(locked_record) = self.get_resolved_locked_record(spec) else {
return Ok(());
};
if install_record.backend != locked_record.backend {
return Ok(());
}
let make_error = |actual: String, expected: String| match &install_record.source {
Some(source) => ProtoLockError::MismatchedChecksumWithSource {
checksum: actual,
lockfile_checksum: expected,
source_url: source.to_owned(),
},
None => ProtoLockError::MismatchedChecksum {
checksum: actual,
lockfile_checksum: expected,
},
};
match (&install_record.checksum, &locked_record.checksum) {
(Some(ir), Some(lr)) => {
debug!(
tool = self.tool.context.as_str(),
checksum = ir.to_string(),
locked_checksum = lr.to_string(),
"Verifying checksum against lockfile",
);
if ir != lr {
return Err(make_error(ir.to_string(), lr.to_string()));
}
}
(None, Some(lr)) => {
if install_record.source == locked_record.source {
return Err(make_error("(missing)".into(), lr.to_string()));
}
}
_ => {}
};
if let Some(l_os) = install_record.os
&& let Some(r_os) = locked_record.os
&& l_os != r_os
{
return Err(ProtoLockError::MismatchedOs {
os: l_os.to_string(),
lockfile_os: r_os.to_string(),
});
}
if let Some(l_arch) = install_record.arch
&& let Some(r_arch) = locked_record.arch
&& l_arch != r_arch
{
return Err(ProtoLockError::MismatchedArch {
arch: l_arch.to_string(),
lockfile_arch: r_arch.to_string(),
});
}
Ok(())
}
}