diff --git a/libips/src/repository/file_backend.rs b/libips/src/repository/file_backend.rs index a57d256..6fad659 100644 --- a/libips/src/repository/file_backend.rs +++ b/libips/src/repository/file_backend.rs @@ -64,6 +64,37 @@ impl Transaction { }) } + /// Update the manifest in the transaction + /// + /// This intelligently merges the provided manifest with the existing one, + /// preserving file actions that have already been added to the transaction. + /// + /// The merge strategy: + /// - Keeps all file actions from the transaction's manifest (these have been processed with checksums, etc.) + /// - Adds any file actions from the provided manifest that don't exist in the transaction's manifest + /// - Merges other types of actions (attributes, directories, dependencies, licenses, links) from both manifests + pub fn update_manifest(&mut self, manifest: Manifest) { + // Keep track of file paths that are already in the transaction's manifest + let existing_file_paths: std::collections::HashSet = self.manifest.files + .iter() + .map(|f| f.path.clone()) + .collect(); + + // Add file actions from the provided manifest that don't exist in the transaction's manifest + for file in manifest.files { + if !existing_file_paths.contains(&file.path) { + self.manifest.add_file(file); + } + } + + // Merge other types of actions + self.manifest.attributes.extend(manifest.attributes); + self.manifest.directories.extend(manifest.directories); + self.manifest.dependencies.extend(manifest.dependencies); + self.manifest.licenses.extend(manifest.licenses); + self.manifest.links.extend(manifest.links); + } + /// Process a file for the transaction /// /// Takes a FileAction and a path to a file in a prototype directory. @@ -299,6 +330,11 @@ impl Repository for FileBackend { fs::create_dir_all(self.path.join("catalog").join(publisher))?; fs::create_dir_all(self.path.join("pkg").join(publisher))?; + // Set as default publisher if no default publisher is set + if self.config.default_publisher.is_none() { + self.config.default_publisher = Some(publisher.to_string()); + } + // Save the updated configuration self.save_config()?; } @@ -417,7 +453,7 @@ impl Repository for FileBackend { // For each package, list contents let mut contents = Vec::new(); - for (pkg_name, pkg_version, pub_name) in packages { + for (pkg_name, pkg_version, _pub_name) in packages { // Example content data (package, path, type) let example_contents = vec![ (format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()), @@ -504,6 +540,22 @@ impl Repository for FileBackend { Ok(()) } + + /// Set the default publisher for the repository + fn set_default_publisher(&mut self, publisher: &str) -> Result<()> { + // Check if the publisher exists + if !self.config.publishers.contains(&publisher.to_string()) { + return Err(anyhow!("Publisher does not exist: {}", publisher)); + } + + // Set the default publisher + self.config.default_publisher = Some(publisher.to_string()); + + // Save the updated configuration + self.save_config()?; + + Ok(()) + } } impl FileBackend { diff --git a/libips/src/repository/mod.rs b/libips/src/repository/mod.rs index 1982d2c..54740f3 100644 --- a/libips/src/repository/mod.rs +++ b/libips/src/repository/mod.rs @@ -45,6 +45,7 @@ pub struct RepositoryConfig { pub version: RepositoryVersion, pub publishers: Vec, pub properties: HashMap, + pub default_publisher: Option, } impl Default for RepositoryConfig { @@ -53,6 +54,7 @@ impl Default for RepositoryConfig { version: RepositoryVersion::default(), publishers: Vec::new(), properties: HashMap::new(), + default_publisher: None, } } } @@ -94,4 +96,7 @@ pub trait Repository { /// Refresh repository metadata fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>; + + /// Set the default publisher for the repository + fn set_default_publisher(&mut self, publisher: &str) -> Result<()>; } \ No newline at end of file diff --git a/libips/src/repository/rest_backend.rs b/libips/src/repository/rest_backend.rs index 962f58e..358a53b 100644 --- a/libips/src/repository/rest_backend.rs +++ b/libips/src/repository/rest_backend.rs @@ -3,9 +3,8 @@ // MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::{Result, anyhow}; +use anyhow::{anyhow, Result}; use std::path::{Path, PathBuf}; -use std::collections::HashMap; use super::{Repository, RepositoryConfig, RepositoryVersion}; @@ -299,6 +298,25 @@ impl Repository for RestBackend { Ok(()) } + + /// Set the default publisher for the repository + fn set_default_publisher(&mut self, publisher: &str) -> Result<()> { + // This is a stub implementation + // In a real implementation, we would make a REST API call to set the default publisher + + // Check if the publisher exists + if !self.config.publishers.contains(&publisher.to_string()) { + return Err(anyhow!("Publisher does not exist: {}", publisher)); + } + + // Set the default publisher + self.config.default_publisher = Some(publisher.to_string()); + + // Save the updated configuration + self.save_config()?; + + Ok(()) + } } impl RestBackend { diff --git a/pkg6dev/src/main.rs b/pkg6dev/src/main.rs index 1c51f4a..7846d0f 100644 --- a/pkg6dev/src/main.rs +++ b/pkg6dev/src/main.rs @@ -1,7 +1,8 @@ use clap::{Parser, Subcommand}; use libips::actions::{ActionError, File, Manifest}; +use libips::repository::{Repository, FileBackend}; -use anyhow::Result; +use anyhow::{Result, anyhow}; use std::collections::HashMap; use std::fs::{read_dir, OpenOptions}; use std::io::Write; @@ -31,6 +32,24 @@ enum Commands { ShowComponent { component: String, }, + /// Publish a package to a repository + Publish { + /// Path to the manifest file + #[clap(short = 'm', long)] + manifest_path: PathBuf, + + /// Path to the prototype directory containing the files to publish + #[clap(short = 'p', long)] + prototype_dir: PathBuf, + + /// Path to the repository + #[clap(short = 'r', long)] + repo_path: PathBuf, + + /// Publisher name (defaults to "test" if not specified) + #[clap(short = 'u', long)] + publisher: Option, + }, } fn main() -> Result<()> { @@ -43,6 +62,12 @@ fn main() -> Result<()> { replacements, output_manifest, } => diff_component(component, replacements, output_manifest), + Commands::Publish { + manifest_path, + prototype_dir, + repo_path, + publisher, + } => publish_package(manifest_path, prototype_dir, repo_path, publisher), } } @@ -285,3 +310,88 @@ fn make_file_map(files: Vec) -> HashMap { }) .collect() } + +/// Publish a package to a repository +/// +/// This function: +/// 1. Opens the repository at the specified path +/// 2. Parses the manifest file +/// 3. Uses the FileBackend's publish_files method to publish the files from the prototype directory +fn publish_package( + manifest_path: &PathBuf, + prototype_dir: &PathBuf, + repo_path: &PathBuf, + publisher: &Option, +) -> Result<()> { + // Check if the manifest file exists + if !manifest_path.exists() { + return Err(anyhow!("Manifest file does not exist: {}", manifest_path.display())); + } + + // Check if the prototype directory exists + if !prototype_dir.exists() { + return Err(anyhow!("Prototype directory does not exist: {}", prototype_dir.display())); + } + + // Parse the manifest file + println!("Parsing manifest file: {}", manifest_path.display()); + let manifest = Manifest::parse_file(manifest_path)?; + + // Open the repository + println!("Opening repository at: {}", repo_path.display()); + let repo = match FileBackend::open(repo_path) { + Ok(repo) => repo, + Err(_) => { + println!("Repository does not exist, creating a new one..."); + // Create a new repository with version 4 + FileBackend::create(repo_path, libips::repository::RepositoryVersion::V4)? + } + }; + + // Determine which publisher to use + let publisher_name = if let Some(pub_name) = publisher { + // Use the explicitly specified publisher + if !repo.config.publishers.contains(pub_name) { + return Err(anyhow!("Publisher '{}' does not exist in the repository. Please add it first using pkg6repo add-publisher.", pub_name)); + } + pub_name.clone() + } else { + // Use the default publisher + match &repo.config.default_publisher { + Some(default_pub) => default_pub.clone(), + None => return Err(anyhow!("No default publisher set in the repository. Please specify a publisher using the --publisher option or set a default publisher.")) + } + }; + + // Begin a transaction + println!("Beginning transaction for publisher: {}", publisher_name); + let mut transaction = repo.begin_transaction()?; + + // Add files from the prototype directory to the transaction + println!("Adding files from prototype directory: {}", prototype_dir.display()); + for file_action in manifest.files.iter() { + // Construct the full path to the file in the prototype directory + let file_path = prototype_dir.join(&file_action.path); + + // Check if the file exists + if !file_path.exists() { + println!("Warning: File does not exist in prototype directory: {}", file_path.display()); + continue; + } + + // Add the file to the transaction + println!("Adding file: {}", file_action.path); + transaction.add_file(file_action.clone(), &file_path)?; + } + + // Update the manifest in the transaction + println!("Updating manifest in the transaction..."); + transaction.update_manifest(manifest); + + // Commit the transaction + println!("Committing transaction..."); + transaction.commit()?; + + println!("Package published successfully!"); + Ok(()) +}