use async_trait::async_trait;
use dprint_cli_core::types::ErrBox;
use dprint_cli_core::{download_url, log_action_with_progress, ProgressBars};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use super::Environment;
#[cfg(unix)]
use super::unix as sys_impl;
#[cfg(target_os = "windows")]
use super::windows as sys_impl;
#[derive(Clone)]
pub struct RealEnvironment {
output_lock: Arc<Mutex<u8>>,
progress_bars: Arc<ProgressBars>,
is_verbose: bool,
}
impl RealEnvironment {
pub fn new(is_verbose: bool) -> RealEnvironment {
RealEnvironment {
output_lock: Arc::new(Mutex::new(0)),
progress_bars: Arc::new(ProgressBars::new()),
is_verbose,
}
}
}
#[async_trait]
impl Environment for RealEnvironment {
fn is_real(&self) -> bool {
true
}
fn read_file_text(&self, file_path: &Path) -> Result<String, ErrBox> {
let file_bytes = self.read_file(file_path)?;
Ok(String::from_utf8(file_bytes)?)
}
fn read_file(&self, file_path: &Path) -> Result<Vec<u8>, ErrBox> {
log_verbose!(self, "Reading file: {}", file_path.display());
Ok(fs::read(file_path)?)
}
fn write_file_text(&self, file_path: &Path, file_text: &str) -> Result<(), ErrBox> {
self.write_file(file_path, file_text.as_bytes())
}
fn write_file(&self, file_path: &Path, bytes: &[u8]) -> Result<(), ErrBox> {
log_verbose!(self, "Writing file: {}", file_path.display());
match fs::write(file_path, bytes) {
Ok(_) => Ok(()),
Err(err) => err!("Error writing file {}: {}", file_path.display(), err.to_string()),
}
}
fn remove_file(&self, file_path: &Path) -> Result<(), ErrBox> {
log_verbose!(self, "Deleting file: {}", file_path.display());
fs::remove_file(file_path)?;
Ok(())
}
fn remove_dir_all(&self, dir_path: &Path) -> Result<(), ErrBox> {
log_verbose!(self, "Deleting directory: {}", dir_path.display());
fs::remove_dir_all(dir_path)?;
Ok(())
}
async fn download_file(&self, url: &str) -> Result<Vec<u8>, ErrBox> {
log_verbose!(self, "Downloading url: {}", url);
download_url(url, &self.progress_bars).await
}
fn path_exists(&self, file_path: &Path) -> bool {
log_verbose!(self, "Checking path exists: {}", file_path.display());
file_path.exists()
}
fn is_dir_empty(&self, dir_path: &Path) -> Result<bool, ErrBox> {
Ok(std::fs::read_dir(dir_path)?.next().is_none())
}
fn create_dir_all(&self, path: &Path) -> Result<(), ErrBox> {
fs::create_dir_all(path)?;
Ok(())
}
fn cwd(&self) -> Result<PathBuf, ErrBox> {
Ok(std::env::current_dir()?)
}
fn log(&self, text: &str) {
let _g = self.output_lock.lock().unwrap();
println!("{}", text);
}
fn log_error(&self, text: &str) {
let _g = self.output_lock.lock().unwrap();
eprintln!("{}", text);
}
async fn log_action_with_progress<
TResult: std::marker::Send + std::marker::Sync,
TCreate: FnOnce(Box<dyn Fn(usize)>) -> TResult + std::marker::Send + std::marker::Sync,
>(
&self,
message: &str,
action: TCreate,
total_size: usize,
) -> Result<TResult, ErrBox> {
log_action_with_progress(&self.progress_bars, message, action, total_size).await
}
fn get_local_user_data_dir(&self) -> Result<PathBuf, ErrBox> {
log_verbose!(self, "Getting local user data directory.");
let bvm_dir = if cfg!(target_os = "windows") {
let dir = dirs::data_local_dir().ok_or_else(|| err_obj!("Could not get local data dir"))?;
dir.join("bvm")
} else {
get_home_dir()?
};
self.create_dir_all(&bvm_dir)?;
Ok(bvm_dir)
}
fn get_user_data_dir(&self) -> Result<PathBuf, ErrBox> {
log_verbose!(self, "Getting user data directory.");
let bvm_dir = if cfg!(target_os = "windows") {
let dir = dirs::data_dir().ok_or_else(|| err_obj!("Could not get data dir"))?;
dir.join("bvm")
} else {
get_home_dir()?
};
self.create_dir_all(&bvm_dir)?;
Ok(bvm_dir)
}
fn get_system_path_dirs(&self) -> Vec<PathBuf> {
log_verbose!(self, "Getting system path directories.");
super::common::get_system_path_dirs()
}
fn ensure_system_path(&self, directory_path: &Path) -> Result<(), ErrBox> {
log_verbose!(self, "Ensuring '{}' is on the path.", directory_path.display());
sys_impl::ensure_system_path(&directory_path)?;
Ok(())
}
fn get_time_secs(&self) -> u64 {
SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn run_shell_command(&self, cwd: &Path, command: &str) -> Result<(), ErrBox> {
#[cfg(unix)]
return finalize_and_run_command(cwd, Command::new("/bin/sh").arg("-c").arg(command));
#[cfg(target_os = "windows")]
return finalize_and_run_command(cwd, Command::new("cmd").arg("/C").arg(command));
fn finalize_and_run_command(cwd: &Path, command: &mut Command) -> Result<(), ErrBox> {
let status = command
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.current_dir(cwd)
.status()?;
handle_exit_status(status)
}
fn handle_exit_status(exit_status: ExitStatus) -> Result<(), ErrBox> {
match exit_status.code() {
Some(code) => {
if code != 0 {
return err!("Received non zero exit code from shell command: {}", code);
} else {
Ok(())
}
}
None => err!("Process terminated by signal."),
}
}
}
fn is_verbose(&self) -> bool {
self.is_verbose
}
}
fn get_home_dir() -> Result<PathBuf, ErrBox> {
let dir = dirs::home_dir().ok_or_else(|| err_obj!("Could not get home data dir"))?;
Ok(dir.join(".bvm"))
}