use std::path::PathBuf;
use std::sync::Arc;
use std::{fs, process::Command};
use crate::platform::error::PlatformError;
use crate::platform::model::{FileAdapterKind, ProjectFileLayout, slug_segment};
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,
}
impl FilesystemFileAdapter {
pub fn new(root: PathBuf) -> Self {
Self { root }
}
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 data_runtime_dir = data_dir.join("runtime");
let data_runtime_pipelines_dir = data_runtime_dir.join("pipelines");
let files_dir = root.join("files");
let repo_dir = root.join("repo");
let repo_git_dir = repo_dir.join(".git");
let repo_pipelines_dir = repo_dir.join("pipelines");
let repo_docs_dir = repo_dir.join("docs");
let repo_nodes_dir = repo_dir.join("nodes");
let zebflow_json_file = repo_dir.join("zebflow.json");
let agent_docs_dir = data_runtime_dir.join("agent_docs");
for dir in [
&root,
&data_dir,
&data_runtime_dir,
&data_runtime_pipelines_dir,
&files_dir,
&files_dir.join("public"),
&files_dir.join("private"),
&repo_dir,
&repo_pipelines_dir,
&repo_docs_dir,
&agent_docs_dir,
] {
fs::create_dir_all(dir)?;
}
for subdir in ["assets", "styles"] {
fs::create_dir_all(repo_pipelines_dir.join(subdir))?;
}
Self::ensure_git_repo(&repo_dir, &repo_git_dir)?;
Ok(ProjectFileLayout {
root,
data_dir,
data_runtime_dir,
data_runtime_pipelines_dir,
files_dir,
repo_dir,
repo_git_dir,
repo_pipelines_dir,
repo_docs_dir,
zebflow_json_file,
agent_docs_dir,
repo_nodes_dir,
})
}
}
pub fn build_file_adapter(kind: FileAdapterKind, data_root: PathBuf) -> Arc<dyn FileAdapter> {
match kind {
FileAdapterKind::Filesystem => {
Arc::new(FilesystemFileAdapter::new(data_root.join("users")))
}
}
}