use crate::clone_backend::{same_volume, ApfsBackend, CloneBackend};
pub use crate::config::Config;
use crate::error::{ErrorCode, MitosisError, Result};
use crate::paths::Home;
use crate::pg::{discover_with, find_major, newest, PgInstall};
use crate::protocol::EventRow;
use crate::router::RouterState;
use crate::state::Store;
use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicUsize;
use std::sync::{Arc, Mutex, MutexGuard, RwLock};
use std::time::Instant;
const EVENT_CHANNEL: usize = 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum InFlightOp {
Create,
Destroy,
}
pub struct Ctx {
pub home: Home,
config: RwLock<Config>,
pub store: Mutex<Store>,
pub backend: Arc<dyn CloneBackend>,
installs: RwLock<Vec<PgInstall>>,
pub started_at: Instant,
pub(crate) building: Mutex<HashSet<String>>,
pub(crate) in_flight: Mutex<HashMap<String, InFlightOp>>,
pub(crate) watching: Mutex<HashSet<String>>,
pub(crate) template_swap: tokio::sync::RwLock<()>,
pub(crate) build_groups: Arc<Mutex<HashSet<i32>>>,
pub room: tokio::sync::Notify,
pub(crate) admission: Arc<tokio::sync::Mutex<()>>,
pub(crate) queued: AtomicUsize,
pub events_tx: tokio::sync::broadcast::Sender<EventRow>,
router: Mutex<RouterState>,
pub(crate) waking: Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
#[doc(hidden)]
pub layered_skips: AtomicUsize,
}
impl Ctx {
pub fn open(home: Home, config: Config) -> Result<Ctx> {
home.ensure_dirs()?;
let backend: Arc<dyn CloneBackend> = Arc::new(ApfsBackend);
backend.check_supported(&home.root)?;
if !same_volume(&home.templates_dir(), &home.clones_dir())? {
return Err(MitosisError::new(
ErrorCode::CrossVolume,
format!(
"{} and {} are on different volumes",
home.templates_dir().display(),
home.clones_dir().display()
),
)
.with_hint(
"keep templates/ and clones/ on the same APFS volume (no symlinks to other disks)",
));
}
let store = Store::open(&home.state_db())?;
let installs = discover_with(Some(&home));
let router = Mutex::new(RouterState::new(config.router_port));
Ok(Ctx {
home,
config: RwLock::new(config),
store: Mutex::new(store),
backend,
installs: RwLock::new(installs),
started_at: Instant::now(),
building: Mutex::new(HashSet::new()),
in_flight: Mutex::new(HashMap::new()),
watching: Mutex::new(HashSet::new()),
template_swap: tokio::sync::RwLock::new(()),
build_groups: Arc::new(Mutex::new(HashSet::new())),
room: tokio::sync::Notify::new(),
admission: Arc::new(tokio::sync::Mutex::new(())),
queued: AtomicUsize::new(0),
events_tx: tokio::sync::broadcast::channel(EVENT_CHANNEL).0,
router,
waking: Mutex::new(HashMap::new()),
layered_skips: AtomicUsize::new(0),
})
}
pub fn config(&self) -> Config {
self.config
.read()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
pub fn set_config(&self, config: Config) {
*self.config.write().unwrap_or_else(|e| e.into_inner()) = config;
}
pub fn installs(&self) -> Vec<PgInstall> {
self.installs
.read()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
async fn rediscover(&self) {
let home = self.home.clone();
if let Ok(found) = tokio::task::spawn_blocking(move || discover_with(Some(&home))).await {
*self.installs.write().unwrap_or_else(|e| e.into_inner()) = found;
}
}
pub async fn install_major(&self, major: u32) -> Result<PgInstall> {
if let Ok(i) = find_major(&self.installs(), major) {
return Ok(i.clone());
}
self.rediscover().await;
find_major(&self.installs(), major).cloned()
}
pub async fn newest_install(&self) -> Result<PgInstall> {
if let Ok(i) = newest(&self.installs()) {
return Ok(i.clone());
}
self.rediscover().await;
newest(&self.installs()).cloned()
}
pub fn router(&self) -> MutexGuard<'_, RouterState> {
self.router.lock().unwrap_or_else(|e| e.into_inner())
}
pub fn store(&self) -> MutexGuard<'_, Store> {
self.store.lock().unwrap_or_else(|e| e.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_creates_layout() {
let tmp = tempfile::tempdir().unwrap();
let ctx = Ctx::open(Home::at(tmp.path().join("m")), Config::default()).unwrap();
assert!(ctx.home.templates_dir().is_dir());
assert!(ctx.home.state_db().is_file());
assert!(ctx.store().list_templates().unwrap().is_empty());
}
#[allow(dead_code)]
fn ctx_is_send_sync(c: Ctx) -> impl Send + Sync {
c
}
}