use std::{collections::BTreeMap, fs, path::PathBuf, str};
use anyhow::{Context as _, Result};
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
context::Context,
util::{load_toml, not_found_err},
};
use super::Source;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub struct LockedConfig {
#[serde(flatten)]
pub ctx: Context,
#[serde(default)]
pub pkgs: BTreeMap<String, LockedPackage>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(tag = "source")]
#[serde(rename_all = "lowercase")]
pub struct LockedPackage {
#[serde(skip)]
pub name: String,
#[serde(default)]
pub bins: Vec<String>,
pub version: String,
#[serde(flatten)]
pub source: Source,
pub desc: Option<String>,
pub filename: String,
pub download_url: Url,
}
impl LockedConfig {
pub fn new(ctx: Context, pkgs: BTreeMap<String, LockedPackage>) -> Self {
Self { ctx, pkgs }
}
pub fn load(ctx: &Context) -> Result<Self> {
let mut lcfg = match load_toml(&ctx.lock_file) {
Err(e) if not_found_err(e.root_cause()) =>
LockedConfig::new(ctx.clone(), Default::default()),
lcfg => lcfg.with_context(|| format!("failed to load {}", ctx.lock_file.display()))?,
};
lcfg.ctx = ctx.clone();
for (name, lpkg) in lcfg.pkgs.iter_mut() {
lpkg.name = name.clone();
if lpkg.bins.is_empty() {
lpkg.bins.push(name.clone());
}
}
Ok(lcfg)
}
pub fn save(&self) -> Result<()> {
let buf = toml::to_string_pretty(self).context("failed to serialize `LockedConfig`")?;
fs::write(&self.ctx.lock_file, buf)
.with_context(|| format!("failed to save {}", self.ctx.lock_file.display()))
}
pub fn upsert(&mut self, lpkg: LockedPackage) {
assert!(
!lpkg.name.is_empty(),
"locked package must have at least one binary"
);
self.pkgs.insert(lpkg.name.clone(), lpkg);
}
}
impl LockedPackage {
pub fn asset_path(&self, ctx: &Context) -> PathBuf {
ctx.cache_dir
.join(&self.name)
.join(&self.version)
.join(&self.filename)
}
pub fn install_dir(&self, ctx: &Context) -> PathBuf {
ctx.data_dir.join(&self.name).join(&self.version)
}
}