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 struct Config {
|
||||||
pub dist_base_url: String,
|
pub dist_base_url: String,
|
||||||
pub default_version: 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 {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
dist_base_url: String::from("https://nodejs.org/dist"),
|
dist_base_url: String::from(NODE_DIST_URL),
|
||||||
default_version: String::from("latest"),
|
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