mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +00:00
Introduce PackageInfo struct for structured package data, refactor list_packages to use it, and enhance publisher directory handling
Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
parent
7332e0f7b5
commit
e3662eaf23
5 changed files with 145 additions and 49 deletions
|
|
@ -14,12 +14,13 @@ use std::fs::File;
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use flate2::Compression as GzipCompression;
|
use flate2::Compression as GzipCompression;
|
||||||
use lz4::EncoderBuilder;
|
use lz4::EncoderBuilder;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::actions::{Manifest, File as FileAction};
|
use crate::actions::{Manifest, File as FileAction};
|
||||||
use crate::digest::Digest;
|
use crate::digest::Digest;
|
||||||
use crate::payload::{Payload, PayloadCompressionAlgorithm};
|
use crate::payload::{Payload, PayloadCompressionAlgorithm};
|
||||||
|
|
||||||
use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo};
|
use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo, PackageInfo};
|
||||||
|
|
||||||
/// Repository implementation that uses the local filesystem
|
/// Repository implementation that uses the local filesystem
|
||||||
pub struct FileBackend {
|
pub struct FileBackend {
|
||||||
|
|
@ -369,6 +370,22 @@ impl Repository for FileBackend {
|
||||||
if !dry_run {
|
if !dry_run {
|
||||||
self.config.publishers.remove(pos);
|
self.config.publishers.remove(pos);
|
||||||
|
|
||||||
|
// Remove publisher-specific directories and their contents recursively
|
||||||
|
let catalog_dir = self.path.join("catalog").join(publisher);
|
||||||
|
let pkg_dir = self.path.join("pkg").join(publisher);
|
||||||
|
|
||||||
|
// Remove the catalog directory if it exists
|
||||||
|
if catalog_dir.exists() {
|
||||||
|
fs::remove_dir_all(&catalog_dir)
|
||||||
|
.map_err(|e| anyhow!("Failed to remove catalog directory: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the package directory if it exists
|
||||||
|
if pkg_dir.exists() {
|
||||||
|
fs::remove_dir_all(&pkg_dir)
|
||||||
|
.map_err(|e| anyhow!("Failed to remove package directory: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
// Save the updated configuration
|
// Save the updated configuration
|
||||||
self.save_config()?;
|
self.save_config()?;
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +480,7 @@ impl Repository for FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List packages in the repository
|
/// List packages in the repository
|
||||||
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<(String, String, String)>> {
|
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>> {
|
||||||
let mut packages = Vec::new();
|
let mut packages = Vec::new();
|
||||||
|
|
||||||
// Filter publishers if specified
|
// Filter publishers if specified
|
||||||
|
|
@ -478,25 +495,91 @@ impl Repository for FileBackend {
|
||||||
|
|
||||||
// For each publisher, list packages
|
// For each publisher, list packages
|
||||||
for pub_name in publishers {
|
for pub_name in publishers {
|
||||||
// In a real implementation, we would scan the repository for packages
|
// Get the publisher's package directory
|
||||||
// For now, we'll just return a placeholder
|
let publisher_pkg_dir = self.path.join("pkg").join(&pub_name);
|
||||||
|
|
||||||
// Example package data (name, version, publisher)
|
// Check if the publisher directory exists
|
||||||
let example_packages = vec![
|
if publisher_pkg_dir.exists() {
|
||||||
("example/package1".to_string(), "1.0.0".to_string(), pub_name.clone()),
|
// Verify that the publisher is in the config
|
||||||
("example/package2".to_string(), "2.0.0".to_string(), pub_name.clone()),
|
if !self.config.publishers.contains(&pub_name) {
|
||||||
];
|
return Err(anyhow!("Publisher directory exists but is not in the repository configuration: {}", pub_name));
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by pattern if specified
|
// Walk through the directory and collect package manifests
|
||||||
let filtered_packages = if let Some(pat) = pattern {
|
if let Ok(entries) = fs::read_dir(&publisher_pkg_dir) {
|
||||||
example_packages.into_iter()
|
for entry in entries.flatten() {
|
||||||
.filter(|(name, _, _)| name.contains(pat))
|
let path = entry.path();
|
||||||
.collect()
|
|
||||||
|
// Skip directories, only process files (package manifests)
|
||||||
|
if path.is_file() {
|
||||||
|
// Parse the manifest file to get real package information
|
||||||
|
match Manifest::parse_file(&path) {
|
||||||
|
Ok(manifest) => {
|
||||||
|
// Look for the pkg.fmri attribute
|
||||||
|
for attr in &manifest.attributes {
|
||||||
|
if attr.key == "pkg.fmri" && !attr.values.is_empty() {
|
||||||
|
let fmri = &attr.values[0];
|
||||||
|
|
||||||
|
// Parse the FMRI to extract package name and version
|
||||||
|
// Format: pkg://publisher/package_name@version
|
||||||
|
if let Some(pkg_part) = fmri.strip_prefix("pkg://") {
|
||||||
|
if let Some(at_pos) = pkg_part.find('@') {
|
||||||
|
let pkg_with_pub = &pkg_part[0..at_pos];
|
||||||
|
let version = &pkg_part[at_pos+1..];
|
||||||
|
|
||||||
|
// Extract package name (may include publisher)
|
||||||
|
let pkg_name = if let Some(slash_pos) = pkg_with_pub.find('/') {
|
||||||
|
// Skip publisher part if present
|
||||||
|
let pub_end = slash_pos + 1;
|
||||||
|
&pkg_with_pub[pub_end..]
|
||||||
} else {
|
} else {
|
||||||
example_packages
|
pkg_with_pub
|
||||||
};
|
};
|
||||||
|
|
||||||
packages.extend(filtered_packages);
|
// Filter by pattern if specified
|
||||||
|
if let Some(pat) = pattern {
|
||||||
|
// Try to compile the pattern as a regex
|
||||||
|
match Regex::new(pat) {
|
||||||
|
Ok(regex) => {
|
||||||
|
// Use regex matching
|
||||||
|
if !regex.is_match(pkg_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
// Log the error but fall back to simple string contains
|
||||||
|
eprintln!("Error compiling regex pattern '{}': {}", pat, err);
|
||||||
|
if !pkg_name.contains(pat) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a PackageInfo struct and add it to the list
|
||||||
|
packages.push(PackageInfo {
|
||||||
|
name: pkg_name.to_string(),
|
||||||
|
version: version.to_string(),
|
||||||
|
publisher: pub_name.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Found the package info, no need to check other attributes
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
// Log the error but continue processing other files
|
||||||
|
eprintln!("Error parsing manifest file {}: {}", path.display(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No else clause - we don't return placeholder data anymore
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(packages)
|
Ok(packages)
|
||||||
|
|
@ -513,11 +596,11 @@ impl Repository for FileBackend {
|
||||||
// For each package, list contents
|
// For each package, list contents
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
|
|
||||||
for (pkg_name, pkg_version, _pub_name) in packages {
|
for pkg_info in packages {
|
||||||
// Example content data (package, path, type)
|
// Example content data (package, path, type)
|
||||||
let example_contents = vec![
|
let example_contents = vec![
|
||||||
(format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()),
|
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/bin/example".to_string(), "file".to_string()),
|
||||||
(format!("{}@{}", pkg_name, pkg_version), "/usr/share/doc/example".to_string(), "dir".to_string()),
|
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/share/doc/example".to_string(), "dir".to_string()),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filter by action type if specified
|
// Filter by action type if specified
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,17 @@ pub struct RepositoryInfo {
|
||||||
pub publishers: Vec<PublisherInfo>,
|
pub publishers: Vec<PublisherInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information about a package in a repository
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PackageInfo {
|
||||||
|
/// Name of the package
|
||||||
|
pub name: String,
|
||||||
|
/// Version of the package
|
||||||
|
pub version: String,
|
||||||
|
/// Publisher of the package
|
||||||
|
pub publisher: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Repository version
|
/// Repository version
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum RepositoryVersion {
|
pub enum RepositoryVersion {
|
||||||
|
|
@ -106,7 +117,7 @@ pub trait Repository {
|
||||||
fn set_publisher_property(&mut self, publisher: &str, property: &str, value: &str) -> Result<()>;
|
fn set_publisher_property(&mut self, publisher: &str, property: &str, value: &str) -> Result<()>;
|
||||||
|
|
||||||
/// List packages in the repository
|
/// List packages in the repository
|
||||||
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<(String, String, String)>>;
|
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>>;
|
||||||
|
|
||||||
/// Show contents of packages
|
/// Show contents of packages
|
||||||
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>>;
|
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>>;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo};
|
use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo, PackageInfo};
|
||||||
|
|
||||||
/// Repository implementation that uses a REST API
|
/// Repository implementation that uses a REST API
|
||||||
pub struct RestBackend {
|
pub struct RestBackend {
|
||||||
|
|
@ -164,7 +164,7 @@ impl Repository for RestBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List packages in the repository
|
/// List packages in the repository
|
||||||
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<(String, String, String)>> {
|
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>> {
|
||||||
// This is a stub implementation
|
// This is a stub implementation
|
||||||
// In a real implementation, we would make a REST API call to list packages
|
// In a real implementation, we would make a REST API call to list packages
|
||||||
|
|
||||||
|
|
@ -182,24 +182,16 @@ impl Repository for RestBackend {
|
||||||
|
|
||||||
// For each publisher, list packages
|
// For each publisher, list packages
|
||||||
for pub_name in publishers {
|
for pub_name in publishers {
|
||||||
// In a real implementation, we would get this information from the REST API
|
// In a real implementation, we would make a REST API call to get package information
|
||||||
|
// The API call would return a list of packages with their names, versions, and other metadata
|
||||||
|
// We would then parse this information and create PackageInfo structs
|
||||||
|
|
||||||
// Example package data (name, version, publisher)
|
// For now, we return an empty list since we don't want to return placeholder data
|
||||||
let example_packages = vec![
|
// and we don't have a real API to call
|
||||||
("example/package1".to_string(), "1.0.0".to_string(), pub_name.clone()),
|
|
||||||
("example/package2".to_string(), "2.0.0".to_string(), pub_name.clone()),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filter by pattern if specified
|
// If pattern filtering is needed, it would be applied here to the results from the API
|
||||||
let filtered_packages = if let Some(pat) = pattern {
|
// When implementing, use the regex crate to handle user-provided regexp patterns properly,
|
||||||
example_packages.into_iter()
|
// similar to the implementation in file_backend.rs
|
||||||
.filter(|(name, _, _)| name.contains(pat))
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
example_packages
|
|
||||||
};
|
|
||||||
|
|
||||||
packages.extend(filtered_packages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(packages)
|
Ok(packages)
|
||||||
|
|
@ -216,13 +208,13 @@ impl Repository for RestBackend {
|
||||||
// For each package, list contents
|
// For each package, list contents
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
|
|
||||||
for (pkg_name, pkg_version, _) in packages {
|
for pkg_info in packages {
|
||||||
// In a real implementation, we would get this information from the REST API
|
// In a real implementation, we would get this information from the REST API
|
||||||
|
|
||||||
// Example content data (package, path, type)
|
// Example content data (package, path, type)
|
||||||
let example_contents = vec![
|
let example_contents = vec![
|
||||||
(format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()),
|
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/bin/example".to_string(), "file".to_string()),
|
||||||
(format!("{}@{}", pkg_name, pkg_version), "/usr/share/doc/example".to_string(), "dir".to_string()),
|
(format!("{}@{}", pkg_info.name, pkg_info.version), "/usr/share/doc/example".to_string(), "dir".to_string()),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filter by action type if specified
|
// Filter by action type if specified
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use anyhow::{Result, anyhow};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo};
|
use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo, PackageInfo};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
@ -377,8 +377,8 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print packages
|
// Print packages
|
||||||
for (name, version, publisher) in packages {
|
for pkg_info in packages {
|
||||||
println!("{:<30} {:<15} {:<10}", name, version, publisher);
|
println!("{:<30} {:<15} {:<10}", pkg_info.name, pkg_info.version, pkg_info.publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -91,12 +91,22 @@ mod tests {
|
||||||
// Check that the publisher was added
|
// Check that the publisher was added
|
||||||
assert!(repo.config.publishers.contains(&"example.com".to_string()));
|
assert!(repo.config.publishers.contains(&"example.com".to_string()));
|
||||||
|
|
||||||
|
// Check that the publisher directories were created
|
||||||
|
let catalog_dir = repo_path.join("catalog").join("example.com");
|
||||||
|
let pkg_dir = repo_path.join("pkg").join("example.com");
|
||||||
|
assert!(catalog_dir.exists(), "Catalog directory should exist after adding publisher");
|
||||||
|
assert!(pkg_dir.exists(), "Package directory should exist after adding publisher");
|
||||||
|
|
||||||
// Remove the publisher
|
// Remove the publisher
|
||||||
repo.remove_publisher("example.com", false).unwrap();
|
repo.remove_publisher("example.com", false).unwrap();
|
||||||
|
|
||||||
// Check that the publisher was removed
|
// Check that the publisher was removed from the configuration
|
||||||
assert!(!repo.config.publishers.contains(&"example.com".to_string()));
|
assert!(!repo.config.publishers.contains(&"example.com".to_string()));
|
||||||
|
|
||||||
|
// Check that the publisher directories were removed
|
||||||
|
assert!(!catalog_dir.exists(), "Catalog directory should not exist after removing publisher");
|
||||||
|
assert!(!pkg_dir.exists(), "Package directory should not exist after removing publisher");
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
cleanup_test_dir(&test_dir);
|
cleanup_test_dir(&test_dir);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue