use rayon::prelude::*;
use std::path::Path;
use crate::model::{LocalRepository, Namespace};
use crate::repositories;
use crate::repositories::size::{self, RepoSizeFile, SizeStatus};
use crate::sync_dir::{is_namespace, namespace_dirs};
pub fn list(path: &Path) -> Vec<String> {
log::debug!("repositories::namespaces::list",);
namespace_dirs(path)
.unwrap_or_default()
.iter()
.filter_map(|dir| dir.file_name()?.to_str().map(str::to_string))
.collect()
}
pub fn get(data_dir: &Path, name: &str) -> Option<Namespace> {
log::debug!("repositories::namespaces::get {name}");
let namespace_path = data_dir.join(name);
if !is_namespace(name) || !namespace_path.is_dir() {
return None;
}
let repos: Vec<LocalRepository> =
repositories::list_repos_in_namespace(&namespace_path).collect();
let figures: Vec<RepoSizeFile> = repos.par_iter().map(size::get_size).collect();
for (repo, figure) in repos.iter().zip(&figures) {
if matches!(figure.status, SizeStatus::Error)
&& figure.size == 0
&& let Err(cause) = size::update_size(repo)
{
tracing::warn!(
repo = ?repo.path,
?cause,
"Could not start a size recalculation for a repository counted as nothing"
);
}
}
let outstanding = figures
.iter()
.filter(|figure| !matches!(figure.status, SizeStatus::Done))
.count();
if outstanding > 0 {
tracing::warn!(
namespace = name,
outstanding,
repositories = figures.len(),
"Reporting a storage total that counts some repositories at a figure no pass completed"
);
}
Some(Namespace {
name: name.to_string(),
storage_usage_gb: figures.iter().map(|figure| figure.size).sum::<u64>() as f64
/ bytesize::GB as f64,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::OxenError;
use crate::repositories::size::repo_size_path;
use crate::test;
use crate::util;
use crate::util::fs::AtomicFile;
fn repo_recording(path: &Path, record: &str) -> Result<LocalRepository, OxenError> {
let repo = repositories::init(path)?;
AtomicFile::new(repo_size_path(&repo)).write(record.as_bytes())?;
Ok(repo)
}
#[test]
fn test_get_sums_the_recorded_size_of_every_repo() -> Result<(), OxenError> {
test::run_empty_dir_test(|dir| {
assert!(
get(dir, "ox").is_none(),
"a namespace with no directory on disk is reported as absent"
);
let namespace_path = dir.join("ox");
repo_recording(&namespace_path.join("first"), "1500")?;
let second = repo_recording(&namespace_path.join("second"), "2500")?;
let namespace = get(dir, "ox").expect("namespace exists");
assert_eq!(namespace.storage_usage_gb, 4000.0 / bytesize::GB as f64);
util::fs::write_to_path(repo_size_path(&second), "not a size record")?;
let namespace = get(dir, "ox").expect("namespace exists");
assert_eq!(
namespace.storage_usage_gb,
1500.0 / bytesize::GB as f64,
"a repository with no readable figure leaves the rest of the total intact"
);
assert_eq!(
size::wait_for_recorded_size(&second)?,
second.version_bytes()?,
"reading a namespace starts a recalculation for a repository with no figure"
);
Ok(())
})
}
}