mirror of https://github.com/Trivernis/nenv
Add archive extracting after download
parent
5238fb57e0
commit
a39df138c9
@ -1,13 +1,64 @@
|
||||
use std::io;
|
||||
|
||||
use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::consts::{CFG_FILE_PATH, NODE_DIST_URL};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Config {
|
||||
pub dist_base_url: String,
|
||||
pub default_version: String,
|
||||
}
|
||||
|
||||
pub type ConfigResult<T> = Result<T, ConfigError>;
|
||||
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
pub enum ConfigError {
|
||||
#[error("IO Error: {0}")]
|
||||
Io(
|
||||
#[from]
|
||||
#[source]
|
||||
io::Error,
|
||||
),
|
||||
#[error("Failed to parse config file: {0}")]
|
||||
Parse(
|
||||
#[from]
|
||||
#[source]
|
||||
toml::de::Error,
|
||||
),
|
||||
#[error("Failed to serialize config file: {0}")]
|
||||
Serialize(
|
||||
#[from]
|
||||
#[source]
|
||||
toml::ser::Error,
|
||||
),
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dist_base_url: String::from("https://nodejs.org/dist"),
|
||||
dist_base_url: String::from(NODE_DIST_URL),
|
||||
default_version: String::from("latest"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Loads the config file from the default config path
|
||||
pub async fn load() -> ConfigResult<Self> {
|
||||
if !CFG_FILE_PATH.exists() {
|
||||
let cfg = Config::default();
|
||||
fs::write(&*CFG_FILE_PATH, toml::to_string_pretty(&cfg)?).await?;
|
||||
|
||||
Ok(cfg)
|
||||
} else {
|
||||
let cfg_string = fs::read_to_string(&*CFG_FILE_PATH).await?;
|
||||
let cfg = toml::from_str(&cfg_string)?;
|
||||
|
||||
Ok(cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{self, BufReader},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use libflate::gzip::Decoder;
|
||||
use miette::Diagnostic;
|
||||
use tar::Archive;
|
||||
use thiserror::Error;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::utils::{progress_bar, progress_spinner};
|
||||
type ExtractResult<T> = Result<T, ExtractError>;
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
pub enum ExtractError {
|
||||
#[error("IO error when extracting: {0}")]
|
||||
Io(
|
||||
#[from]
|
||||
#[source]
|
||||
io::Error,
|
||||
),
|
||||
#[error("Failed to extract zip: {0}")]
|
||||
Zip(
|
||||
#[from]
|
||||
#[source]
|
||||
zip::result::ZipError,
|
||||
),
|
||||
}
|
||||
|
||||
pub fn extract_file(src: &Path, dst: &Path) -> ExtractResult<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
extract_zip(src, dst)?;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
extract_tar_gz(src, dst)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_tar_gz(src: &Path, dst: &Path) -> ExtractResult<()> {
|
||||
let mut reader = BufReader::new(File::open(src)?);
|
||||
let mut decoder = Decoder::new(reader)?;
|
||||
let mut archive = Archive::new(decoder);
|
||||
let pb = progress_spinner();
|
||||
pb.set_message("Extracting tar.gz archive");
|
||||
|
||||
archive.unpack(dst)?;
|
||||
pb.finish_with_message("Archive extracted.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_zip(src: &Path, dst: &Path) -> ExtractResult<()> {
|
||||
let mut archive = ZipArchive::new(File::open(src)?)?;
|
||||
|
||||
let pb = progress_bar(archive.len() as u64);
|
||||
pb.set_message("Extracting zip archive");
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
let Some(path) = file.enclosed_name() else {
|
||||
tracing::error!(
|
||||
"Cannot extract {:?} because it has an invalid name",
|
||||
file.name()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let output_path = dst.join(path);
|
||||
if (*file.name()).ends_with('/') {
|
||||
tracing::debug!("Creating directory {output_path:?}");
|
||||
fs::create_dir_all(output_path)?;
|
||||
} else {
|
||||
if let Some(parent) = output_path.parent() {
|
||||
if !parent.exists() {
|
||||
tracing::debug!("Creating parent directory {parent:?}");
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
}
|
||||
let mut file_output = File::create(&output_path)?;
|
||||
tracing::debug!("Extracting to {output_path:?}");
|
||||
io::copy(&mut file, &mut file_output)?;
|
||||
}
|
||||
pb.tick()
|
||||
}
|
||||
pb.finish_with_message("Archive extracted.");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
|
||||
pub fn progress_bar(total: u64) -> ProgressBar {
|
||||
let pb = ProgressBar::new(total);
|
||||
pb.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template(
|
||||
"{msg} {spinner}\n[{wide_bar}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
pb.enable_steady_tick(Duration::from_millis(50));
|
||||
pb
|
||||
}
|
||||
|
||||
pub fn progress_spinner() -> ProgressBar {
|
||||
let pb = ProgressBar::new_spinner();
|
||||
pb.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("{msg} {spinner}")
|
||||
.unwrap(),
|
||||
);
|
||||
pb.enable_steady_tick(Duration::from_millis(50));
|
||||
pb
|
||||
}
|
Loading…
Reference in New Issue