/ src / bin / cmd / state.rs
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  }