use std::path::{Path, PathBuf};
use chrono::SubsecRound;
use crate::workspace::{WorkspaceEntry, WorkspaceRegistry};
use super::adopt::{WRITE_LOCK, default_name, now};
use super::schema::SCHEMA_VERSION;
use super::store::{
canonical_lenient, find_by_root, lock_manifests, read_manifest, read_project_toml,
reap_orphans, write_manifest,
};
use super::{
LegacySection, ProjectError, ProjectManifest, ProjectState, ProjectTomlPresence, SeedSection,
ServeSection, new_id,
};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SeedReport {
pub created: Vec<String>,
pub adopted: Vec<String>,
pub missing: Vec<String>,
pub unchanged: Vec<String>,
pub skipped: Vec<(PathBuf, String)>,
}
pub fn seed_from_workspaces(
workspaces_json: &Path,
manifests_dir: &Path,
) -> Result<SeedReport, ProjectError> {
let registry = WorkspaceRegistry::load(workspaces_json).map_err(|e| ProjectError::Parse {
path: workspaces_json.to_path_buf(),
message: e.to_string(),
})?;
seed_from_registry(®istry, manifests_dir)
}
pub fn seed_from_registry(
registry: &WorkspaceRegistry,
manifests_dir: &Path,
) -> Result<SeedReport, ProjectError> {
let _guard = WRITE_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let _flock = lock_manifests(manifests_dir)?;
reap_orphans(manifests_dir);
let mut report = SeedReport::default();
for entry in ®istry.workspaces {
if let Err(e) = seed_entry(entry, manifests_dir, &mut report) {
report.skipped.push((entry.path.clone(), e.to_string()));
}
}
Ok(report)
}
fn seed_entry(
entry: &WorkspaceEntry,
dir: &Path,
report: &mut SeedReport,
) -> Result<(), ProjectError> {
let root = canonical_lenient(&entry.path);
let exists = root.is_dir();
let known = find_by_root(dir, &root)?;
if let Some(mut m) = known {
let want = if exists {
ProjectState::Active
} else {
ProjectState::Missing
};
if m.state == ProjectState::Archived || m.state == want {
report.unchanged.push(m.id);
return Ok(());
}
m.state = want;
write_manifest(dir, &m)?;
if exists {
report.adopted.push(m.id)
} else {
report.missing.push(m.id)
}
return Ok(());
}
let stamp = now();
let created = entry.created_at.map_or(stamp, |t| t.trunc_subsecs(0));
let last_seen = entry.last_accessed.map_or(created, |t| t.trunc_subsecs(0));
let (id, name, presence, reused) = match read_project_toml(&root)?.filter(|_| exists) {
Some(pt) => (pt.id, pt.name, ProjectTomlPresence::Present, true),
None => {
let name = if entry.name.is_empty() {
default_name(&root)
} else {
entry.name.clone()
};
(new_id(), name, ProjectTomlPresence::Pending, false)
}
};
if let Some(other) = read_manifest(dir, &id)? {
return Err(ProjectError::RootConflict {
id,
root,
existing: other.root,
});
}
let m = ProjectManifest {
schema_version: SCHEMA_VERSION,
id: id.clone(),
name,
legacy: Some(LegacySection {
runtime_dir: Some(root.join(".weftos").join("runtime")),
}),
root,
state: if exists {
ProjectState::Active
} else {
ProjectState::Missing
},
created,
last_seen,
project_toml: presence,
seed: Some(SeedSection {
source: "workspaces.json".into(),
legacy_name: Some(entry.name.clone()),
}),
serve: Some(ServeSection::default()),
chain: None,
binary: None,
extra: Default::default(),
};
write_manifest(dir, &m)?;
match (exists, reused) {
(false, _) => report.missing.push(id),
(true, true) => report.adopted.push(id),
(true, false) => report.created.push(id),
}
Ok(())
}