diff options
Diffstat (limited to 'zellij-utils/src/downloader/mod.rs')
-rw-r--r-- | zellij-utils/src/downloader/mod.rs | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/zellij-utils/src/downloader/mod.rs b/zellij-utils/src/downloader/mod.rs new file mode 100644 index 000000000..b0b2771dd --- /dev/null +++ b/zellij-utils/src/downloader/mod.rs @@ -0,0 +1,147 @@ +pub mod download; + +use async_std::{ + fs::{create_dir_all, File}, + io::{ReadExt, WriteExt}, + stream, task, +}; +use futures::{StreamExt, TryStreamExt}; +use std::path::PathBuf; +use surf::Client; +use thiserror::Error; + +use self::download::Download; + +#[derive(Error, Debug)] +pub enum DownloaderError { + #[error("RequestError: {0}")] + Request(surf::Error), + #[error("StatusError: {0}, StatusCode: {1}")] + Status(String, surf::StatusCode), + #[error("IoError: {0}")] + Io(#[source] std::io::Error), + #[error("IoPathError: {0}, File: {1}")] + IoPath(std::io::Error, PathBuf), +} + +#[derive(Default, Debug)] +pub struct Downloader { + client: Client, + directory: PathBuf, +} + +impl Downloader { + pub fn new(directory: PathBuf) -> Self { + Self { + client: surf::client().with(surf::middleware::Redirect::default()), + directory, + } + } + + pub fn set_directory(&mut self, directory: PathBuf) { + self.directory = directory; + } + + pub fn download(&self, downloads: &[Download]) -> Vec<Result<(), DownloaderError>> { + task::block_on(async { + stream::from_iter(downloads) + .map(|download| self.fetch(download)) + .buffer_unordered(4) + .collect::<Vec<_>>() + .await + }) + } + + pub async fn fetch(&self, download: &Download) -> Result<(), DownloaderError> { + let mut file_size: usize = 0; + + let file_path = self.directory.join(&download.file_name); + + if file_path.exists() { + file_size = match file_path.metadata() { + Ok(metadata) => metadata.len() as usize, + Err(e) => return Err(DownloaderError::IoPath(e, file_path)), + } + } + + let response = self + .client + .get(&download.url) + .await + .map_err(|e| DownloaderError::Request(e))?; + let status = response.status(); + + if status.is_client_error() || status.is_server_error() { + return Err(DownloaderError::Status( + status.canonical_reason().to_string(), + status, + )); + } + + let length = response.len().unwrap_or(0); + if length > 0 && length == file_size { + return Ok(()); + } + + let mut dest = { + create_dir_all(&self.directory) + .await + .map_err(|e| DownloaderError::IoPath(e, self.directory.clone()))?; + File::create(&file_path) + .await + .map_err(|e| DownloaderError::IoPath(e, file_path))? + }; + + let mut bytes = response.bytes(); + while let Some(byte) = bytes.try_next().await.map_err(DownloaderError::Io)? { + dest.write_all(&[byte]).await.map_err(DownloaderError::Io)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use tempfile::tempdir; + + #[test] + #[ignore] + fn test_fetch_plugin() { + let dir = tempdir().expect("could not get temp dir"); + let dir_path = dir.path(); + + let downloader = Downloader::new(dir_path.to_path_buf()); + let dl = Download::from( + "https://github.com/imsnif/monocle/releases/download/0.37.2/monocle.wasm", + ); + + let result = task::block_on(downloader.fetch(&dl)); + + assert!(result.is_ok()); + } + + #[test] + #[ignore] + fn test_download_plugins() { + let dir = tempdir().expect("could not get temp dir"); + let dir_path = dir.path(); + + let downloader = Downloader::new(dir_path.to_path_buf()); + let downloads = vec![ + Download::from( + "https://github.com/imsnif/monocle/releases/download/0.37.2/monocle.wasm", + ), + Download::from( + "https://github.com/imsnif/multitask/releases/download/0.38.2/multitask.wasm", + ), + ]; + + let results = downloader.download(&downloads); + for result in results { + assert!(result.is_ok()) + } + } +} |