state.rs
1 use std::{ 2 collections::HashMap, 3 path::{Path, PathBuf}, 4 }; 5 6 use ambient_ci::{ 7 project::{self, ProjectError}, 8 runlog::RunLog, 9 }; 10 use clap::Parser; 11 use serde::Serialize; 12 use walkdir::WalkDir; 13 14 use super::{AmbientError, Config, Leaf}; 15 16 /// Show per-project statistics for the state directory. 17 #[derive(Debug, Parser)] 18 pub struct State {} 19 20 impl Leaf for State { 21 fn run(&self, config: &Config, _runlog: &mut RunLog) -> Result<(), AmbientError> { 22 let mut projects = StateDir::new(config.state()); 23 let statedir = config.state().to_path_buf(); 24 for entry in WalkDir::new(&statedir).min_depth(1).max_depth(1) { 25 let entry = 26 entry.map_err(|err| StateError::ListDir(config.state().to_path_buf(), err))?; 27 if let Some(project) = entry.path().file_name() { 28 if let Some(project) = project.to_str() { 29 projects.insert(project)?; 30 } 31 } 32 } 33 34 let json = serde_json::to_string_pretty(&projects).map_err(StateError::ToJson)?; 35 println!("{json}"); 36 Ok(()) 37 } 38 } 39 40 #[derive(Default, Serialize)] 41 struct StateDir { 42 #[serde(skip)] 43 statedir: PathBuf, 44 projects: HashMap<String, ProjectState>, 45 project_count: usize, 46 } 47 48 impl StateDir { 49 fn new(statedir: &Path) -> Self { 50 Self { 51 statedir: statedir.into(), 52 ..Default::default() 53 } 54 } 55 56 fn insert(&mut self, project: &str) -> Result<(), StateError> { 57 let project_state = ProjectState::new(&self.statedir, project)?; 58 self.projects.insert(project.into(), project_state); 59 self.project_count = self.projects.len(); 60 Ok(()) 61 } 62 } 63 64 #[derive(Debug, Serialize)] 65 struct ProjectState { 66 latest_commit: Option<String>, 67 run_log: Option<u64>, 68 dependencies: Option<u64>, 69 cache: Option<u64>, 70 artifacts: Option<u64>, 71 } 72 73 impl ProjectState { 74 fn new(statedir: &Path, project: &str) -> Result<Self, StateError> { 75 let state = project::State::from_file(statedir, project) 76 .map_err(|err| StateError::LoadState(project.to_string(), err))?; 77 78 Ok(Self { 79 latest_commit: state.latest_commit.clone(), 80 run_log: Some(Self::file_length(statedir, &state.run_log_filename())?), 81 dependencies: Some(Self::dir_size(&state.dependenciesdir())?), 82 cache: Some(Self::dir_size(&state.cachedir())?), 83 artifacts: Some(Self::dir_size(&state.artifactsdir())?), 84 }) 85 } 86 87 fn file_length(statedir: &Path, filename: &Path) -> Result<u64, StateError> { 88 let pathname = statedir.join(filename); 89 let meta = pathname 90 .metadata() 91 .map_err(|err| StateError::Metadata(pathname, err))?; 92 Ok(meta.len()) 93 } 94 95 fn dir_size(path: &Path) -> Result<u64, StateError> { 96 let mut bytes = 0; 97 for entry in WalkDir::new(path) { 98 let entry = entry.map_err(|err| StateError::ListDir(path.to_path_buf(), err))?; 99 let meta = entry 100 .metadata() 101 .map_err(|err| StateError::MetadataWalkDir(entry.path().to_path_buf(), err))?; 102 bytes += meta.len(); 103 } 104 Ok(bytes) 105 } 106 } 107 108 #[derive(Debug, thiserror::Error)] 109 pub enum StateError { 110 #[error("failed list contents of state directory {0}")] 111 ListDir(PathBuf, #[source] walkdir::Error), 112 113 #[error("failed to load project state: {0}")] 114 LoadState(String, #[source] ProjectError), 115 116 #[error("failed to serialize project states as JSON")] 117 ToJson(#[source] serde_json::Error), 118 119 #[error("failed to get meta data for {0}")] 120 Metadata(PathBuf, #[source] std::io::Error), 121 122 #[error("failed to get meta data for {0}")] 123 MetadataWalkDir(PathBuf, #[source] walkdir::Error), 124 }