From 7f7339ab6d3d8a5aba7f8ba44997589ffd50fc94 Mon Sep 17 00:00:00 2001 From: Rabindra Dhakal Date: Fri, 11 Oct 2024 19:52:20 +0545 Subject: [PATCH] feat(download): introduce ability to download arbitrary files --- src/cli.rs | 9 ++++++ src/lib.rs | 5 +++ src/misc/download.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++ src/misc/mod.rs | 1 + 4 files changed, 91 insertions(+) create mode 100644 src/misc/download.rs create mode 100644 src/misc/mod.rs diff --git a/src/cli.rs b/src/cli.rs index 396984b..26a53e2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -109,4 +109,13 @@ pub enum Commands { #[arg(required = true)] package: String, }, + + /// Download arbitrary files + #[command(arg_required_else_help = true)] + #[clap(name = "download", visible_alias = "dl")] + Download { + /// Links to files + #[arg(required = true)] + links: Vec, + }, } diff --git a/src/lib.rs b/src/lib.rs index ea1909c..ce1760d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use anyhow::Result; use clap::Parser; use cli::{Args, Commands}; +use misc::download::download_and_save; use registry::PackageRegistry; use core::{ @@ -10,6 +11,7 @@ use core::{ mod cli; mod core; +mod misc; mod registry; pub async fn init() -> Result<()> { @@ -54,6 +56,9 @@ pub async fn init() -> Result<()> { Commands::Use { package } => { registry.use_package(&package).await?; } + Commands::Download { links } => { + download_and_save(links.as_ref()).await?; + } }; Ok(()) diff --git a/src/misc/download.rs b/src/misc/download.rs new file mode 100644 index 0000000..7824850 --- /dev/null +++ b/src/misc/download.rs @@ -0,0 +1,76 @@ +use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::Path}; + +use anyhow::{Context, Result}; +use chrono::Utc; +use futures::StreamExt; +use reqwest::Url; +use tokio::fs; + +use crate::core::util::format_bytes; + +fn extract_filename(url: &str) -> String { + Path::new(url) + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_else(|| { + let dt = Utc::now().timestamp(); + dt.to_string() + }) +} + +fn is_elf(content: &[u8]) -> bool { + let magic_bytes = &content[..4.min(content.len())]; + let elf_bytes = [0x7f, 0x45, 0x4c, 0x46]; + magic_bytes == elf_bytes +} + +async fn download(url: &str) -> Result<()> { + let client = reqwest::Client::new(); + let response = client.get(url).send().await?; + + if !response.status().is_success() { + return Err(anyhow::anyhow!( + "Error fetching {} [{}]", + url, + response.status() + )); + } + + let filename = extract_filename(url); + let mut content = Vec::new(); + + println!( + "Downloading file from {} [{}]", + url, + format_bytes(response.content_length().unwrap_or_default()) + ); + + let mut stream = response.bytes_stream(); + + while let Some(chunk) = stream.next().await { + let chunk = chunk.context("Failed to read chunk")?; + content.extend_from_slice(&chunk); + } + + fs::write(&filename, &content).await?; + + if is_elf(&content) { + fs::set_permissions(&filename, Permissions::from_mode(0o755)).await?; + } + + println!("Downloaded {}", filename); + + Ok(()) +} + +pub async fn download_and_save(links: &[String]) -> Result<()> { + for link in links { + if let Ok(url) = Url::parse(link) { + download(url.as_str()).await?; + } else { + eprintln!("{} is not a valid URL", link); + }; + } + + Ok(()) +} diff --git a/src/misc/mod.rs b/src/misc/mod.rs new file mode 100644 index 0000000..674b799 --- /dev/null +++ b/src/misc/mod.rs @@ -0,0 +1 @@ +pub mod download;