use std::io;
use std::path::{Path, PathBuf};
use uuid::Uuid;
use crate::constants::OXEN_HIDDEN_DIR;
use crate::error::OxenError;
use crate::util::fs::{AtomicFile, create_dir_all};
pub(crate) const NAME_TABLE_DIR: &str = "name_table";
const REPOS_DIR: &str = "repo";
const REPOS_DIR_MARKER: &str = "placement-v2";
const SERVER_OWNED_DIRS: &[&str] = &[NAME_TABLE_DIR, OXEN_HIDDEN_DIR, REPOS_DIR];
pub(crate) fn is_namespace(name: &str) -> bool {
!SERVER_OWNED_DIRS.contains(&name)
}
pub(crate) fn is_server_owned(name: &str) -> bool {
SERVER_OWNED_DIRS
.iter()
.any(|owned| owned.eq_ignore_ascii_case(name))
}
pub(crate) fn placed_repo_dir(sync_dir: &Path, repo_uuid: Uuid) -> PathBuf {
let uuid = repo_uuid.to_string();
sync_dir
.join(REPOS_DIR)
.join(&uuid[0..2])
.join(&uuid[2..4])
.join(uuid)
}
pub(crate) fn prepare_placed_repo_dir(
sync_dir: &Path,
repo_uuid: Uuid,
) -> Result<PathBuf, OxenError> {
let marker = sync_dir.join(REPOS_DIR).join(REPOS_DIR_MARKER);
if !marker.is_file() {
AtomicFile::new(&marker).write(b"")?;
}
let dir = placed_repo_dir(sync_dir, repo_uuid);
if let Some(parent) = dir.parent() {
create_dir_all(parent)?;
}
Ok(dir)
}
pub(crate) fn create_placed_repo_dir(
sync_dir: &Path,
repo_uuid: Uuid,
) -> Result<PathBuf, OxenError> {
let dir = prepare_placed_repo_dir(sync_dir, repo_uuid)?;
match std::fs::create_dir(&dir) {
Ok(()) => Ok(dir),
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
Err(OxenError::RepoUuidTaken(repo_uuid))
}
Err(err) => Err(OxenError::file_error(&dir, err)),
}
}
pub fn namespace_called_repo(sync_dir: &Path) -> Option<PathBuf> {
let dir = sync_dir.join(REPOS_DIR);
let occupied = std::fs::read_dir(&dir).is_ok_and(|mut entries| entries.next().is_some());
(occupied && !dir.join(REPOS_DIR_MARKER).is_file()).then_some(dir)
}
pub fn namespace_dirs(sync_dir: &Path) -> Result<Vec<PathBuf>, OxenError> {
sorted_dirs(sync_dir, |path| {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(is_namespace)
&& path.is_dir()
})
.map_err(|err| OxenError::internal_error(format!("Cannot read {sync_dir:?}: {err}")))
}
pub fn placed_repo_dirs(sync_dir: &Path) -> io::Result<Vec<PathBuf>> {
let root = sync_dir.join(REPOS_DIR);
let mut dirs = vec![];
if root.is_dir() {
for bucket in sorted_dirs(&root, Path::is_dir)? {
for sub_bucket in sorted_dirs(&bucket, Path::is_dir)? {
dirs.extend(repo_dirs(&sub_bucket)?);
}
}
}
Ok(dirs)
}
pub fn repo_dirs(namespace_dir: &Path) -> io::Result<Vec<PathBuf>> {
sorted_dirs(namespace_dir, |path| path.join(OXEN_HIDDEN_DIR).is_dir())
}
fn sorted_dirs(dir: &Path, keep: impl Fn(&Path) -> bool) -> io::Result<Vec<PathBuf>> {
let mut dirs = vec![];
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if keep(&path) {
dirs.push(path);
}
}
dirs.sort();
Ok(dirs)
}