use std::collections::BTreeMap;
use std::fs::{self, File};
use std::io;
use std::path::{Path, PathBuf};
use crate::graph::graph_json_path;
use crate::layout;
use super::record::{
new_generation, owner_of, record_path, remove_state, state_dir, write_digests, write_marker,
write_record, ArtifactIdentity, Artifacts, Record, RECORD_FORMAT, RECORD_VERSION,
};
pub struct MapSession {
root: PathBuf,
owner: Option<String>,
generation: Option<String>,
_lock: Option<File>,
}
impl MapSession {
#[must_use]
pub fn owner(&self) -> Option<&str> {
self.owner.as_deref()
}
#[must_use]
pub fn generation(&self) -> &str {
self.generation.as_deref().unwrap_or("")
}
pub fn mark_in_progress(&self) -> io::Result<()> {
match self.owner.as_deref() {
Some(owner) => write_marker(&self.root, owner),
None => remove_state(&self.root),
}
}
pub fn publish(
&self,
head: Option<String>,
scope: &[String],
file_count: u64,
dirty_at_map: Option<Vec<String>>,
config_inputs: BTreeMap<String, String>,
) -> io::Result<()> {
let Some(owner) = self.owner.as_deref() else {
return Ok(());
};
let record = Record {
format: RECORD_FORMAT.to_string(),
version: RECORD_VERSION,
state: "published".to_string(),
owner: owner.to_string(),
generation: self.generation().to_string(),
indexed_head: head,
scope: scope.to_vec(),
indexed_at: crate::hookio::iso8601_now(),
file_count,
dirty_at_map,
config_inputs,
artifacts: Artifacts {
graph: ArtifactIdentity::of(&graph_json_path(&self.root)),
manifest: ArtifactIdentity::of(&state_dir(&self.root).join(layout::MANIFEST_FILE)),
},
};
write_record(&self.root, &record)
}
pub fn write_digests(&self, files: &BTreeMap<String, String>) -> io::Result<()> {
write_digests(&self.root, self.generation(), files)
}
}
pub fn begin(root: &Path) -> io::Result<MapSession> {
let lock = acquire_lock(root)?;
let owner = owner_of(root);
let generation = owner.as_deref().map(new_generation);
Ok(MapSession {
root: root.to_path_buf(),
owner,
generation,
_lock: Some(lock),
})
}
fn acquire_lock(root: &Path) -> io::Result<File> {
let path = super::record::lock_path(root);
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
let file = fs::OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(&path)?;
match file.try_lock() {
Ok(()) => Ok(file),
Err(std::fs::TryLockError::WouldBlock) => {
eprintln!("devscout map: waiting for another map of this repository to finish");
file.lock()?;
Ok(file)
}
Err(std::fs::TryLockError::Error(_)) => Ok(file),
}
}
#[must_use]
pub fn record_exists(root: &Path) -> bool {
record_path(root).exists()
}