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::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
|
||||
pub struct FileBackend {
|
||||
|
|
@ -27,6 +27,27 @@ pub struct FileBackend {
|
|||
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
|
||||
pub struct Transaction {
|
||||
/// Unique ID for the transaction
|
||||
|
|
@ -357,23 +378,62 @@ impl Repository for FileBackend {
|
|||
}
|
||||
|
||||
/// Get repository information
|
||||
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>> {
|
||||
let mut info = Vec::new();
|
||||
fn get_info(&self) -> Result<RepositoryInfo> {
|
||||
let mut publishers = Vec::new();
|
||||
|
||||
for publisher in &self.config.publishers {
|
||||
// Count packages (this is a placeholder, actual implementation would count packages)
|
||||
let package_count = 0;
|
||||
for publisher_name in &self.config.publishers {
|
||||
// Count packages by scanning the pkg/<publisher> directory
|
||||
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();
|
||||
|
||||
// Updated timestamp (placeholder)
|
||||
let updated = "2025-07-21T18:46:00.000000Z".to_string();
|
||||
// Format the timestamp in ISO 8601 format
|
||||
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
|
||||
|
|
|
|||
|
|
@ -16,6 +16,26 @@ pub use rest_backend::RestBackend;
|
|||
/// Repository configuration filename
|
||||
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
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum RepositoryVersion {
|
||||
|
|
@ -77,7 +97,7 @@ pub trait Repository {
|
|||
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()>;
|
||||
|
||||
/// Get repository information
|
||||
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>>;
|
||||
fn get_info(&self) -> Result<RepositoryInfo>;
|
||||
|
||||
/// Set a repository property
|
||||
fn set_property(&mut self, property: &str, value: &str) -> Result<()>;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{Repository, RepositoryConfig, RepositoryVersion};
|
||||
use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo};
|
||||
|
||||
/// Repository implementation that uses a REST API
|
||||
pub struct RestBackend {
|
||||
|
|
@ -105,22 +105,29 @@ impl Repository for RestBackend {
|
|||
}
|
||||
|
||||
/// Get repository information
|
||||
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>> {
|
||||
fn get_info(&self) -> Result<RepositoryInfo> {
|
||||
// This is a stub implementation
|
||||
// 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
|
||||
let package_count = 0;
|
||||
let status = "online".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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use anyhow::{Result, anyhow};
|
|||
use std::path::PathBuf;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use libips::repository::{Repository, RepositoryVersion, FileBackend};
|
||||
use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
@ -321,7 +321,7 @@ fn main() -> Result<()> {
|
|||
let repo = FileBackend::open(repo_uri_or_path)?;
|
||||
|
||||
// Get repository info
|
||||
let info = repo.get_info()?;
|
||||
let repo_info = repo.get_info()?;
|
||||
|
||||
// Print headers if not omitted
|
||||
if !omit_headers {
|
||||
|
|
@ -329,8 +329,13 @@ fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
// Print repository info
|
||||
for (pub_name, pkg_count, status, updated) in info {
|
||||
println!("{:<10} {:<8} {:<6} {:<30}", pub_name, pkg_count, status, updated);
|
||||
for publisher_info in repo_info.publishers {
|
||||
println!("{:<10} {:<8} {:<6} {:<30}",
|
||||
publisher_info.name,
|
||||
publisher_info.package_count,
|
||||
publisher_info.status,
|
||||
publisher_info.updated
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,13 +1,43 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use libips::repository::{Repository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME};
|
||||
use tempfile::tempdir;
|
||||
use libips::repository::{Repository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo};
|
||||
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]
|
||||
fn test_create_repository() {
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let repo_path = temp_dir.path().join("repo");
|
||||
// Create a real test directory
|
||||
let test_dir = create_test_dir("create_repository");
|
||||
let repo_path = test_dir.join("repo");
|
||||
|
||||
// Create a repository
|
||||
let _ = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||
|
|
@ -20,13 +50,16 @@ mod tests {
|
|||
assert!(repo_path.join("pkg").exists());
|
||||
assert!(repo_path.join("trans").exists());
|
||||
assert!(repo_path.join(REPOSITORY_CONFIG_FILENAME).exists());
|
||||
|
||||
// Clean up
|
||||
cleanup_test_dir(&test_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_publisher() {
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let repo_path = temp_dir.path().join("repo");
|
||||
// Create a real test directory
|
||||
let test_dir = create_test_dir("add_publisher");
|
||||
let repo_path = test_dir.join("repo");
|
||||
|
||||
// Create a repository
|
||||
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_path.join("catalog").join("example.com").exists());
|
||||
assert!(repo_path.join("pkg").join("example.com").exists());
|
||||
|
||||
// Clean up
|
||||
cleanup_test_dir(&test_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_publisher() {
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let repo_path = temp_dir.path().join("repo");
|
||||
// Create a real test directory
|
||||
let test_dir = create_test_dir("remove_publisher");
|
||||
let repo_path = test_dir.join("repo");
|
||||
|
||||
// Create a repository
|
||||
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||
|
|
@ -60,13 +96,16 @@ mod tests {
|
|||
|
||||
// Check that the publisher was removed
|
||||
assert!(!repo.config.publishers.contains(&"example.com".to_string()));
|
||||
|
||||
// Clean up
|
||||
cleanup_test_dir(&test_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_property() {
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let repo_path = temp_dir.path().join("repo");
|
||||
// Create a real test directory
|
||||
let test_dir = create_test_dir("set_property");
|
||||
let repo_path = test_dir.join("repo");
|
||||
|
||||
// Create a repository
|
||||
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||
|
|
@ -76,5 +115,34 @@ mod tests {
|
|||
|
||||
// Check that the property was set
|
||||
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