use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::Path;
const LOCKFILE_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lockfile {
pub version: u32,
pub created_at: String,
pub platform: String,
pub packages: BTreeMap<String, LockedPackage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockedPackage {
pub version: String,
pub revision: u32,
pub bottle_sha256: Option<String>,
pub bottle_url: Option<String>,
pub source_sha256: Option<String>,
pub source_url: Option<String>,
#[serde(default)]
pub built_from_source: bool,
#[serde(default)]
pub dependencies: Vec<String>,
}
impl Lockfile {
pub fn new() -> Self {
let platform = format!(
"{}-{}",
std::env::consts::OS,
std::env::consts::ARCH
);
let created_at = chrono_lite_timestamp();
Self {
version: LOCKFILE_VERSION,
created_at,
platform,
packages: BTreeMap::new(),
}
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let contents = std::fs::read_to_string(path)?;
let lockfile: Lockfile = toml::from_str(&contents)?;
Ok(lockfile)
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
let contents = toml::to_string_pretty(self)?;
std::fs::write(path, contents)?;
Ok(())
}
pub fn add_package(&mut self, name: &str, package: LockedPackage) {
self.packages.insert(name.to_string(), package);
}
pub fn remove_package(&mut self, name: &str) {
self.packages.remove(name);
}
pub fn get_package(&self, name: &str) -> Option<&LockedPackage> {
self.packages.get(name)
}
pub fn is_locked(&self, name: &str) -> bool {
self.packages.contains_key(name)
}
pub fn matches_platform(&self) -> bool {
let current = format!(
"{}-{}",
std::env::consts::OS,
std::env::consts::ARCH
);
self.platform == current
}
pub fn package_names(&self) -> impl Iterator<Item = &str> {
self.packages.keys().map(|s| s.as_str())
}
}
impl Default for Lockfile {
fn default() -> Self {
Self::new()
}
}
impl LockedPackage {
pub fn from_bottle(
version: &str,
revision: u32,
bottle_url: &str,
bottle_sha256: &str,
dependencies: Vec<String>,
) -> Self {
Self {
version: version.to_string(),
revision,
bottle_sha256: Some(bottle_sha256.to_string()),
bottle_url: Some(bottle_url.to_string()),
source_sha256: None,
source_url: None,
built_from_source: false,
dependencies,
}
}
pub fn from_source(
version: &str,
revision: u32,
source_url: &str,
source_sha256: &str,
dependencies: Vec<String>,
) -> Self {
Self {
version: version.to_string(),
revision,
bottle_sha256: None,
bottle_url: None,
source_sha256: Some(source_sha256.to_string()),
source_url: Some(source_url.to_string()),
built_from_source: true,
dependencies,
}
}
}
fn chrono_lite_timestamp() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
format!("{}", duration.as_secs())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lockfile_creation() {
let lockfile = Lockfile::new();
assert_eq!(lockfile.version, LOCKFILE_VERSION);
assert!(lockfile.packages.is_empty());
}
#[test]
fn test_add_package() {
let mut lockfile = Lockfile::new();
let pkg = LockedPackage::from_bottle(
"1.0.0",
0,
"https://example.com/pkg.tar.gz",
"abc123",
vec!["dep1".to_string()],
);
lockfile.add_package("test-pkg", pkg);
assert!(lockfile.is_locked("test-pkg"));
}
}