use futures::future::BoxFuture;
use futures::{TryFutureExt};
use std::process::{Command, Child};
use crate::fetch;
use crate::errors::PgEmbedError;
use tokio::io::AsyncWriteExt;
use crate::errors::PgEmbedError::PgCleanUpFailure;
use sqlx::{Connection, PgConnection};
use std::borrow::BorrowMut;
use std::time::Duration;
use tokio::time::sleep;
pub struct PgSettings {
pub executables_dir: String,
pub database_dir: String,
pub port: i16,
pub user: String,
pub password: String,
pub persistent: bool,
pub start_timeout: Duration,
}
pub struct PgEmbed {
pub pg_settings: PgSettings,
pub fetch_settings: fetch::FetchSettings,
pub process: Option<Child>,
pub db_uri: String,
}
impl Drop for PgEmbed {
fn drop(&mut self) {
let _ = &self.stop_db();
if !&self.pg_settings.persistent {
let _ = &self.clean();
}
}
}
impl PgEmbed {
pub fn new(pg_settings: PgSettings, fetch_settings: fetch::FetchSettings) -> Self {
let db_uri = format!(
"postgres://{}:{}@localhost:{}",
&pg_settings.user,
&pg_settings.password,
&pg_settings.port
);
PgEmbed {
pg_settings,
fetch_settings,
process: None,
db_uri
}
}
pub fn clean(&self) -> Result<(), PgEmbedError> {
let bin_dir = format!("{}/bin", &self.pg_settings.executables_dir);
let lib_dir = format!("{}/lib", &self.pg_settings.executables_dir);
let share_dir = format!("{}/share", &self.pg_settings.executables_dir);
let pw_file = format!("{}/pwfile", &self.pg_settings.executables_dir);
std::fs::remove_dir_all(&self.pg_settings.database_dir).map_err(|e| PgCleanUpFailure(e))?;
std::fs::remove_dir_all(bin_dir).map_err(|e| PgCleanUpFailure(e))?;
std::fs::remove_dir_all(lib_dir).map_err(|e| PgCleanUpFailure(e))?;
std::fs::remove_dir_all(share_dir).map_err(|e| PgCleanUpFailure(e))?;
std::fs::remove_file(pw_file).map_err(|e| PgCleanUpFailure(e))?;
Ok(())
}
pub async fn setup(&self) -> Result<(), PgEmbedError> {
&self.aquire_postgres().await?;
&self.create_password_file().await?;
&self.init_db().await?;
Ok(())
}
pub async fn aquire_postgres(&self) -> Result<(), PgEmbedError> {
let pg_file = fetch::fetch_postgres(&self.fetch_settings, &self.pg_settings.executables_dir).await?;
fetch::unpack_postgres(&pg_file, &self.pg_settings.executables_dir).await
}
pub async fn init_db(&self) -> Result<bool, PgEmbedError> {
let database_path = std::path::Path::new(&self.pg_settings.database_dir);
if !database_path.is_dir() {
let init_db_executable = format!("{}/bin/initdb", &self.pg_settings.executables_dir);
let password_file_arg = format!("--pwfile={}/pwfile", &self.pg_settings.executables_dir);
let process = Command::new(
init_db_executable,
)
.args(&[
"-A",
&self.pg_settings.password,
"-U",
&self.pg_settings.user,
"-D",
&self.pg_settings.database_dir,
&password_file_arg,
])
.spawn().map_err(|e| PgEmbedError::PgInitFailure(e))?;
sleep(self.pg_settings.start_timeout).await;
Ok(true)
} else {
Ok(false)
}
}
pub async fn start_db(&mut self) -> Result<(), PgEmbedError> {
let pg_ctl_executable = format!("{}/bin/pg_ctl", &self.pg_settings.executables_dir);
let port_arg = format!("-F -p {}", &self.pg_settings.port.to_string());
let mut process = Command::new(
pg_ctl_executable,
)
.args(&[
"-o", &port_arg, "start", "-w", "-D", &self.pg_settings.database_dir
])
.spawn().map_err(|e| PgEmbedError::PgStartFailure(e))?;
self.process = Some(process);
sleep(self.pg_settings.start_timeout).await;
Ok(())
}
pub fn stop_db(&mut self) -> Result<(), PgEmbedError> {
let pg_ctl_executable = format!("{}/bin/pg_ctl", &self.pg_settings.executables_dir);
let mut process = Command::new(
pg_ctl_executable,
)
.args(&[
"stop", "-w", "-D", &self.pg_settings.database_dir,
])
.spawn().map_err(|e| PgEmbedError::PgStopFailure(e))?;
match process.try_wait() {
Ok(Some(status)) => {
println!("postgresql stopped");
self.process = None;
Ok(())
}
Ok(None) => {
println!("... waiting for postgresql to stop");
let res = process.wait();
println!("result: {:?}", res);
Ok(())
}
Err(e) => Err(PgEmbedError::PgStopFailure(e)),
}
}
pub async fn create_password_file(&self) -> Result<(), PgEmbedError> {
let file_path = format!(
"{}/{}",
&self.pg_settings.executables_dir, "pwfile"
);
let mut file: tokio::fs::File = tokio::fs::File::create(&file_path).map_err(|e| PgEmbedError::WriteFileError(e)).await?;
let _ = file
.write(&self.pg_settings.password.as_bytes()).map_err(|e| PgEmbedError::WriteFileError(e))
.await?;
Ok(())
}
pub async fn create_database(&self, db_name: &str) -> Result<(), PgEmbedError> {
let sql_query = format!("CREATE DATABASE {}", db_name);
let mut conn = PgConnection::connect(&self.db_uri).await?;
let _ = sqlx::query(&sql_query).execute(&mut conn).await?;
Ok(())
}
pub fn full_db_uri(&self, db_name: &str) -> String {
format!("{}/{}", &self.db_uri, db_name)
}
}