use archiver_rs::{
Archive, Compressed,
};
use futures::future::BoxFuture;
use futures::{TryFutureExt};
use std::borrow::Borrow;
use std::path::{PathBuf, Path};
use crate::errors::PgEmbedError;
use reqwest::Response;
use tokio::io::AsyncWriteExt;
#[derive(Debug, PartialEq)]
pub enum OperationSystem {
Darwin,
Windows,
Linux,
AlpineLinux,
}
impl ToString for OperationSystem {
fn to_string(&self) -> String {
match &self {
OperationSystem::Darwin => { "darwin".to_string() }
OperationSystem::Windows => { "windows".to_string() }
OperationSystem::Linux => { "linux".to_string() }
OperationSystem::AlpineLinux => { "linux".to_string() }
}
}
}
#[derive(Debug, PartialEq)]
pub enum Architecture {
Amd64,
I386,
Arm32v6,
Arm32v7,
Arm64v8,
Ppc64le,
}
impl ToString for Architecture {
fn to_string(&self) -> String {
match &self {
Architecture::Amd64 => {
"amd64".to_string()
}
Architecture::I386 => {
"i386".to_string()
}
Architecture::Arm32v6 => {
"arm32v6".to_string()
}
Architecture::Arm32v7 => {
"arm32v7".to_string()
}
Architecture::Arm64v8 => {
"arm64v8".to_string()
}
Architecture::Ppc64le => {
"ppc64le".to_string()
}
}
}
}
pub struct PostgresVersion(
&'static str,
);
pub const PG_V13: PostgresVersion =
PostgresVersion("13.1.0-1");
pub const PG_V12: PostgresVersion =
PostgresVersion("12.1.0-1");
pub const PG_V11: PostgresVersion =
PostgresVersion("11.6.0-1");
pub const PG_V10: PostgresVersion =
PostgresVersion("10.11.0-1");
pub const PG_V9: PostgresVersion =
PostgresVersion("9.6.16-1");
pub struct FetchSettings {
pub host: String,
pub operating_system:
OperationSystem,
pub architecture: Architecture,
pub version: PostgresVersion,
}
impl Default for FetchSettings {
fn default() -> Self {
FetchSettings {
host: "https://repo1.maven.org".to_string(),
operating_system:
OperationSystem::Darwin,
architecture:
Architecture::Amd64,
version: PG_V13,
}
}
}
impl FetchSettings {
fn platform(&self) -> String {
let os = self
.operating_system
.to_string();
let arch =
if self.operating_system == OperationSystem::AlpineLinux {
format!("{}-{}", self.architecture.to_string(), "alpine")
} else { self.architecture.to_string() };
format!("{}-{}", os, arch)
}
}
pub async fn fetch_postgres(
settings: &FetchSettings, executable_path: &PathBuf,
) -> Result<String, PgEmbedError>
{
let platform =
settings.platform();
let file_name = format!(
"{}-{}.zip",
platform,
&settings.version.0
);
let mut file_path = executable_path.clone();
file_path.push(&file_name);
if !file_path.exists() {
let download_url = format!(
"{}/maven2/io/zonky/test/postgres/embedded-postgres-binaries-{}/{}/embedded-postgres-binaries-{}-{}.jar",
&settings.host,
&platform,
&settings.version.0,
&platform,
&settings.version.0);
let mut response: Response =
reqwest::get(download_url).map_err(|e|
{ PgEmbedError::DownloadFailure(e) })
.await?;
tokio::fs::create_dir_all(
executable_path,
).map_err(|e| PgEmbedError::DirCreationError(e))
.await?;
let mut file: tokio::fs::File =
tokio::fs::File::create(file_path.as_path()).map_err(|e| PgEmbedError::WriteFileError(e)).await?;
let content = response.bytes().map_err(|e| PgEmbedError::ConversionFailure(e))
.await?;
file.write_all(&content).map_err(|e| PgEmbedError::WriteFileError(e))
.await?;
}
Ok(file_name)
}
fn unzip_txz(file_path: &PathBuf, executables_path: &PathBuf) -> Result<PathBuf, PgEmbedError> {
let path = std::path::Path::new(
&file_path,
);
let mut zip =
archiver_rs::Zip::open(&path).map_err(|e| PgEmbedError::ReadFileError(e))?;
let file_name = zip.files().map_err(|e| PgEmbedError::UnpackFailure(e))?
.into_iter()
.find(|name| {
name.ends_with(".txz")
});
match file_name {
Some(file_name) => {
let mut target_path = executables_path.clone();
target_path.push(&file_name);
zip.extract_single(
&target_path.as_path(),
file_name.clone(),
).map_err(|e| PgEmbedError::UnpackFailure(e))?;
Ok(target_path)
}
None => { Err(PgEmbedError::InvalidPgPackage("no postgresql txz in zip".to_string())) }
}
}
fn decompress_xz(file_path: &PathBuf) -> Result<PathBuf, PgEmbedError> {
let mut xz =
archiver_rs::Xz::open(
file_path.as_path(),
).map_err(|e| PgEmbedError::ReadFileError(e))?;
let target_path = file_path.with_extension(".tar");
xz.decompress(&target_path.as_path()).map_err(|e| PgEmbedError::UnpackFailure(e))?;
Ok(target_path)
}
fn decompress_tar(file_path: &PathBuf, executables_path: &PathBuf) -> Result<(), PgEmbedError> {
let mut tar =
archiver_rs::Tar::open(
&file_path.as_path(),
).map_err(|e| PgEmbedError::ReadFileError(e))?;
tar.extract(executables_path.as_path()).map_err(|e| PgEmbedError::UnpackFailure(e))?;
Ok(())
}
pub async fn unpack_postgres(
file_name: &str, executables_path: &PathBuf,
) -> Result<(), PgEmbedError> {
let mut file_path = executables_path.clone();
file_path.push(file_name);
let txz_file_path = unzip_txz(&file_path, &executables_path)?;
let tar_file_path = decompress_xz(&txz_file_path)?;
tokio::fs::remove_file(txz_file_path).map_err(|e| PgEmbedError::PgCleanUpFailure(e)).await?;
decompress_tar(&tar_file_path, &executables_path);
tokio::fs::remove_file(tar_file_path).map_err(|e| PgEmbedError::PgCleanUpFailure(e)).await?;
Ok(())
}