diff --git a/Cargo.lock b/Cargo.lock index b35f783..5a4f95b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,6 +842,7 @@ name = "school-computer-toolkit" version = "0.1.0" dependencies = [ "dirs", + "futures-util", "miette", "ramhorns", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 5569dc3..d744e04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] dirs = "4" +futures-util = { version = "0.3.21", default-features = false, features = [ "alloc" ] } miette = { version = "4.7", features = [ "fancy" ] } ramhorns = "0.14" reqwest = { version = "0.11.10", features = [ "json" ] } diff --git a/src/install.rs b/src/install.rs index 91ff8f1..456f719 100644 --- a/src/install.rs +++ b/src/install.rs @@ -6,9 +6,6 @@ use tokio::io::AsyncWriteExt; use crate::Context; -const USER_AGENT_STR: &'static str = - "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; - /// A sequential pipeline of [`Step`]s. #[derive(Debug, Clone)] pub struct Pipeline<'a> { @@ -114,7 +111,6 @@ impl<'a> Step<'a> { let mut resp = ctx .reqwest .get(url) - .header("User-Agent", USER_AGENT_STR) .send() .await .into_diagnostic() @@ -344,7 +340,6 @@ async fn fetch_latest_release<'a, 'b>( .wrap_err("Invalid GitHub repo for download step.")?; let mut resp = reqwest .get(url) - .header("User-Agent", USER_AGENT_STR) .send() .await .into_diagnostic() diff --git a/src/main.rs b/src/main.rs index 9a1aa2f..e2cc5d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,20 +14,26 @@ use install::{Pipeline, RemoteResource, ShortcutTarget, Step}; const SWTOOLS_PATH: &'static str = "C:\\SWTools"; +const USER_AGENT_STR: &'static str = + "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; + pub fn swtools_path() -> &'static Path { Path::new(SWTOOLS_PATH) } -pub fn desktop_path() -> PathBuf { - let mut dir = dirs::home_dir() - .expect("Could not find your home directory.") - .join("Desktop"); - if !dir.exists() { - dir = Path::new("H:\\Profile\\Desktop").to_owned(); - } - dir +pub fn desktop_path_fallible() -> Option { + dirs::desktop_dir() + .filter(|dir| dir.exists()) + .or_else(|| Some(Path::new("H:\\Profile\\Desktop").to_owned())) + .filter(|dir| dir.exists()) + .or_else(|| dirs::home_dir().map(|dir| dir.join("Desktop"))) } +pub fn desktop_path() -> PathBuf { + desktop_path_fallible().expect("Desktop directory should have been resolved by now.") +} + +#[derive(Clone)] pub struct Context { pub reqwest: reqwest::Client, } @@ -44,7 +50,7 @@ async fn main() -> Result<()> { bail!("Could not find or access {}", SWTOOLS_PATH); } - if !desktop_path().exists() { + if desktop_path_fallible().is_none() { bail!("Could not find your desktop directory."); } @@ -59,20 +65,20 @@ async fn main() -> Result<()> { let psiphon_dir = swtools_path().join("psiphon"); let psiphon_bin = psiphon_dir.join("psiphon3.exe"); - let utilities = [ + let pipelines = [ Pipeline::new( "Install Notepad++", vec![ Step::DownloadFile { - file: nppp_zip.as_path().into(), + file: nppp_zip.clone().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: nppp_dir.as_path().into(), + file: nppp_zip.clone().into(), + dest: nppp_dir.clone().into(), }, Step::CreateShortcut { target: ShortcutTarget::Executable { @@ -88,14 +94,14 @@ async fn main() -> Result<()> { "Install Explorer++", vec![ Step::DownloadFile { - file: epp_zip.as_path().into(), + file: epp_zip.clone().into(), res: RemoteResource::Url( "https://explorerplusplus.com/software/explorer++_1.3.5_x64.zip", ), }, Step::ExtractFile { - file: epp_zip.as_path().into(), - dest: epp_dir.as_path().into(), + file: epp_zip.clone().into(), + dest: epp_dir.clone().into(), }, Step::CreateShortcut { target: ShortcutTarget::Executable { @@ -111,14 +117,14 @@ async fn main() -> Result<()> { "Install Psiphon VPN", vec![ Step::DownloadFile { - file: psiphon_bin.as_path().into(), + file: psiphon_bin.clone().into(), res: RemoteResource::Url( "https://s3.amazonaws.com/f58p-mqce-k1yj/psiphon3.exe", ), }, Step::CreateShortcut { target: ShortcutTarget::Executable { - file: psiphon_bin.as_path().into(), + file: psiphon_bin.clone().into(), args: "", }, file: desktop_path().join("Psiphon3.lnk").into(), @@ -166,14 +172,42 @@ async fn main() -> Result<()> { let ctx = Context { reqwest: reqwest::Client::builder() + .user_agent(USER_AGENT_STR) .build() .into_diagnostic() .wrap_err("Could not initialize HTTP client.")?, }; - for utility in utilities { - utility.invoke(&ctx).await?; + let results = futures_util::future::join_all( + pipelines + .into_iter() + .map(|pipeline| tokio::spawn({ + let ctx = ctx.clone(); + async move { + pipeline.invoke(&ctx).await + } + })), + ) + .await; + let results = results + .into_iter() + .map(|result| match result.into_diagnostic().wrap_err("Task error.") { + Ok(Ok(t)) => Ok(t), + Ok(Err(e)) => Err(e), + Err(e) => Err(e), + }); + + let mut had_error = false; + for result in results { + if let Err(e) = result { + had_error = true; + eprintln!("{e:?}"); + } } - Ok(()) + if had_error { + Err(miette!("One or more errors in pipelines.")) + } else { + Ok(()) + } }