use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use prikk_error::{PrikkError, Result};
use prikk_hash::{sha256, to_hex};
use prikk_object::{ObjectId, ObjectType};
use crate::fsutil::{sync_directory_best_effort, write_file_atomically};
const REPO_DIR: &str = ".prikk";
const FORMAT_VERSION: &str = "1\n";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RepositoryLayout {
root: PathBuf,
prikk_dir: PathBuf,
}
impl RepositoryLayout {
#[must_use]
pub fn new(root: impl Into<PathBuf>) -> Self {
let root = root.into();
let prikk_dir = root.join(REPO_DIR);
Self { root, prikk_dir }
}
pub fn init(root: impl Into<PathBuf>) -> Result<Self> {
let layout = Self::new(root);
fs::create_dir_all(&layout.prikk_dir)?;
for dir in layout.required_directories() {
fs::create_dir_all(dir)?;
}
write_file_atomically(&layout.format_path(), FORMAT_VERSION.as_bytes())?;
sync_directory_best_effort(&layout.prikk_dir)?;
Ok(layout)
}
pub fn open(root: impl Into<PathBuf>) -> Result<Self> {
let layout = Self::new(root);
let mut version = String::new();
File::open(layout.format_path())?.read_to_string(&mut version)?;
if version != FORMAT_VERSION {
return Err(PrikkError::UnsupportedFormatVersion(0));
}
Ok(layout)
}
#[must_use]
pub fn root(&self) -> &Path {
&self.root
}
#[must_use]
pub fn prikk_dir(&self) -> &Path {
&self.prikk_dir
}
#[must_use]
pub fn format_path(&self) -> PathBuf {
self.prikk_dir.join("FORMAT")
}
#[must_use]
pub fn objects_dir(&self) -> PathBuf {
self.prikk_dir.join("objects")
}
#[must_use]
pub fn active_dir(&self) -> PathBuf {
self.prikk_dir.join("active")
}
#[must_use]
pub fn default_active_dir(&self) -> PathBuf {
self.active_dir().join("default")
}
#[must_use]
pub fn default_queue_wal_path(&self) -> PathBuf {
self.default_active_dir().join("queue.wal")
}
#[must_use]
pub fn default_active_lock_path(&self) -> PathBuf {
self.default_active_dir().join("active.lock")
}
#[must_use]
pub fn refs_dir(&self) -> PathBuf {
self.prikk_dir.join("refs")
}
#[must_use]
pub fn cache_dir(&self) -> PathBuf {
self.prikk_dir.join("cache")
}
#[must_use]
pub fn quarantine_dir(&self) -> PathBuf {
self.prikk_dir.join("quarantine")
}
#[must_use]
pub fn required_directories(&self) -> Vec<PathBuf> {
let mut dirs = Vec::new();
dirs.push(self.objects_dir());
for object_type in persisted_object_types() {
dirs.push(self.object_type_dir(object_type));
}
dirs.push(self.active_dir());
dirs.push(self.default_active_dir());
dirs.push(self.refs_dir());
dirs.push(self.refs_dir().join("by-id"));
dirs.push(self.refs_dir().join("logs"));
dirs.push(self.refs_dir().join("locks"));
dirs.push(self.refs_dir().join("tmp"));
dirs.push(self.cache_dir());
dirs.push(self.quarantine_dir());
dirs
}
#[must_use]
pub fn object_type_dir(&self, object_type: ObjectType) -> PathBuf {
self.objects_dir()
.join(object_type_directory_name(object_type))
}
#[must_use]
pub fn object_path(&self, object_type: ObjectType, id: ObjectId) -> PathBuf {
let hex = id.to_hex();
let prefix = hex_prefix(&hex);
self.object_type_dir(object_type)
.join(prefix)
.join(format!("{hex}.pobj"))
}
#[must_use]
pub fn ref_pointer_path(&self, ref_name: &str) -> PathBuf {
self.refs_dir()
.join("by-id")
.join(format!("{}.ref", ref_name_storage_key(ref_name)))
}
#[must_use]
pub fn ref_log_path(&self, ref_name: &str) -> PathBuf {
self.refs_dir()
.join("logs")
.join(format!("{}.log", ref_name_storage_key(ref_name)))
}
#[must_use]
pub fn ref_lock_path(&self, ref_name: &str) -> PathBuf {
self.refs_dir()
.join("locks")
.join(format!("{}.lock", ref_name_storage_key(ref_name)))
}
#[must_use]
pub fn ref_tmp_path(&self, ref_name: &str) -> PathBuf {
self.refs_dir()
.join("tmp")
.join(format!("{}.tmp", ref_name_storage_key(ref_name)))
}
}
#[must_use]
pub fn persisted_object_types() -> [ObjectType; 6] {
[
ObjectType::Patch,
ObjectType::Block,
ObjectType::RefState,
ObjectType::Tag,
ObjectType::Attestation,
ObjectType::Blob,
]
}
#[must_use]
pub fn object_type_directory_name(object_type: ObjectType) -> &'static str {
match object_type {
ObjectType::Patch => "patch",
ObjectType::Block => "block",
ObjectType::RefState => "ref-state",
ObjectType::Tag => "tag",
ObjectType::Attestation => "attestation",
ObjectType::Blob => "blob",
ObjectType::RefUpdate => "ref-update-inline-only",
ObjectType::BlockSummaryCache => "block-summary-cache-rebuildable",
ObjectType::RecoveryNote => "recovery-note-inline-only",
ObjectType::ProjectGenesis => "genesis",
}
}
fn hex_prefix(hex: &str) -> String {
hex.chars().take(2).collect()
}
fn ref_name_storage_key(ref_name: &str) -> String {
to_hex(&sha256(ref_name.as_bytes()))
}