use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use ambient_ci::{
project::{self, ProjectError},
runlog::RunLog,
};
use clap::Parser;
use serde::Serialize;
use walkdir::WalkDir;
use super::{AmbientError, Config, Leaf};
#[derive(Debug, Parser)]
pub struct State {}
impl Leaf for State {
fn run(&self, config: &Config, _runlog: &mut RunLog) -> Result<(), AmbientError> {
let mut projects = StateDir::new(config.state());
let statedir = config.state().to_path_buf();
for entry in WalkDir::new(&statedir).min_depth(1).max_depth(1) {
let entry =
entry.map_err(|err| StateError::ListDir(config.state().to_path_buf(), err))?;
if let Some(project) = entry.path().file_name() {
if let Some(project) = project.to_str() {
projects.insert(project)?;
}
}
}
let json = serde_json::to_string_pretty(&projects).map_err(StateError::ToJson)?;
println!("{json}");
Ok(())
}
}
#[derive(Default, Serialize)]
struct StateDir {
#[serde(skip)]
statedir: PathBuf,
projects: HashMap<String, ProjectState>,
project_count: usize,
}
impl StateDir {
fn new(statedir: &Path) -> Self {
Self {
statedir: statedir.into(),
..Default::default()
}
}
fn insert(&mut self, project: &str) -> Result<(), StateError> {
let project_state = ProjectState::new(&self.statedir, project)?;
self.projects.insert(project.into(), project_state);
self.project_count = self.projects.len();
Ok(())
}
}
#[derive(Debug, Serialize)]
struct ProjectState {
latest_commit: Option<String>,
run_log: Option<u64>,
dependencies: Option<u64>,
cache: Option<u64>,
artifacts: Option<u64>,
}
impl ProjectState {
fn new(statedir: &Path, project: &str) -> Result<Self, StateError> {
let state = project::State::from_file(statedir, project)
.map_err(|err| StateError::LoadState(project.to_string(), err))?;
Ok(Self {
latest_commit: state.latest_commit.clone(),
run_log: Some(Self::file_length(statedir, &state.run_log_filename())?),
dependencies: Some(Self::dir_size(&state.dependenciesdir())?),
cache: Some(Self::dir_size(&state.cachedir())?),
artifacts: Some(Self::dir_size(&state.artifactsdir())?),
})
}
fn file_length(statedir: &Path, filename: &Path) -> Result<u64, StateError> {
let pathname = statedir.join(filename);
let meta = pathname
.metadata()
.map_err(|err| StateError::Metadata(pathname, err))?;
Ok(meta.len())
}
fn dir_size(path: &Path) -> Result<u64, StateError> {
let mut bytes = 0;
for entry in WalkDir::new(path) {
let entry = entry.map_err(|err| StateError::ListDir(path.to_path_buf(), err))?;
let meta = entry
.metadata()
.map_err(|err| StateError::MetadataWalkDir(entry.path().to_path_buf(), err))?;
bytes += meta.len();
}
Ok(bytes)
}
}
#[derive(Debug, thiserror::Error)]
pub enum StateError {
#[error("failed list contents of state directory {0}")]
ListDir(PathBuf, #[source] walkdir::Error),
#[error("failed to load project state: {0}")]
LoadState(String, #[source] ProjectError),
#[error("failed to serialize project states as JSON")]
ToJson(#[source] serde_json::Error),
#[error("failed to get meta data for {0}")]
Metadata(PathBuf, #[source] std::io::Error),
#[error("failed to get meta data for {0}")]
MetadataWalkDir(PathBuf, #[source] walkdir::Error),
}