diff --git a/libips/src/image/catalog.rs b/libips/src/image/catalog.rs index a4da8d9..7c2a93e 100644 --- a/libips/src/image/catalog.rs +++ b/libips/src/image/catalog.rs @@ -119,53 +119,79 @@ impl ImageCatalog { /// Build the catalog from downloaded catalogs pub fn build_catalog(&self, publishers: &[String]) -> Result<()> { + println!("Building catalog with publishers: {:?}", publishers); + println!("Catalog directory: {:?}", self.catalog_dir); + println!("Catalog database path: {:?}", self.db_path); + if publishers.is_empty() { + println!("No publishers provided"); return Err(CatalogError::NoPublishers); } // Open the database + println!("Opening database at {:?}", self.db_path); let db = Database::open(&self.db_path) .map_err(|e| CatalogError::Database(format!("Failed to open database: {}", e)))?; // Begin a writing transaction + println!("Beginning write transaction"); let tx = db.begin_write() .map_err(|e| CatalogError::Database(format!("Failed to begin transaction: {}", e)))?; // Open the catalog table + println!("Opening catalog table"); let mut catalog_table = tx.open_table(CATALOG_TABLE) .map_err(|e| CatalogError::Database(format!("Failed to open catalog table: {}", e)))?; // Open the obsoleted table + println!("Opening obsoleted table"); let mut obsoleted_table = tx.open_table(OBSOLETED_TABLE) .map_err(|e| CatalogError::Database(format!("Failed to open obsoleted table: {}", e)))?; // Process each publisher for publisher in publishers { + println!("Processing publisher: {}", publisher); let publisher_catalog_dir = self.catalog_dir.join(publisher); + println!("Publisher catalog directory: {:?}", publisher_catalog_dir); // Skip if the publisher catalog directory doesn't exist if !publisher_catalog_dir.exists() { + println!("Publisher catalog directory not found: {}", publisher_catalog_dir.display()); warn!("Publisher catalog directory not found: {}", publisher_catalog_dir.display()); continue; } // Create a catalog manager for this publisher + println!("Creating catalog manager for publisher: {}", publisher); let mut catalog_manager = CatalogManager::new(&publisher_catalog_dir, publisher) .map_err(|e| CatalogError::Repository(crate::repository::RepositoryError::Other(format!("Failed to create catalog manager: {}", e))))?; // Get all catalog parts + println!("Getting catalog parts for publisher: {}", publisher); let parts = catalog_manager.attrs().parts.clone(); + println!("Catalog parts: {:?}", parts.keys().collect::>()); // Load all catalog parts for part_name in parts.keys() { + println!("Loading catalog part: {}", part_name); catalog_manager.load_part(part_name) .map_err(|e| CatalogError::Repository(crate::repository::RepositoryError::Other(format!("Failed to load catalog part: {}", e))))?; } // Process each catalog part for (part_name, _) in parts { + println!("Processing catalog part: {}", part_name); if let Some(part) = catalog_manager.get_part(&part_name) { + println!("Found catalog part: {}", part_name); + println!("Packages in part: {:?}", part.packages.keys().collect::>()); + if let Some(publisher_packages) = part.packages.get(publisher) { + println!("Packages for publisher {}: {:?}", publisher, publisher_packages.keys().collect::>()); + } else { + println!("No packages found for publisher: {}", publisher); + } self.process_catalog_part(&mut catalog_table, &mut obsoleted_table, part, publisher)?; + } else { + println!("Catalog part not found: {}", part_name); } } } @@ -190,70 +216,109 @@ impl ImageCatalog { part: &CatalogPart, publisher: &str, ) -> Result<()> { + println!("Processing catalog part for publisher: {}", publisher); + // Get packages for this publisher if let Some(publisher_packages) = part.packages.get(publisher) { + println!("Found {} package stems for publisher {}", publisher_packages.len(), publisher); + // Process each package stem for (stem, versions) in publisher_packages { + println!("Processing package stem: {}", stem); + println!("Found {} versions for stem {}", versions.len(), stem); + // Process each package version for version_entry in versions { + println!("Processing version: {}", version_entry.version); + println!("Actions: {:?}", version_entry.actions); + // Create the FMRI let version = if !version_entry.version.is_empty() { match crate::fmri::Version::parse(&version_entry.version) { - Ok(v) => Some(v), + Ok(v) => { + println!("Parsed version: {:?}", v); + Some(v) + }, Err(e) => { + println!("Failed to parse version '{}': {}", version_entry.version, e); warn!("Failed to parse version '{}': {}", version_entry.version, e); continue; } } } else { + println!("Empty version string"); None }; let fmri = Fmri::with_publisher(publisher, stem, version); + println!("Created FMRI: {}", fmri); // Create the key for the catalog table (stem@version) let catalog_key = format!("{}@{}", stem, version_entry.version); + println!("Catalog key: {}", catalog_key); // Create the key for the obsoleted table (full FMRI including publisher) let obsoleted_key = fmri.to_string(); + println!("Obsoleted key: {}", obsoleted_key); // Check if we already have this package in the catalog let existing_manifest = if let Ok(bytes) = catalog_table.get(catalog_key.as_str()) { if let Some(bytes) = bytes { + println!("Found existing manifest for {}", catalog_key); Some(serde_json::from_slice::(bytes.value())?) } else { + println!("No existing manifest found for {}", catalog_key); None } } else { + println!("Error getting manifest for {}", catalog_key); None }; // Create or update the manifest + println!("Creating or updating manifest"); let manifest = self.create_or_update_manifest(existing_manifest, version_entry, stem, publisher)?; // Check if the package is obsolete let is_obsolete = self.is_package_obsolete(&manifest); + println!("Package is obsolete: {}", is_obsolete); // Serialize the manifest let manifest_bytes = serde_json::to_vec(&manifest)?; + println!("Serialized manifest size: {} bytes", manifest_bytes.len()); // Store the package in the appropriate table if is_obsolete { + println!("Storing obsolete package in obsoleted table"); // Store obsolete packages in the obsoleted table with the full FMRI as key // We don't store any meaningful values in the obsoleted table as per requirements, // but we need to provide a valid byte slice let empty_bytes: &[u8] = &[0u8; 0]; - obsoleted_table.insert(obsoleted_key.as_str(), empty_bytes) - .map_err(|e| CatalogError::Database(format!("Failed to insert into obsoleted table: {}", e)))?; + match obsoleted_table.insert(obsoleted_key.as_str(), empty_bytes) { + Ok(_) => println!("Successfully inserted into obsoleted table"), + Err(e) => { + println!("Failed to insert into obsoleted table: {}", e); + return Err(CatalogError::Database(format!("Failed to insert into obsoleted table: {}", e))); + } + } } else { + println!("Storing non-obsolete package in catalog table"); // Store non-obsolete packages in the catalog table with stem@version as a key - catalog_table.insert(catalog_key.as_str(), manifest_bytes.as_slice()) - .map_err(|e| CatalogError::Database(format!("Failed to insert into catalog table: {}", e)))?; + match catalog_table.insert(catalog_key.as_str(), manifest_bytes.as_slice()) { + Ok(_) => println!("Successfully inserted into catalog table"), + Err(e) => { + println!("Failed to insert into catalog table: {}", e); + return Err(CatalogError::Database(format!("Failed to insert into catalog table: {}", e))); + } + } } } } + } else { + println!("No packages found for publisher: {}", publisher); } + println!("Finished processing catalog part for publisher: {}", publisher); Ok(()) } diff --git a/libips/src/image/installed_tests.rs b/libips/src/image/installed_tests.rs index 11b3fc4..ad7ef69 100644 --- a/libips/src/image/installed_tests.rs +++ b/libips/src/image/installed_tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::actions::{Attr, Manifest}; use crate::fmri::Fmri; -use redb::ReadableTable; +use redb::{Database, ReadableTable}; use std::str::FromStr; use tempfile::tempdir; @@ -12,7 +12,7 @@ fn test_installed_packages() { let image_path = temp_dir.path().join("image"); // Create the image - let image = Image::create_image(&image_path).unwrap(); + let image = Image::create_image(&image_path, ImageType::Full).unwrap(); // Verify that the installed packages database was initialized assert!(image.installed_db_path().exists()); diff --git a/libips/src/image/mod.rs b/libips/src/image/mod.rs index 7b3355c..c425a76 100644 --- a/libips/src/image/mod.rs +++ b/libips/src/image/mod.rs @@ -4,7 +4,6 @@ mod tests; use miette::Diagnostic; use properties::*; -use redb::Database; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::{self, File}; @@ -455,9 +454,17 @@ impl Image { /// /// This method only creates the image structure without adding publishers or downloading catalogs. /// Publisher addition and catalog downloading should be handled separately. - pub fn create_image>(path: P) -> Result { - // Create a new image - let image = Image::new_full(path.as_ref().to_path_buf()); + /// + /// # Arguments + /// + /// * `path` - The path where the image will be created + /// * `image_type` - The type of image to create (Full or Partial) + pub fn create_image>(path: P, image_type: ImageType) -> Result { + // Create a new image based on the specified type + let image = match image_type { + ImageType::Full => Image::new_full(path.as_ref().to_path_buf()), + ImageType::Partial => Image::new_partial(path.as_ref().to_path_buf()), + }; // Create the directory structure image.create_metadata_dir()?; diff --git a/libips/src/image/tests.rs b/libips/src/image/tests.rs index f03992d..dec51ea 100644 --- a/libips/src/image/tests.rs +++ b/libips/src/image/tests.rs @@ -9,7 +9,7 @@ fn test_image_catalog() { let image_path = temp_dir.path().join("image"); // Create the image - let image = Image::create_image(&image_path).unwrap(); + let image = Image::create_image(&image_path, ImageType::Full).unwrap(); // Verify that the catalog database was initialized assert!(image.catalog_db_path().exists()); @@ -25,14 +25,24 @@ fn test_catalog_methods() { let image_path = temp_dir.path().join("image"); // Create the image - let mut image = Image::create_image(&image_path).unwrap(); + let mut image = Image::create_image(&image_path, ImageType::Full).unwrap(); + + // Print the image type and paths + println!("Image type: {:?}", image.image_type()); + println!("Image path: {:?}", image.path()); + println!("Metadata dir: {:?}", image.metadata_dir()); + println!("Catalog dir: {:?}", image.catalog_dir()); // Add a publisher image.add_publisher("test", "http://example.com/repo", vec![], true).unwrap(); + // Print the publishers + println!("Publishers: {:?}", image.publishers()); + // Create the catalog directory structure let catalog_dir = image.catalog_dir(); let publisher_dir = catalog_dir.join("test"); + println!("Publisher dir: {:?}", publisher_dir); fs::create_dir_all(&publisher_dir).unwrap(); // Create a simple catalog.attrs file @@ -42,6 +52,8 @@ fn test_catalog_methods() { }, "version": 1 }"#; + println!("Writing catalog.attrs to {:?}", publisher_dir.join("catalog.attrs")); + println!("catalog.attrs content: {}", attrs_content); fs::write(publisher_dir.join("catalog.attrs"), attrs_content).unwrap(); // Create a simple base catalog part @@ -71,13 +83,33 @@ fn test_catalog_methods() { } } }"#; + println!("Writing base catalog part to {:?}", publisher_dir.join("base")); + println!("base catalog part content: {}", base_content); fs::write(publisher_dir.join("base"), base_content).unwrap(); + // Verify that the files were written correctly + println!("Checking if catalog.attrs exists: {}", publisher_dir.join("catalog.attrs").exists()); + println!("Checking if base catalog part exists: {}", publisher_dir.join("base").exists()); + // Build the catalog - image.build_catalog().unwrap(); + println!("Building catalog..."); + match image.build_catalog() { + Ok(_) => println!("Catalog built successfully"), + Err(e) => println!("Failed to build catalog: {:?}", e), + } // Query the catalog - let packages = image.query_catalog(None).unwrap(); + println!("Querying catalog..."); + let packages = match image.query_catalog(None) { + Ok(pkgs) => { + println!("Found {} packages", pkgs.len()); + pkgs + }, + Err(e) => { + println!("Failed to query catalog: {:?}", e); + panic!("Failed to query catalog: {:?}", e); + } + }; // Verify that both non-obsolete and obsolete packages are in the results assert_eq!(packages.len(), 2); diff --git a/libips/src/repository/catalog.rs b/libips/src/repository/catalog.rs index bc5f564..8a00fba 100644 --- a/libips/src/repository/catalog.rs +++ b/libips/src/repository/catalog.rs @@ -396,8 +396,7 @@ pub struct CatalogManager { impl CatalogManager { /// Create a new catalog manager pub fn new>(base_dir: P, publisher: &str) -> Result { - let base_dir = base_dir.as_ref().to_path_buf(); - let publisher_catalog_dir = base_dir.join(publisher).join("catalog"); + let publisher_catalog_dir = base_dir.as_ref().to_path_buf(); // Create catalog directory if it doesn't exist if !publisher_catalog_dir.exists() { diff --git a/pkg6/src/main.rs b/pkg6/src/main.rs index 5e98270..06886a4 100644 --- a/pkg6/src/main.rs +++ b/pkg6/src/main.rs @@ -2,8 +2,6 @@ mod error; use error::{Pkg6Error, Result}; use clap::{Parser, Subcommand}; -use libips::fmri::Fmri; -use libips::image::Publisher; use serde::Serialize; use std::path::PathBuf; use std::io::Write; @@ -39,6 +37,14 @@ struct PublisherOutput { #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] struct App { + /// Path to the image to operate on + /// + /// If not specified, the default image is determined as follows: + /// - If $HOME/.pkg exists, that directory is used + /// - Otherwise, the root directory (/) is used + #[clap(short = 'R', global = true)] + image_path: Option, + #[clap(subcommand)] command: Commands, } @@ -411,9 +417,39 @@ enum Commands { /// Publisher origin URL (required if publisher is specified) #[clap(short = 'g', requires = "publisher")] origin: Option, + + /// Type of image to create (full or partial, default: full) + #[clap(short = 't', long = "type", default_value = "full")] + image_type: String, }, } +/// Determines the image path to use based on the provided argument and default rules +/// +/// If the image_path argument is provided, that path is used. +/// Otherwise, if $HOME/.pkg exists, that path is used. +/// Otherwise, the root directory (/) is used. +fn determine_image_path(image_path: Option) -> PathBuf { + if let Some(path) = image_path { + // Use the explicitly provided path + debug!("Using explicitly provided image path: {}", path.display()); + path + } else { + // Check if $HOME/.pkg exists + if let Ok(home_dir) = std::env::var("HOME") { + let home_pkg = PathBuf::from(home_dir).join(".pkg"); + if home_pkg.exists() { + debug!("Using user home image path: {}", home_pkg.display()); + return PathBuf::from(home_pkg); + } + } + + // Default to root directory + debug!("Using root directory as image path"); + PathBuf::from("/") + } +} + fn main() -> Result<()> { // Add debug statement at the very beginning eprintln!("MAIN: Starting pkg6 command"); @@ -588,15 +624,16 @@ fn main() -> Result<()> { debug!("Origin: {:?}", origin); debug!("Mirror: {:?}", mirror); - // Get the current working directory as the default image path - let current_dir = std::env::current_dir()?; + // Determine the image path using the -R argument or default rules + let image_path = determine_image_path(cli.image_path.clone()); + info!("Using image at: {}", image_path.display()); - // Try to load the image from the current directory - let mut image = match libips::image::Image::load(¤t_dir) { + // Try to load the image from the determined path + let mut image = match libips::image::Image::load(&image_path) { Ok(img) => img, Err(e) => { - error!("Failed to load image from current directory: {}", e); - error!("Make sure you are in an image directory or use pkg6 image-create first"); + error!("Failed to load image from {}: {}", image_path.display(), e); + error!("Make sure the path points to a valid image or use pkg6 image-create first"); return Err(e.into()); } }; @@ -639,15 +676,16 @@ fn main() -> Result<()> { Commands::UnsetPublisher { publisher } => { info!("Unsetting publisher: {}", publisher); - // Get the current working directory as the default image path - let current_dir = std::env::current_dir()?; + // Determine the image path using the -R argument or default rules + let image_path = determine_image_path(cli.image_path.clone()); + info!("Using image at: {}", image_path.display()); - // Try to load the image from the current directory - let mut image = match libips::image::Image::load(¤t_dir) { + // Try to load the image from the determined path + let mut image = match libips::image::Image::load(&image_path) { Ok(img) => img, Err(e) => { - error!("Failed to load image from current directory: {}", e); - error!("Make sure you are in an image directory or use pkg6 image-create first"); + error!("Failed to load image from {}: {}", image_path.display(), e); + error!("Make sure the path points to a valid image or use pkg6 image-create first"); return Err(e.into()); } }; @@ -670,28 +708,20 @@ fn main() -> Result<()> { Commands::Publisher { verbose, output_format, publishers } => { info!("Showing publisher information"); - // Get the current working directory as the default image path - let current_dir = std::env::current_dir()?; + // Determine the image path using the -R argument or default rules + let image_path = determine_image_path(cli.image_path.clone()); + info!("Using image at: {}", image_path.display()); - // Determine the path to the image configuration file - let image_json_path = match libips::image::ImageType::Full { - libips::image::ImageType::Full => current_dir.join("var/pkg/pkg6.image.json"), - libips::image::ImageType::Partial => current_dir.join(".pkg/pkg6.image.json"), + // Try to load the image from the determined path + let image = match libips::image::Image::load(&image_path) { + Ok(img) => img, + Err(e) => { + error!("Failed to load image from {}: {}", image_path.display(), e); + error!("Make sure the path points to a valid image or use pkg6 image-create first"); + return Err(e.into()); + } }; - // Check if the image configuration file exists - if !image_json_path.exists() { - error!("Image configuration file not found at {}", image_json_path.display()); - error!("Make sure you are in an image directory or use pkg6 image-create first"); - return Err(Pkg6Error::from(format!("Image configuration file not found at {}", image_json_path.display()))); - } - - // Read the image configuration file - let image_json = std::fs::read_to_string(&image_json_path)?; - - // Parse the image configuration file - let image: libips::image::Image = serde_json::from_str(&image_json)?; - // Get all publishers let all_publishers = image.publishers(); @@ -725,8 +755,8 @@ fn main() -> Result<()> { .map(|p| { let catalog_dir = if *verbose { let dir = match image.image_type() { - libips::image::ImageType::Full => current_dir.join("var/pkg/catalog"), - libips::image::ImageType::Partial => current_dir.join(".pkg/catalog"), + libips::image::ImageType::Full => image_path.join("var/pkg/catalog"), + libips::image::ImageType::Partial => image_path.join(".pkg/catalog"), }; Some(dir.join(&p.name).display().to_string()) } else { @@ -808,13 +838,24 @@ fn main() -> Result<()> { info!("Publisher completed successfully"); Ok(()) }, - Commands::ImageCreate { full_path, publisher, origin } => { + Commands::ImageCreate { full_path, publisher, origin, image_type } => { info!("Creating image at: {}", full_path.display()); debug!("Publisher: {:?}", publisher); debug!("Origin: {:?}", origin); + debug!("Image type: {}", image_type); + + // Convert the image type string to the ImageType enum + let image_type = match image_type.to_lowercase().as_str() { + "full" => libips::image::ImageType::Full, + "partial" => libips::image::ImageType::Partial, + _ => { + error!("Invalid image type: {}. Using default (full)", image_type); + libips::image::ImageType::Full + } + }; // Create the image (only creates the basic structure) - let mut image = libips::image::Image::create_image(&full_path)?; + let mut image = libips::image::Image::create_image(&full_path, image_type)?; info!("Image created successfully at: {}", full_path.display()); // If publisher and origin are provided, add the publisher and download the catalog