diff --git a/Cargo.lock b/Cargo.lock index ed095b5..cf70c62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,6 +874,15 @@ dependencies = [ "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]] name = "slab" version = "0.4.6" @@ -1017,6 +1026,7 @@ dependencies = [ "mio", "once_cell", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "winapi", diff --git a/Cargo.toml b/Cargo.toml index 6b3f314..427879a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,6 @@ ramhorns = "0.14" reqwest = { version = "0.11.10", features = [ "json" ] } serde = { version = "1", features = [ "derive" ] } serde_json = "1" -tokio = { version = "1.19", features = [ "fs", "macros", "rt" ] } +tokio = { version = "1.19", features = [ "fs", "macros", "process", "rt" ] } url = "2.2" diff --git a/src/install.rs b/src/install.rs index 0406ddb..88e721c 100644 --- a/src/install.rs +++ b/src/install.rs @@ -16,7 +16,10 @@ pub struct Pipeline<'a> { impl<'a> Pipeline<'a> { #[inline(always)] pub fn new(name: &'a str, steps: impl Into>>) -> Self { - Self { name, steps: steps.into() } + Self { + name, + steps: steps.into(), + } } pub async fn invoke(&self, ctx: &Context) -> Result<()> { @@ -69,7 +72,10 @@ impl<'a> Step<'a> { pub async fn invoke(&self, ctx: &Context) -> Result<()> { match self { Self::DownloadFile { res, file } => { - println!("Downloading file {file} from {res:?}...", file = file.to_str().unwrap_or("")); + println!( + "Downloading file {file} from {res:?}...", + file = file.to_str().unwrap_or("") + ); const FETCH_FILE_ERROR_MSG: &'static str = "Fetching the remote resource failed."; const WRITE_FILE_ERROR_MSG: &'static str = "Writing the remote resource to disk failed."; @@ -85,7 +91,9 @@ impl<'a> Step<'a> { )) .into_diagnostic() .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() .await .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() .await .into_diagnostic() .wrap_err(FETCH_FILE_ERROR_MSG)?; 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( - tokio::fs::File::create(file) + tokio::fs::File::create(file.as_os_str()) .await .into_diagnostic() .wrap_err(WRITE_FILE_ERROR_MSG)?, @@ -143,14 +158,43 @@ impl<'a> Step<'a> { .wrap_err(WRITE_FILE_ERROR_MSG)?; } Self::ExtractFile { file, dest } => { - mkdir_all(dest).await?; - todo!(); + const EXTRACT_FILE_ERROR_MSG: &'static str = "Extracting file failed."; + 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 } => { - 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 } => { - 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!(); } } @@ -247,5 +291,10 @@ const fn empty_str<'a>() -> &'a str { } async fn mkdir_all(path: impl AsRef) -> 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.") } diff --git a/src/main.rs b/src/main.rs index 59149d6..a6a5cd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,24 +34,58 @@ async fn main() -> Result<()> { bail!("Could not find or access {}", SWTOOLS_PATH); } - let utilities = [Pipeline::new( - "Install Notepad++", - vec![ - Step::DownloadFile { - file: swtools_path() - .join("temp") - .join("notepad-plus-plus.zip") - .into(), - res: RemoteResource::GitHubArtifact { - repo: "notepad-plus-plus/notepad-plus-plus", - pattern: "npp.{{tag_name_strip_prefix}}.portable.x64.zip", + let nppp_zip = swtools_path().join("temp").join("notepad-plus-plus.zip"); + let epp_zip = swtools_path().join("temp").join("explorer-plus-plus.zip"); + + let utilities = [ + Pipeline::new( + "Install Notepad++", + vec![ + Step::DownloadFile { + file: nppp_zip.as_path().into(), + res: RemoteResource::GitHubArtifact { + 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 { - 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 {