use std::{ fs::{self, File, create_dir_all}, io, path::Path, }; use flate2::read::GzDecoder; use tar::Archive; use toml; use super::types::{Index, Install, Package}; #[allow(dead_code)] pub trait IndexOperations { fn gen_index(repo_path: &str) -> Result; } impl IndexOperations for Package { /// Generates an INDEX.toml file for a given repository directory. /// /// This function scans the `repo_path` directory for `.mesk` files. /// For each `.mesk` file found, it extracts the `INSTALL` metadata to get package details. /// It then collects all package information and serializes it into an `INDEX.toml` file /// located in the root of the `repo_path` directory. /// If `license` or `descr` fields are missing in the `INSTALL` file, they are set to empty strings (""). /// /// # Arguments /// * `repo_path` - A string slice representing the path to the repository directory. /// /// # Errors /// /// Returns an error if: /// - The `repo_path` directory cannot be read. /// - An `.mesk` file cannot be opened or read. /// - The `INSTALL` file cannot be extracted from an archive. /// - The `INSTALL` file content cannot be parsed as TOML. /// - The final `INDEX.toml` file cannot be written to disk. fn gen_index(repo_path: &str) -> Result { let repo_dir = Path::new(repo_path); if !repo_dir.is_dir() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("Repository directory does not exist: {}", repo_path), )); } let mut all_packages = Vec::new(); for entry_res in fs::read_dir(repo_path)? { let entry = entry_res?; let path = entry.path(); if path.extension().and_then(|s| s.to_str()) == Some("mesk") { log::info!("Processing archive for index: {}", path.display()); let temp_extract_dir = std::env::temp_dir().join(format!( "mesk_index_temp_{}", path.file_stem().unwrap_or_default().to_string_lossy() )); create_dir_all(&temp_extract_dir)?; let file = File::open(&path)?; let gz = GzDecoder::new(file); let mut archive = Archive::new(gz); for tar_entry_res in archive.entries()? { let mut tar_entry = tar_entry_res?; let entry_path = tar_entry.path()?; if entry_path.file_name().and_then(|n| n.to_str()) == Some("INSTALL") { if let Some(parent_name) = entry_path .parent() .and_then(|p| p.file_name()) .and_then(|n| n.to_str()) { let install_extract_path = temp_extract_dir.join(parent_name).join("INSTALL"); if let Some(parent_dir) = install_extract_path.parent() { create_dir_all(parent_dir)?; } tar_entry.unpack(&install_extract_path)?; log::debug!( "Extracted INSTALL from {} to {}", path.display(), install_extract_path.display() ); let install_content = fs::read_to_string(&install_extract_path)?; let install_data: Install = toml::from_str(&install_content) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let pkg_for_index = Package { name: install_data.package.name, version: install_data.package.version, arch: install_data.package.arch, descr: Some(install_data.package.descr.unwrap_or_default()), license: Some(install_data.package.license.unwrap_or_default()), url: install_data.package.url, git_repo: install_data.package.git_repo, }; all_packages.push(pkg_for_index); break; } else { log::warn!( "INSTALL file in archive {} has unexpected path structure, skipping.", path.display() ); } } } fs::remove_dir_all(&temp_extract_dir)?; } } let index = Index { packages: all_packages, }; let index_toml_content = toml::to_string_pretty(&index) .map_err(|e| std::io::Error::other(format!("Failed to serialize INDEX.toml: {}", e)))?; let index_path = repo_dir.join("INDEX.toml"); fs::write(&index_path, index_toml_content) .map_err(|e| std::io::Error::other(format!("Failed to write INDEX.toml: {}", e)))?; log::info!("Successfully generated INDEX.toml at {:?}", index_path); Ok(true) } }