From f8b9506be25332f8772fca44c1cf7106caeea3fe Mon Sep 17 00:00:00 2001 From: Namilskyy Date: Sun, 30 Nov 2025 17:16:54 +0300 Subject: Fixed all clippy warnings --- src/net/http_package.rs | 251 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 src/net/http_package.rs (limited to 'src/net/http_package.rs') diff --git a/src/net/http_package.rs b/src/net/http_package.rs new file mode 100644 index 0000000..ec7c318 --- /dev/null +++ b/src/net/http_package.rs @@ -0,0 +1,251 @@ +use crate::cfg::config::Config; +use crate::pkgtoolkit::pkgtools::Package; +use futures_util::stream::TryStreamExt; +use indicatif::{ProgressBar, ProgressStyle}; +use reqwest; +use serde::Deserialize; +use std::collections::HashMap; +use std::path::Path; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; + +pub struct HTTPPackage { + config: Config, + index_packages: Option>, +} + +#[derive(Deserialize, Debug)] +struct IndexData { + packages: Vec, +} + +impl HTTPPackage { + /// Creates a new HTTPPackage object with the given configuration. + /// + /// # Returns + /// + /// A new HTTPPackage object with the given configuration. + pub fn new(config: Config) -> Self { + HTTPPackage { + config, + index_packages: None, + } + } + + /// Downloads the INDEX.tar.gz file from the configured repository + /// and stores it in the configured cache directory with a progress bar. + /// + /// # Errors + /// + /// Returns an error if the request fails, if the response status is not successful, or if there's an issue while reading or writing the file. + pub async fn fetch_index_http(&mut self) -> Result> { + let repo_url_str = &self.config.repo.repo_url; + let cache_dir = &self.config.paths.cache_dir; + + let index_url = if repo_url_str.ends_with(".tar.gz") { + repo_url_str.clone() + } else { + format!("{}/INDEX.tar.gz", repo_url_str.trim_end_matches('/')) + }; + + let client = reqwest::Client::new(); + + // Make a HEAD request to get the content length for the progress bar + let head_response = client.head(&index_url).send().await?; + let content_length: u64 = head_response + .headers() + .get(reqwest::header::CONTENT_LENGTH) + .and_then(|ct_len| ct_len.to_str().ok()) + .and_then(|ct_len| ct_len.parse().ok()) + .unwrap_or(0); + + // Create progress bar + let pb = if content_length > 0 { + let pb = ProgressBar::new(content_length); + pb.set_style( + ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")? + .progress_chars("#>-"), + ); + pb + } else { + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .template("{spinner:.green} [{elapsed_precise}] Fetching INDEX.tar.gz...")?, + ); + pb + }; + + // Send GET request and stream the response body + let response = client.get(&index_url).send().await?; + if !response.status().is_success() { + return Err(format!("HTTP Error: {}", response.status()).into()); + } + + let mut stream = response.bytes_stream(); + let file_path = Path::new(cache_dir).join("INDEX.tar.gz"); + + let mut file = File::create(&file_path).await?; + let mut downloaded: u64 = 0; + + while let Some(chunk) = stream.try_next().await? { + file.write_all(&chunk).await?; + let chunk_len = chunk.len() as u64; + downloaded += chunk_len; + pb.set_position(downloaded); + } + + pb.finish_with_message("INDEX.tar.gz download finished"); + + // --- Извлечение и парсинг INDEX.toml --- + log::info!("Extracting INDEX.tar.gz to cache directory..."); + Package::extract_archive(&file_path.to_string_lossy())?; // Используем существующую функцию из pkgtoolkit + + let index_toml_path = Path::new(cache_dir).join("INDEX.toml"); + if !index_toml_path.exists() { + log::warn!("INDEX.toml not found in INDEX.tar.gz. Proceeding without index data."); + self.index_packages = Some(HashMap::new()); + return Ok(true); + } + + let index_content = tokio::fs::read_to_string(&index_toml_path).await?; + let index_data: IndexData = toml::from_str(&index_content)?; + + let mut package_map = HashMap::new(); + for pkg in index_data.packages { + // PKG_URL = /repo/package.mesk + // FULL URL = "http://mesk.anthrill.i2p/i2p/repo/pkg.mesk" + let base_url = url::Url::parse(&self.config.repo.repo_url)?; + let full_url = base_url.join(&pkg.url)?; + let mut pkg_clone = pkg.clone(); + pkg_clone.url = full_url.to_string(); + + package_map.insert(pkg_clone.name.clone(), pkg_clone); + } + + self.index_packages = Some(package_map.clone()); + log::info!( + "Index loaded successfully, {} packages found.", + package_map.len() + ); + + Ok(true) + } + + /// An internal auxiliary function for downloading data and writing it to a file with a progress display. + /// + /// # Arguments + /// * `url` - The URL to download from. + /// * `file_path` - The path to the file to write the data to. + /// * `description` - Description of the operation for the progress bar. + /// + /// # Errors + /// + /// Returns an error if the request fails, if the response status is not successful, or if there's an issue while writing the file. + async fn download_file_with_progress( + client: &reqwest::Client, + url: &str, + file_path: &Path, + description: &str, + ) -> Result<(), Box> { + // Make a HEAD request to get the content length for the progress bar + let head_response = client.head(url).send().await?; + let content_length: u64 = head_response + .headers() + .get(reqwest::header::CONTENT_LENGTH) + .and_then(|ct_len| ct_len.to_str().ok()) + .and_then(|ct_len| ct_len.parse().ok()) + .unwrap_or(0); + let pb = if content_length > 0 { + let pb = ProgressBar::new(content_length); + pb.set_style( + ProgressStyle::default_bar() + .template(&format!( + "{{spinner:.green}} [{{elapsed_precise}}] [{{bar:40.cyan/blue}}] {{bytes}}/{{total_bytes}} ({} {{eta}})", + description + ))? + .progress_chars("#>-"), + ); + pb + } else { + let pb = ProgressBar::new_spinner(); + pb.set_style(ProgressStyle::default_spinner().template(&format!( + "{{spinner:.green}} [{{elapsed_precise}}] {}...", + description + ))?); + pb + }; + + // Send GET request and stream the response body + let response = client.get(url).send().await?; + if !response.status().is_success() { + return Err(format!("HTTP Error: {}", response.status()).into()); + } + + let mut stream = response.bytes_stream(); + let mut file = File::create(&file_path).await?; + let mut downloaded: u64 = 0; + + while let Some(chunk) = stream.try_next().await? { + file.write_all(&chunk).await?; + let chunk_len = chunk.len() as u64; + downloaded += chunk_len; + pb.set_position(downloaded); + } + + pb.finish_with_message(format!("{} download finished", description)); + Ok(()) + } + + /// Fetches a specific package identified by `package_name`. + /// Assumes `fetch_index_http` has been called and `self.index_packages` is populated to get the URL. + /// Downloads the package file (.mesk) to the cache directory with a progress bar. + /// + /// # Errors + /// + /// Returns an error if the index is not loaded, the package is not found in the index, + /// the package URL is invalid, the request fails, or if there's an issue writing the file. + pub async fn fetch_package_http( + &self, + package_name: &str, + ) -> Result> { + let package_info = self.fetch_package_info(package_name)?; + let url = &package_info.url; + + let client = reqwest::Client::new(); + + let file_name = Path::new(url) + .file_name() + .ok_or("Could not determine filename from URL")? + .to_str() + .ok_or("Filename is not valid UTF-8")?; + let cache_dir = &self.config.paths.cache_dir; + let file_path = Path::new(cache_dir).join(file_name); + + Self::download_file_with_progress(&client, url, &file_path, file_name).await?; + + log::info!( + "Package '{}' downloaded successfully to {:?}", + package_name, + file_path + ); + Ok(true) + } + + /// Fetches a specific package identified by `index` (likely the package name). + /// Assumes `fetch_index_http` has been called and `self.index_packages` is populated. + pub fn fetch_package_info( + &self, + package_name: &str, + ) -> Result<&Package, Box> { + let packages = self + .index_packages + .as_ref() + .ok_or("Index not loaded. Call fetch_index_http first.")?; + let pkg_info = packages + .get(package_name) + .ok_or(format!("Package '{}' not found in index.", package_name))?; + Ok(pkg_info) + } +} -- cgit v1.2.3