use std::path::PathBuf;
use std::sync::Arc;
use std::{fs, process::Command};
use crate::infra::io::durable::migrate_tier_entry;
use crate::platform::error::PlatformError;
use crate::platform::model::{FileAdapterKind, ProjectFileLayout, slug_segment};
use crate::platform::services::project_config::ProjectConfigurationService;
pub trait FileAdapter: Send + Sync {
fn id(&self) -> &'static str;
fn initialize(&self) -> Result<(), PlatformError>;
fn ensure_project_layout(
&self,
owner: &str,
project: &str,
) -> Result<ProjectFileLayout, PlatformError>;
}
pub struct FilesystemFileAdapter {
root: PathBuf,
configs: Arc<ProjectConfigurationService>,
}
impl FilesystemFileAdapter {
pub fn new(root: PathBuf, configs: Arc<ProjectConfigurationService>) -> Self {
Self { root, configs }
}
fn project_root(&self, owner: &str, project: &str) -> PathBuf {
self.root
.join(slug_segment(owner))
.join(slug_segment(project))
}
fn ensure_git_repo(repo_dir: &PathBuf, git_dir: &PathBuf) -> Result<(), PlatformError> {
if git_dir.exists() {
return Ok(());
}
let status = Command::new("git")
.arg("init")
.arg("-q")
.arg("--initial-branch=main")
.current_dir(repo_dir)
.status()
.map_err(|e| PlatformError::new("PLATFORM_GIT_INIT", e.to_string()))?;
if status.success() {
return Ok(());
}
Err(PlatformError::new(
"PLATFORM_GIT_INIT",
format!("git init failed with status {status}"),
))
}
}
impl FileAdapter for FilesystemFileAdapter {
fn id(&self) -> &'static str {
"file.filesystem"
}
fn initialize(&self) -> Result<(), PlatformError> {
std::fs::create_dir_all(&self.root)?;
Ok(())
}
fn ensure_project_layout(
&self,
owner: &str,
project: &str,
) -> Result<ProjectFileLayout, PlatformError> {
let root = self.project_root(owner, project);
let data_dir = root.join("data");
let files_dir = root.join("files");
let repo_dir = root.join("repo");
let repo_git_dir = repo_dir.join(".git");
let project_config_file =
repo_dir.join(crate::contracts::kinds::PROJECT_CONFIGURATION_FILE);
let repo_layout = self.configs.project_layout(owner, project)?;
let file_backend = self.configs.project_file_backend(owner, project)?;
let resolved = ProjectFileLayout {
root,
data_dir,
files_dir,
repo_dir,
repo_git_dir,
project_config_file,
repo_layout,
file_backend,
};
migrate_tier_entry(
&resolved.data_dir.join("runtime"),
&resolved.data_cache_dir(),
)
.map_err(|err| PlatformError::new("PLATFORM_DATA_TIER_MIGRATE", err.to_string()))?;
migrate_tier_entry(
&resolved.data_dir.join("nodes"),
&resolved.data_hub_nodes_dir(),
)
.map_err(|err| PlatformError::new("PLATFORM_DATA_TIER_MIGRATE", err.to_string()))?;
for dir in [
&resolved.root,
&resolved.data_dir,
&resolved.data_store_dir(),
&resolved.data_cache_dir(),
&resolved.data_cache_pipelines_dir(),
&resolved.data_cache_agent_docs_dir(),
&resolved.data_hub_dir(),
&resolved.data_hub_nodes_dir(),
&resolved.data_hub_rwe_libraries_dir(),
&resolved.data_recovery_dir(),
&resolved.data_logs_dir(),
&resolved.files_dir,
&resolved.repo_dir,
] {
fs::create_dir_all(dir)?;
}
Self::ensure_git_repo(&resolved.repo_dir, &resolved.repo_git_dir)?;
Ok(resolved)
}
}
pub fn build_file_adapter(
kind: FileAdapterKind,
data_root: PathBuf,
configs: Arc<ProjectConfigurationService>,
) -> Arc<dyn FileAdapter> {
match kind {
FileAdapterKind::Filesystem => {
Arc::new(FilesystemFileAdapter::new(data_root.join("users"), configs))
}
}
}