Add Explorer++ and Minecraft installers and implement ExtractFile and ExecuteCommand

This commit is contained in:
Michael Pfaff 2022-06-09 18:29:58 -04:00
parent 35a3451061
commit d962fc5870
Signed by: michael
GPG Key ID: CF402C4A012AA9D4
4 changed files with 120 additions and 27 deletions

10
Cargo.lock generated
View File

@ -874,6 +874,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.6" version = "0.4.6"
@ -1017,6 +1026,7 @@ dependencies = [
"mio", "mio",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi", "winapi",

View File

@ -11,6 +11,6 @@ ramhorns = "0.14"
reqwest = { version = "0.11.10", features = [ "json" ] } reqwest = { version = "0.11.10", features = [ "json" ] }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
serde_json = "1" serde_json = "1"
tokio = { version = "1.19", features = [ "fs", "macros", "rt" ] } tokio = { version = "1.19", features = [ "fs", "macros", "process", "rt" ] }
url = "2.2" url = "2.2"

View File

@ -16,7 +16,10 @@ pub struct Pipeline<'a> {
impl<'a> Pipeline<'a> { impl<'a> Pipeline<'a> {
#[inline(always)] #[inline(always)]
pub fn new(name: &'a str, steps: impl Into<Vec<Step<'a>>>) -> Self { pub fn new(name: &'a str, steps: impl Into<Vec<Step<'a>>>) -> Self {
Self { name, steps: steps.into() } Self {
name,
steps: steps.into(),
}
} }
pub async fn invoke(&self, ctx: &Context) -> Result<()> { pub async fn invoke(&self, ctx: &Context) -> Result<()> {
@ -69,7 +72,10 @@ impl<'a> Step<'a> {
pub async fn invoke(&self, ctx: &Context) -> Result<()> { pub async fn invoke(&self, ctx: &Context) -> Result<()> {
match self { match self {
Self::DownloadFile { res, file } => { Self::DownloadFile { res, file } => {
println!("Downloading file {file} from {res:?}...", file = file.to_str().unwrap_or("<NON UTF-8>")); println!(
"Downloading file {file} from {res:?}...",
file = file.to_str().unwrap_or("<NON UTF-8>")
);
const FETCH_FILE_ERROR_MSG: &'static str = "Fetching the remote resource failed."; const FETCH_FILE_ERROR_MSG: &'static str = "Fetching the remote resource failed.";
const WRITE_FILE_ERROR_MSG: &'static str = const WRITE_FILE_ERROR_MSG: &'static str =
"Writing the remote resource to disk failed."; "Writing the remote resource to disk failed.";
@ -85,7 +91,9 @@ impl<'a> Step<'a> {
)) ))
.into_diagnostic() .into_diagnostic()
.wrap_err("Invalid GitHub repo for download step.")?; .wrap_err("Invalid GitHub repo for download step.")?;
let mut release: GitHubRelease = ctx.reqwest.get(url) let mut release: GitHubRelease = ctx
.reqwest
.get(url)
.send() .send()
.await .await
.into_diagnostic() .into_diagnostic()
@ -111,15 +119,22 @@ impl<'a> Step<'a> {
)? )?
} }
}; };
let mut resp = ctx.reqwest.get(url) let mut resp = ctx
.reqwest
.get(url)
.send() .send()
.await .await
.into_diagnostic() .into_diagnostic()
.wrap_err(FETCH_FILE_ERROR_MSG)?; .wrap_err(FETCH_FILE_ERROR_MSG)?;
let _content_length = resp.content_length(); let _content_length = resp.content_length();
mkdir_all(file.parent().ok_or_else(|| miette!("Destination file for download step has no parent."))?).await?; mkdir_all(
file.parent().ok_or_else(|| {
miette!("Destination file for download step has no parent.")
})?,
)
.await?;
let mut writer = tokio::io::BufWriter::new( let mut writer = tokio::io::BufWriter::new(
tokio::fs::File::create(file) tokio::fs::File::create(file.as_os_str())
.await .await
.into_diagnostic() .into_diagnostic()
.wrap_err(WRITE_FILE_ERROR_MSG)?, .wrap_err(WRITE_FILE_ERROR_MSG)?,
@ -143,14 +158,43 @@ impl<'a> Step<'a> {
.wrap_err(WRITE_FILE_ERROR_MSG)?; .wrap_err(WRITE_FILE_ERROR_MSG)?;
} }
Self::ExtractFile { file, dest } => { Self::ExtractFile { file, dest } => {
mkdir_all(dest).await?; const EXTRACT_FILE_ERROR_MSG: &'static str = "Extracting file failed.";
todo!(); let dest = tokio::fs::canonicalize(&dest)
.await
.into_diagnostic()
.wrap_err(EXTRACT_FILE_ERROR_MSG)?;
mkdir_all(&dest).await.wrap_err(EXTRACT_FILE_ERROR_MSG)?;
let status = tokio::process::Command::new("tar")
.arg("-xf")
.arg(file.as_os_str())
.current_dir(dest)
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.status()
.await
.into_diagnostic()
.wrap_err(EXTRACT_FILE_ERROR_MSG)?;
ensure!(status.success(), EXTRACT_FILE_ERROR_MSG);
} }
Self::ExecuteCommand { file, args } => { Self::ExecuteCommand { file, args } => {
todo!(); const EXECUTE_COMMAND_ERROR_MSG: &'static str = "Executing command failed.";
let status = tokio::process::Command::new(file.as_os_str())
.args(*args)
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.status()
.await
.into_diagnostic()
.wrap_err(EXECUTE_COMMAND_ERROR_MSG)?;
ensure!(status.success(), EXECUTE_COMMAND_ERROR_MSG);
} }
Self::CreateShortcut { target, icon, file } => { Self::CreateShortcut { target, icon, file } => {
mkdir_all(file.parent().ok_or_else(|| miette!("Destination file for shortcut step has no parent."))?).await?; mkdir_all(
file.parent().ok_or_else(|| {
miette!("Destination file for shortcut step has no parent.")
})?,
)
.await?;
todo!(); todo!();
} }
} }
@ -247,5 +291,10 @@ const fn empty_str<'a>() -> &'a str {
} }
async fn mkdir_all(path: impl AsRef<Path>) -> Result<()> { async fn mkdir_all(path: impl AsRef<Path>) -> Result<()> {
tokio::fs::DirBuilder::new().recursive(true).create(path).await.into_diagnostic().wrap_err("Creating directory and any missing parents failed.") tokio::fs::DirBuilder::new()
.recursive(true)
.create(path)
.await
.into_diagnostic()
.wrap_err("Creating directory and any missing parents failed.")
} }

View File

@ -34,24 +34,58 @@ async fn main() -> Result<()> {
bail!("Could not find or access {}", SWTOOLS_PATH); bail!("Could not find or access {}", SWTOOLS_PATH);
} }
let utilities = [Pipeline::new( let nppp_zip = swtools_path().join("temp").join("notepad-plus-plus.zip");
"Install Notepad++", let epp_zip = swtools_path().join("temp").join("explorer-plus-plus.zip");
vec![
Step::DownloadFile { let utilities = [
file: swtools_path() Pipeline::new(
.join("temp") "Install Notepad++",
.join("notepad-plus-plus.zip") vec![
.into(), Step::DownloadFile {
res: RemoteResource::GitHubArtifact { file: nppp_zip.as_path().into(),
repo: "notepad-plus-plus/notepad-plus-plus", res: RemoteResource::GitHubArtifact {
pattern: "npp.{{tag_name_strip_prefix}}.portable.x64.zip", repo: "notepad-plus-plus/notepad-plus-plus",
pattern: "npp.{{tag_name_strip_prefix}}.portable.x64.zip",
},
}, },
}, Step::ExtractFile {
], file: nppp_zip.as_path().into(),
)]; dest: swtools_path().join("notepad-plus-plus").into(),
},
],
),
Pipeline::new(
"Install Explorer++",
vec![
Step::DownloadFile {
file: epp_zip.as_path().into(),
res: RemoteResource::Url(
"https://explorerplusplus.com/software/explorer++_1.3.5_x64.zip",
),
},
Step::ExtractFile {
file: epp_zip.as_path().into(),
dest: swtools_path().join("explorer-plus-plus").into(),
},
],
),
Pipeline::new(
"Install Minecraft (Java Edition)",
vec![Step::DownloadFile {
file: swtools_path()
.join("minecraft")
.join("Minecraft.exe")
.into(),
res: RemoteResource::Url("https://launcher.mojang.com/download/Minecraft.exe"),
}],
),
];
let ctx = Context { let ctx = Context {
reqwest: reqwest::Client::builder().build().into_diagnostic().wrap_err("Could not initialize HTTP client.")?, reqwest: reqwest::Client::builder()
.build()
.into_diagnostic()
.wrap_err("Could not initialize HTTP client.")?,
}; };
for utility in utilities { for utility in utilities {