summaryrefslogtreecommitdiff
path: root/src/pkgtoolkit/install.rs
diff options
context:
space:
mode:
authorNamilskyy <alive6863@gmail.com>2025-12-06 16:36:44 +0300
committerNamilskyy <alive6863@gmail.com>2025-12-06 16:36:44 +0300
commitdef46ae74c3f5974ed448e9877b0e0067a8e67d2 (patch)
tree4eda9fe8b6da96ea8f1824a14235286fe6e49c72 /src/pkgtoolkit/install.rs
parent79c8ecb6bf4d2fc2df5c90007e7c26b456ddc33f (diff)
Big code-cleaning in pkgtoolkit, implemented much functions and fixed logical mistakes
Diffstat (limited to 'src/pkgtoolkit/install.rs')
-rw-r--r--src/pkgtoolkit/install.rs343
1 files changed, 343 insertions, 0 deletions
diff --git a/src/pkgtoolkit/install.rs b/src/pkgtoolkit/install.rs
new file mode 100644
index 0000000..65476d9
--- /dev/null
+++ b/src/pkgtoolkit/install.rs
@@ -0,0 +1,343 @@
+use crate::cfg::config::Config;
+use std::{
+ fs::{self, Permissions, create_dir_all, set_permissions},
+ os::unix::fs::PermissionsExt,
+ path::{Path, StripPrefixError},
+ process::Command,
+};
+
+use toml;
+
+use super::archive::ArchiveOperations;
+use super::build::BuildOperations;
+use super::types::{Package, PackageManifest};
+
+pub trait InstallOperations {
+ fn collect_files_from_dir(root: &Path, base: &Path) -> Result<Vec<String>, std::io::Error>;
+ fn install(&mut self) -> Result<bool, std::io::Error>;
+ fn uninstall(&self) -> Result<bool, std::io::Error>;
+ fn load_manifest(&self) -> Result<PackageManifest, std::io::Error>;
+ fn list_installed_packages() -> Result<Vec<PackageManifest>, std::io::Error>;
+ fn is_installed(&self) -> Result<bool, std::io::Error>;
+}
+
+impl InstallOperations for Package {
+ /// Recursively collects all files from a directory and its subdirectories
+ /// and returns them as a vector of strings.
+ ///
+ /// # Arguments
+ ///
+ /// * `root`: The root directory from which to collect files.
+ /// * `base`: The base directory from which to strip the prefix from the file paths.
+ ///
+ /// # Returns
+ ///
+ /// A vector of strings containing the file paths relative to the `base` directory.
+ fn collect_files_from_dir(root: &Path, base: &Path) -> Result<Vec<String>, std::io::Error> {
+ let mut files = Vec::new();
+ for entry in fs::read_dir(root)? {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_dir() {
+ files.extend(Self::collect_files_from_dir(&path, base)?);
+ } else {
+ let rel_path = path
+ .strip_prefix(base)
+ .map_err(|e: StripPrefixError| {
+ std::io::Error::new(std::io::ErrorKind::InvalidData, e)
+ })?
+ .to_string_lossy()
+ .to_string();
+ files.push(rel_path);
+ }
+ }
+ Ok(files)
+ }
+
+ /// Installs the package according to the INSTALL file in the archive.
+ ///
+ /// There are two strategies for installing the package. If the BUILD file is empty, the package is assumed to be a binary package and the default install hook is skipped. If the BUILD file is not empty, the package is assumed to be a source package and the default build hook is skipped.
+ ///
+ /// If the BUILD file is empty and the INSTALL file contains a custom script, the custom script is run instead of the default install hook.
+ /// If the BUILD file is not empty and the INSTALL file contains a custom script, the custom script is ignored.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the BUILD file is invalid or if the build or install hook fails.
+ fn install(&mut self) -> Result<bool, std::io::Error> {
+ let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?;
+ let (install_meta, _setts_meta, build_meta) = Self::loadmeta(self)?;
+
+ let installed_db = Path::new(&config.paths.installed_db);
+ create_dir_all(installed_db)?;
+
+ let mut all_files: Vec<String> = Vec::new();
+
+ let is_build_present_and_not_empty = build_meta.is_some();
+
+ if is_build_present_and_not_empty {
+ log::info!(
+ "Found BUILD file, preparing to build and install package: {}",
+ self.name
+ );
+ let build_meta_ref = build_meta.as_ref().unwrap();
+ if let Err(e) = self.execute_build(build_meta_ref) {
+ return Err(std::io::Error::other(format!(
+ "Build failed during installation: {}",
+ e
+ )));
+ }
+
+ let staging_dir = Path::new(&config.paths.cache_dir).join("staging");
+ create_dir_all(&staging_dir)?;
+ all_files = Self::collect_files_from_dir(&staging_dir, &staging_dir)?
+ .iter()
+ .map(|rel| format!("/{}", rel)) // Преобразуйте в абсолютные пути (предполагаем root=/)
+ .collect();
+
+ for file in &all_files {
+ let src = staging_dir.join(file.trim_start_matches('/'));
+ let dest = Path::new(file);
+
+ // Check if source file exists
+ if !src.exists() {
+ log::warn!("Source file not found: {:?}, skipping", src);
+ continue;
+ }
+
+ if let Some(parent) = dest.parent() {
+ create_dir_all(parent)?;
+ }
+
+ fs::copy(&src, dest).map_err(|e| {
+ std::io::Error::other(format!(
+ "Failed to copy {} to {}: {}",
+ src.display(),
+ dest.display(),
+ e
+ ))
+ })?;
+ // TODO: Set proper permissions
+ }
+
+ fs::remove_dir_all(&staging_dir)?;
+ } else {
+ log::info!(
+ "No BUILD file or it's empty. Treating as binary package. Installing via INSTALL config or custom script."
+ );
+
+ // Validate custom script before execution
+ if let Some(ref script) = install_meta.install.custom_script {
+ Self::validate_custom_script(script)?;
+ }
+
+ if let Some(ref script) = install_meta.install.custom_script {
+ log::info!(
+ "Executing custom install script for {}",
+ install_meta.package.name
+ );
+ let status = if script.starts_with("./") || script.contains('/') {
+ Command::new("/bin/sh").arg("-c").arg(script).status()
+ } else {
+ Command::new(script).status()
+ }
+ .map_err(|e| {
+ std::io::Error::other(format!("Failed to run custom script: {}", e))
+ })?;
+
+ if !status.success() {
+ return Err(std::io::Error::other("Custom install script failed"));
+ }
+
+ log::warn!(
+ "Custom script used; file list may be incomplete. Add manual tracking if needed."
+ );
+ } else {
+ log::info!(
+ "No custom script. Running default install hook for {}",
+ install_meta.package.name
+ );
+ let source_file_name = &self.name;
+ let build_dir = Path::new(&config.paths.cache_dir)
+ .join(format!("{}-{}", self.name, self.version));
+ let src_path = build_dir.join(source_file_name);
+ let dest_path = Path::new(&install_meta.install.path);
+
+ // Check if source file exists
+ if !src_path.exists() {
+ return Err(std::io::Error::other(format!(
+ "Source binary file not found: {}",
+ src_path.display()
+ )));
+ }
+
+ if let Some(parent) = dest_path.parent() {
+ create_dir_all(parent).map_err(|e| {
+ std::io::Error::other(format!("Failed to create parent dir: {}", e))
+ })?;
+ }
+
+ fs::copy(&src_path, dest_path)
+ .map_err(|e| std::io::Error::other(format!("Failed to copy file: {}", e)))?;
+
+ let mode = u32::from_str_radix(&install_meta.install.mode, 8).map_err(|_| {
+ std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "Invalid mode string in INSTALL",
+ )
+ })?;
+ let perms = Permissions::from_mode(mode);
+ set_permissions(dest_path, perms).map_err(|e| {
+ std::io::Error::other(format!("Failed to set permissions: {}", e))
+ })?;
+
+ let output = Command::new("chown")
+ .arg(format!(
+ "{}:{}",
+ install_meta.install.user, install_meta.install.group
+ ))
+ .arg(dest_path)
+ .output()
+ .map_err(|e| std::io::Error::other(format!("'chown' command failed: {}", e)))?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ log::warn!(
+ "Warning: 'chown' command failed (requires root?):\n{}",
+ stderr
+ );
+ }
+
+ all_files = install_meta.files;
+ }
+ }
+
+ let manifest = PackageManifest {
+ name: self.name.clone(),
+ version: self.version.clone(),
+ all_files,
+ };
+ let manifest_path = installed_db.join(format!("{}-{}.toml", self.name, self.version));
+ let manifest_toml =
+ toml::to_string_pretty(&manifest).map_err(|e| std::io::Error::other(e.to_string()))?;
+ fs::write(&manifest_path, manifest_toml)?;
+
+ log::info!(
+ "Package {} installed successfully. Manifest generated at {:?} with {} files tracked",
+ self.name,
+ manifest_path,
+ manifest.all_files.len()
+ );
+ Ok(true)
+ }
+
+ /// Loads the package manifest from the installed database
+ fn load_manifest(&self) -> Result<PackageManifest, std::io::Error> {
+ let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?;
+ let installed_db = Path::new(&config.paths.installed_db);
+ let manifest_path = installed_db.join(format!("{}-{}.toml", self.name, self.version));
+
+ if !manifest_path.exists() {
+ return Err(std::io::Error::other(format!(
+ "Package manifest not found: {:?}",
+ manifest_path
+ )));
+ }
+
+ let manifest_content = fs::read_to_string(&manifest_path)?;
+ let manifest: PackageManifest = toml::from_str(&manifest_content)
+ .map_err(|e| std::io::Error::other(format!("Failed to parse manifest: {}", e)))?;
+
+ Ok(manifest)
+ }
+
+ /// Uninstalls the package by removing all files listed in the manifest
+ /// and then removing the manifest file itself
+ fn uninstall(&self) -> Result<bool, std::io::Error> {
+ let manifest = self.load_manifest()?;
+
+ log::info!(
+ "Uninstalling package {}-{}",
+ manifest.name,
+ manifest.version
+ );
+
+ let mut removed_files = 0;
+ let mut failed_removals = 0;
+
+ for file_path in &manifest.all_files {
+ let path = Path::new(file_path);
+
+ if path.exists() {
+ match fs::remove_file(path) {
+ Ok(()) => {
+ removed_files += 1;
+ log::debug!("Removed file: {:?}", path);
+ }
+ Err(e) => {
+ failed_removals += 1;
+ log::warn!("Failed to remove file {:?}: {}", path, e);
+ }
+ }
+ } else {
+ log::debug!("File not found, skipping: {:?}", path);
+ }
+ }
+
+ // Remove the manifest file
+ let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?;
+ let installed_db = Path::new(&config.paths.installed_db);
+ let manifest_path = installed_db.join(format!("{}-{}.toml", self.name, self.version));
+
+ if manifest_path.exists() {
+ fs::remove_file(&manifest_path)?;
+ log::info!("Removed manifest file: {:?}", manifest_path);
+ }
+
+ log::info!(
+ "Package {}-{} uninstalled successfully. Removed {} files, {} failures",
+ manifest.name,
+ manifest.version,
+ removed_files,
+ failed_removals
+ );
+
+ Ok(failed_removals == 0)
+ }
+
+ /// Lists all installed packages by reading manifest files from the installed database
+ fn list_installed_packages() -> Result<Vec<PackageManifest>, std::io::Error> {
+ let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?;
+ let installed_db = Path::new(&config.paths.installed_db);
+
+ if !installed_db.exists() {
+ return Ok(Vec::new());
+ }
+
+ let mut packages = Vec::new();
+
+ for entry in fs::read_dir(installed_db)? {
+ let entry = entry?;
+ let path = entry.path();
+
+ if path.extension().and_then(|s| s.to_str()) == Some("toml") {
+ let manifest_content = fs::read_to_string(&path)?;
+ let manifest: PackageManifest = toml::from_str(&manifest_content).map_err(|e| {
+ std::io::Error::other(format!("Failed to parse manifest {:?}: {}", path, e))
+ })?;
+ packages.push(manifest);
+ }
+ }
+
+ Ok(packages)
+ }
+
+ /// Checks if the package is currently installed by looking for its manifest
+ fn is_installed(&self) -> Result<bool, std::io::Error> {
+ let config = Config::parse().map_err(|e| std::io::Error::other(e.to_string()))?;
+ let installed_db = Path::new(&config.paths.installed_db);
+ let manifest_path = installed_db.join(format!("{}-{}.toml", self.name, self.version));
+
+ Ok(manifest_path.exists())
+ }
+}