mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +00:00
Refactor repository info retrieval to return structured data, add detailed publisher information, and update tests accordingly
Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
parent
a9584fa6d2
commit
7332e0f7b5
5 changed files with 196 additions and 36 deletions
|
|
@ -19,7 +19,7 @@ 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};
|
use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo};
|
||||||
|
|
||||||
/// Repository implementation that uses the local filesystem
|
/// Repository implementation that uses the local filesystem
|
||||||
pub struct FileBackend {
|
pub struct FileBackend {
|
||||||
|
|
@ -27,6 +27,27 @@ pub struct FileBackend {
|
||||||
pub config: RepositoryConfig,
|
pub config: RepositoryConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a SystemTime as an ISO 8601 timestamp string
|
||||||
|
fn format_iso8601_timestamp(time: &SystemTime) -> String {
|
||||||
|
let duration = time.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap_or_else(|_| std::time::Duration::from_secs(0));
|
||||||
|
|
||||||
|
let secs = duration.as_secs();
|
||||||
|
let micros = duration.subsec_micros();
|
||||||
|
|
||||||
|
// Format as ISO 8601 with microsecond precision
|
||||||
|
format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
|
||||||
|
// Convert seconds to date and time components
|
||||||
|
1970 + secs / 31536000, // year (approximate)
|
||||||
|
(secs % 31536000) / 2592000 + 1, // month (approximate)
|
||||||
|
(secs % 2592000) / 86400 + 1, // day (approximate)
|
||||||
|
(secs % 86400) / 3600, // hour
|
||||||
|
(secs % 3600) / 60, // minute
|
||||||
|
secs % 60, // second
|
||||||
|
micros // microseconds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Transaction for publishing packages
|
/// Transaction for publishing packages
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
/// Unique ID for the transaction
|
/// Unique ID for the transaction
|
||||||
|
|
@ -357,23 +378,62 @@ impl Repository for FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get repository information
|
/// Get repository information
|
||||||
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>> {
|
fn get_info(&self) -> Result<RepositoryInfo> {
|
||||||
let mut info = Vec::new();
|
let mut publishers = Vec::new();
|
||||||
|
|
||||||
for publisher in &self.config.publishers {
|
for publisher_name in &self.config.publishers {
|
||||||
// Count packages (this is a placeholder, actual implementation would count packages)
|
// Count packages by scanning the pkg/<publisher> directory
|
||||||
let package_count = 0;
|
let publisher_pkg_dir = self.path.join("pkg").join(publisher_name);
|
||||||
|
let mut package_count = 0;
|
||||||
|
let mut latest_timestamp = SystemTime::UNIX_EPOCH;
|
||||||
|
|
||||||
// Status is always "online" for now
|
// Check if the publisher directory exists
|
||||||
|
if publisher_pkg_dir.exists() {
|
||||||
|
// Walk through the directory and count package manifests
|
||||||
|
if let Ok(entries) = fs::read_dir(&publisher_pkg_dir) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
// Skip directories, only count files (package manifests)
|
||||||
|
if path.is_file() {
|
||||||
|
package_count += 1;
|
||||||
|
|
||||||
|
// Update the latest timestamp if this file is newer
|
||||||
|
if let Ok(metadata) = fs::metadata(&path) {
|
||||||
|
if let Ok(modified) = metadata.modified() {
|
||||||
|
if modified > latest_timestamp {
|
||||||
|
latest_timestamp = modified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status is always "online" for file-based repositories
|
||||||
let status = "online".to_string();
|
let status = "online".to_string();
|
||||||
|
|
||||||
// Updated timestamp (placeholder)
|
// Format the timestamp in ISO 8601 format
|
||||||
let updated = "2025-07-21T18:46:00.000000Z".to_string();
|
let updated = if latest_timestamp == SystemTime::UNIX_EPOCH {
|
||||||
|
// If no files were found, use current time
|
||||||
|
let now = SystemTime::now();
|
||||||
|
format_iso8601_timestamp(&now)
|
||||||
|
} else {
|
||||||
|
format_iso8601_timestamp(&latest_timestamp)
|
||||||
|
};
|
||||||
|
|
||||||
info.push((publisher.clone(), package_count, status, updated));
|
// Create a PublisherInfo struct and add it to the list
|
||||||
|
publishers.push(PublisherInfo {
|
||||||
|
name: publisher_name.clone(),
|
||||||
|
package_count,
|
||||||
|
status,
|
||||||
|
updated,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(info)
|
// Create and return a RepositoryInfo struct
|
||||||
|
Ok(RepositoryInfo { publishers })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a repository property
|
/// Set a repository property
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,26 @@ pub use rest_backend::RestBackend;
|
||||||
/// Repository configuration filename
|
/// Repository configuration filename
|
||||||
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
|
||||||
|
|
||||||
|
/// Information about a publisher in a repository
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PublisherInfo {
|
||||||
|
/// Name of the publisher
|
||||||
|
pub name: String,
|
||||||
|
/// Number of packages from this publisher
|
||||||
|
pub package_count: usize,
|
||||||
|
/// Status of the publisher (e.g., "online", "offline")
|
||||||
|
pub status: String,
|
||||||
|
/// Last updated timestamp in ISO 8601 format
|
||||||
|
pub updated: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a repository
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RepositoryInfo {
|
||||||
|
/// Information about publishers in the repository
|
||||||
|
pub publishers: Vec<PublisherInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 {
|
||||||
|
|
@ -77,7 +97,7 @@ pub trait Repository {
|
||||||
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()>;
|
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()>;
|
||||||
|
|
||||||
/// Get repository information
|
/// Get repository information
|
||||||
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>>;
|
fn get_info(&self) -> Result<RepositoryInfo>;
|
||||||
|
|
||||||
/// Set a repository property
|
/// Set a repository property
|
||||||
fn set_property(&mut self, property: &str, value: &str) -> Result<()>;
|
fn set_property(&mut self, property: &str, value: &str) -> Result<()>;
|
||||||
|
|
|
||||||
|
|
@ -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};
|
use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo};
|
||||||
|
|
||||||
/// Repository implementation that uses a REST API
|
/// Repository implementation that uses a REST API
|
||||||
pub struct RestBackend {
|
pub struct RestBackend {
|
||||||
|
|
@ -105,22 +105,29 @@ impl Repository for RestBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get repository information
|
/// Get repository information
|
||||||
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>> {
|
fn get_info(&self) -> Result<RepositoryInfo> {
|
||||||
// This is a stub implementation
|
// This is a stub implementation
|
||||||
// In a real implementation, we would make a REST API call to get repository information
|
// In a real implementation, we would make a REST API call to get repository information
|
||||||
|
|
||||||
let mut info = Vec::new();
|
let mut publishers = Vec::new();
|
||||||
|
|
||||||
for publisher in &self.config.publishers {
|
for publisher_name in &self.config.publishers {
|
||||||
// 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
|
||||||
let package_count = 0;
|
let package_count = 0;
|
||||||
let status = "online".to_string();
|
let status = "online".to_string();
|
||||||
let updated = "2025-07-21T18:46:00.000000Z".to_string();
|
let updated = "2025-07-21T18:46:00.000000Z".to_string();
|
||||||
|
|
||||||
info.push((publisher.clone(), package_count, status, updated));
|
// Create a PublisherInfo struct and add it to the list
|
||||||
|
publishers.push(PublisherInfo {
|
||||||
|
name: publisher_name.clone(),
|
||||||
|
package_count,
|
||||||
|
status,
|
||||||
|
updated,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(info)
|
// Create and return a RepositoryInfo struct
|
||||||
|
Ok(RepositoryInfo { publishers })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a repository property
|
/// Set a repository property
|
||||||
|
|
|
||||||
|
|
@ -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};
|
use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
@ -321,7 +321,7 @@ fn main() -> Result<()> {
|
||||||
let repo = FileBackend::open(repo_uri_or_path)?;
|
let repo = FileBackend::open(repo_uri_or_path)?;
|
||||||
|
|
||||||
// Get repository info
|
// Get repository info
|
||||||
let info = repo.get_info()?;
|
let repo_info = repo.get_info()?;
|
||||||
|
|
||||||
// Print headers if not omitted
|
// Print headers if not omitted
|
||||||
if !omit_headers {
|
if !omit_headers {
|
||||||
|
|
@ -329,8 +329,13 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print repository info
|
// Print repository info
|
||||||
for (pub_name, pkg_count, status, updated) in info {
|
for publisher_info in repo_info.publishers {
|
||||||
println!("{:<10} {:<8} {:<6} {:<30}", pub_name, pkg_count, status, updated);
|
println!("{:<10} {:<8} {:<6} {:<30}",
|
||||||
|
publisher_info.name,
|
||||||
|
publisher_info.package_count,
|
||||||
|
publisher_info.status,
|
||||||
|
publisher_info.updated
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,43 @@
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use libips::repository::{Repository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME};
|
use libips::repository::{Repository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo};
|
||||||
use tempfile::tempdir;
|
use std::path::PathBuf;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
// These tests interact with real repositories in a known location
|
||||||
|
// instead of using temporary directories. This allows for better
|
||||||
|
// debugging and inspection of the repositories during testing.
|
||||||
|
|
||||||
|
// The base directory for all test repositories
|
||||||
|
const TEST_REPO_BASE_DIR: &str = "/tmp/pkg6repo_test";
|
||||||
|
|
||||||
|
// Helper function to create a unique test directory
|
||||||
|
fn create_test_dir(test_name: &str) -> PathBuf {
|
||||||
|
let test_dir = PathBuf::from(format!("{}/{}", TEST_REPO_BASE_DIR, test_name));
|
||||||
|
|
||||||
|
// Clean up any existing directory
|
||||||
|
if test_dir.exists() {
|
||||||
|
fs::remove_dir_all(&test_dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the directory
|
||||||
|
fs::create_dir_all(&test_dir).unwrap();
|
||||||
|
|
||||||
|
test_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to clean up test directory
|
||||||
|
fn cleanup_test_dir(test_dir: &PathBuf) {
|
||||||
|
if test_dir.exists() {
|
||||||
|
fs::remove_dir_all(test_dir).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_repository() {
|
fn test_create_repository() {
|
||||||
// Create a temporary directory for the test
|
// Create a real test directory
|
||||||
let temp_dir = tempdir().unwrap();
|
let test_dir = create_test_dir("create_repository");
|
||||||
let repo_path = temp_dir.path().join("repo");
|
let repo_path = test_dir.join("repo");
|
||||||
|
|
||||||
// Create a repository
|
// Create a repository
|
||||||
let _ = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
let _ = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||||
|
|
@ -20,13 +50,16 @@ mod tests {
|
||||||
assert!(repo_path.join("pkg").exists());
|
assert!(repo_path.join("pkg").exists());
|
||||||
assert!(repo_path.join("trans").exists());
|
assert!(repo_path.join("trans").exists());
|
||||||
assert!(repo_path.join(REPOSITORY_CONFIG_FILENAME).exists());
|
assert!(repo_path.join(REPOSITORY_CONFIG_FILENAME).exists());
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
cleanup_test_dir(&test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_publisher() {
|
fn test_add_publisher() {
|
||||||
// Create a temporary directory for the test
|
// Create a real test directory
|
||||||
let temp_dir = tempdir().unwrap();
|
let test_dir = create_test_dir("add_publisher");
|
||||||
let repo_path = temp_dir.path().join("repo");
|
let repo_path = test_dir.join("repo");
|
||||||
|
|
||||||
// Create a repository
|
// Create a repository
|
||||||
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||||
|
|
@ -38,13 +71,16 @@ mod tests {
|
||||||
assert!(repo.config.publishers.contains(&"example.com".to_string()));
|
assert!(repo.config.publishers.contains(&"example.com".to_string()));
|
||||||
assert!(repo_path.join("catalog").join("example.com").exists());
|
assert!(repo_path.join("catalog").join("example.com").exists());
|
||||||
assert!(repo_path.join("pkg").join("example.com").exists());
|
assert!(repo_path.join("pkg").join("example.com").exists());
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
cleanup_test_dir(&test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_remove_publisher() {
|
fn test_remove_publisher() {
|
||||||
// Create a temporary directory for the test
|
// Create a real test directory
|
||||||
let temp_dir = tempdir().unwrap();
|
let test_dir = create_test_dir("remove_publisher");
|
||||||
let repo_path = temp_dir.path().join("repo");
|
let repo_path = test_dir.join("repo");
|
||||||
|
|
||||||
// Create a repository
|
// Create a repository
|
||||||
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||||
|
|
@ -60,13 +96,16 @@ mod tests {
|
||||||
|
|
||||||
// Check that the publisher was removed
|
// Check that the publisher was removed
|
||||||
assert!(!repo.config.publishers.contains(&"example.com".to_string()));
|
assert!(!repo.config.publishers.contains(&"example.com".to_string()));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
cleanup_test_dir(&test_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_property() {
|
fn test_set_property() {
|
||||||
// Create a temporary directory for the test
|
// Create a real test directory
|
||||||
let temp_dir = tempdir().unwrap();
|
let test_dir = create_test_dir("set_property");
|
||||||
let repo_path = temp_dir.path().join("repo");
|
let repo_path = test_dir.join("repo");
|
||||||
|
|
||||||
// Create a repository
|
// Create a repository
|
||||||
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||||
|
|
@ -76,5 +115,34 @@ mod tests {
|
||||||
|
|
||||||
// Check that the property was set
|
// Check that the property was set
|
||||||
assert_eq!(repo.config.properties.get("publisher/prefix").unwrap(), "example.com");
|
assert_eq!(repo.config.properties.get("publisher/prefix").unwrap(), "example.com");
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
cleanup_test_dir(&test_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_info() {
|
||||||
|
// Create a real test directory
|
||||||
|
let test_dir = create_test_dir("get_info");
|
||||||
|
let repo_path = test_dir.join("repo");
|
||||||
|
|
||||||
|
// Create a repository
|
||||||
|
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||||
|
|
||||||
|
// Add a publisher
|
||||||
|
repo.add_publisher("example.com").unwrap();
|
||||||
|
|
||||||
|
// Get repository information
|
||||||
|
let repo_info = repo.get_info().unwrap();
|
||||||
|
|
||||||
|
// Check that the information is correct
|
||||||
|
assert_eq!(repo_info.publishers.len(), 1);
|
||||||
|
let publisher_info = &repo_info.publishers[0];
|
||||||
|
assert_eq!(publisher_info.name, "example.com");
|
||||||
|
assert_eq!(publisher_info.package_count, 0); // No packages yet
|
||||||
|
assert_eq!(publisher_info.status, "online");
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
cleanup_test_dir(&test_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue