use std::time::Duration;
use std::{ffi::OsStr, process::ExitStatus};
use either::{Either, Left, Right};
use rand::Rng;
use super::{
coordinate::{resource, CoordinateError, State},
exists, Cluster, ClusterError,
};
pub type ResourceFree = resource::ResourceFree<Cluster>;
pub type ResourceShared = resource::ResourceShared<Cluster>;
pub type ResourceExclusive = resource::ResourceExclusive<Cluster>;
pub type Error = CoordinateError<ClusterError>;
impl From<ClusterError> for Error {
fn from(err: ClusterError) -> Self {
Self::ControlError(err)
}
}
impl resource::FacetFree for Cluster {
type FacetFree<'a> = ClusterFree<'a>;
fn facet_free(&self) -> Self::FacetFree<'_> {
ClusterFree { cluster: self }
}
}
impl resource::FacetShared for Cluster {
type FacetShared<'a> = ClusterShared<'a>;
fn facet_shared(&self) -> Self::FacetShared<'_> {
ClusterShared { cluster: self }
}
}
impl resource::FacetExclusive for Cluster {
type FacetExclusive<'a> = ClusterExclusive<'a>;
fn facet_exclusive(&self) -> Self::FacetExclusive<'_> {
ClusterExclusive { cluster: self }
}
}
pub struct ClusterFree<'a> {
cluster: &'a Cluster,
}
impl ClusterFree<'_> {
pub fn exists(&self) -> Result<bool, ClusterError> {
Ok(exists(self.cluster))
}
pub fn running(&self) -> Result<bool, ClusterError> {
self.cluster.running()
}
}
pub struct ClusterShared<'a> {
cluster: &'a Cluster,
}
impl ClusterShared<'_> {
pub fn exists(&self) -> Result<bool, ClusterError> {
Ok(exists(self.cluster))
}
pub fn running(&self) -> Result<bool, ClusterError> {
self.cluster.running()
}
pub fn pool(&self, database: Option<&str>) -> Result<sqlx::PgPool, ClusterError> {
self.cluster.pool(database)
}
pub fn exec<T: AsRef<OsStr>>(
&self,
database: Option<&str>,
command: T,
args: &[T],
) -> Result<ExitStatus, ClusterError> {
self.cluster.exec(database, command, args)
}
}
pub struct ClusterExclusive<'a> {
cluster: &'a Cluster,
}
impl ClusterExclusive<'_> {
pub fn start(&self, options: super::Options<'_>) -> Result<State, ClusterError> {
self.cluster.start(options)
}
pub fn stop(&self) -> Result<State, ClusterError> {
self.cluster.stop()
}
pub fn destroy(&self) -> Result<State, ClusterError> {
self.cluster.destroy()
}
pub fn exists(&self) -> Result<bool, ClusterError> {
Ok(exists(self.cluster))
}
pub fn running(&self) -> Result<bool, ClusterError> {
self.cluster.running()
}
pub fn pool(&self, database: Option<&str>) -> Result<sqlx::PgPool, ClusterError> {
self.cluster.pool(database)
}
pub fn exec<T: AsRef<OsStr>>(
&self,
database: Option<&str>,
command: T,
args: &[T],
) -> Result<ExitStatus, ClusterError> {
self.cluster.exec(database, command, args)
}
}
pub type HeldResource = Either<ResourceShared, ResourceExclusive>;
pub fn startup(
mut resource: ResourceFree,
options: super::Options<'_>,
) -> Result<(State, HeldResource), Error> {
loop {
resource = match resource.try_exclusive() {
Ok(Left(resource)) => {
let resource = resource.shared()?;
if resource.facet().running()? {
return Ok((State::Unmodified, Left(resource)));
}
let resource = resource.release()?;
let delay = rand::rng().next_u32();
let delay = 200 + (delay % 800);
let delay = Duration::from_millis(u64::from(delay));
std::thread::sleep(delay);
resource
}
Ok(Right(resource)) => {
let state = resource.facet().start(options)?;
return Ok((state, Right(resource)));
}
Err(err) => return Err(err),
};
}
}
pub fn startup_if_exists(
mut resource: ResourceFree,
options: super::Options<'_>,
) -> Result<(State, HeldResource), Error> {
loop {
resource = match resource.try_exclusive() {
Ok(Left(resource)) => {
let resource = resource.shared()?;
if resource.facet().running()? {
return Ok((State::Unmodified, Left(resource)));
}
let resource = resource.release()?;
let delay = rand::rng().next_u32();
let delay = 200 + (delay % 800);
let delay = Duration::from_millis(u64::from(delay));
std::thread::sleep(delay);
resource
}
Ok(Right(resource)) => {
let facet = resource.facet();
let state = if facet.exists()? {
facet.start(options)?
} else {
return Err(CoordinateError::DoesNotExist);
};
return Ok((state, Right(resource)));
}
Err(err) => return Err(err),
};
}
}
pub fn shutdown(resource: ResourceShared) -> Result<(State, HeldResource), Error> {
match resource.try_exclusive() {
Ok(Left(resource)) => {
Ok((State::Unmodified, Left(resource)))
}
Ok(Right(resource)) => {
match resource.facet().stop() {
Ok(state) => Ok((state, Right(resource))),
Err(err) => {
resource.release()?;
Err(err)?
}
}
}
Err(err) => Err(err),
}
}
pub fn destroy(resource: ResourceShared) -> Result<(State, HeldResource), Error> {
match resource.try_exclusive() {
Ok(Left(resource)) => {
Ok((State::Unmodified, Left(resource)))
}
Ok(Right(resource)) => {
match resource.facet().destroy() {
Ok(state) => Ok((state, Right(resource))),
Err(err) => {
resource.release()?;
Err(err)?
}
}
}
Err(err) => Err(err),
}
}