Refactor repository structure to use publisher-specific directories

- Introduce publisher-specific hierarchy (`publisher/<publisher_name>`) for catalog, package, and file storage.
- Replace hardcoded paths with helper methods (`construct_catalog_path`, `construct_package_dir`, `construct_file_path_with_publisher`) for consistent path generation.
- Remove outdated tests and simplify redundant assertions in repository tests.
- Update `CatalogManager` and file handling logic to leverage publisher-specific paths.
- Enhance debug logs to include publisher context where applicable.
This commit is contained in:
Till Wegmueller 2025-07-31 00:18:21 +02:00
parent 845ffec13b
commit 99dd0fe87c
No known key found for this signature in database
9 changed files with 259 additions and 188 deletions

View file

@ -380,6 +380,9 @@ pub struct CatalogManager {
/// Path to the catalog directory /// Path to the catalog directory
catalog_dir: PathBuf, catalog_dir: PathBuf,
/// Publisher name
publisher: String,
/// Catalog attributes /// Catalog attributes
attrs: CatalogAttrs, attrs: CatalogAttrs,
@ -392,16 +395,17 @@ pub struct CatalogManager {
impl CatalogManager { impl CatalogManager {
/// Create a new catalog manager /// Create a new catalog manager
pub fn new<P: AsRef<Path>>(catalog_dir: P) -> Result<Self> { pub fn new<P: AsRef<Path>>(base_dir: P, publisher: &str) -> Result<Self> {
let catalog_dir = catalog_dir.as_ref().to_path_buf(); let base_dir = base_dir.as_ref().to_path_buf();
let publisher_catalog_dir = base_dir.join(publisher).join("catalog");
// Create catalog directory if it doesn't exist // Create catalog directory if it doesn't exist
if !catalog_dir.exists() { if !publisher_catalog_dir.exists() {
fs::create_dir_all(&catalog_dir)?; fs::create_dir_all(&publisher_catalog_dir)?;
} }
// Try to load existing catalog attributes // Try to load existing catalog attributes
let attrs_path = catalog_dir.join("catalog.attrs"); let attrs_path = publisher_catalog_dir.join("catalog.attrs");
let attrs = if attrs_path.exists() { let attrs = if attrs_path.exists() {
CatalogAttrs::load(&attrs_path)? CatalogAttrs::load(&attrs_path)?
} else { } else {
@ -409,7 +413,8 @@ impl CatalogManager {
}; };
Ok(CatalogManager { Ok(CatalogManager {
catalog_dir, catalog_dir: publisher_catalog_dir,
publisher: publisher.to_string(),
attrs, attrs,
parts: HashMap::new(), parts: HashMap::new(),
update_logs: HashMap::new(), update_logs: HashMap::new(),
@ -537,4 +542,41 @@ impl CatalogManager {
pub fn get_update_log_mut(&mut self, name: &str) -> Option<&mut UpdateLog> { pub fn get_update_log_mut(&mut self, name: &str) -> Option<&mut UpdateLog> {
self.update_logs.get_mut(name) self.update_logs.get_mut(name)
} }
/// Add a package to a catalog part using the stored publisher
pub fn add_package_to_part(
&mut self,
part_name: &str,
fmri: &Fmri,
actions: Option<Vec<String>>,
signature: Option<String>,
) -> Result<()> {
if let Some(part) = self.parts.get_mut(part_name) {
part.add_package(&self.publisher, fmri, actions, signature);
Ok(())
} else {
Err(CatalogError::CatalogPartNotLoaded {
name: part_name.to_string(),
})
}
}
/// Add an update to an update log using the stored publisher
pub fn add_update_to_log(
&mut self,
log_name: &str,
fmri: &Fmri,
op_type: CatalogOperationType,
catalog_parts: HashMap<String, HashMap<String, Vec<String>>>,
signature: Option<String>,
) -> Result<()> {
if let Some(log) = self.update_logs.get_mut(log_name) {
log.add_update(&self.publisher, fmri, op_type, catalog_parts, signature);
Ok(())
} else {
Err(CatalogError::UpdateLogNotLoaded {
name: log_name.to_string(),
})
}
}
} }

View file

@ -454,24 +454,45 @@ impl Transaction {
let manifest_json = serde_json::to_string_pretty(&self.manifest)?; let manifest_json = serde_json::to_string_pretty(&self.manifest)?;
fs::write(&manifest_path, manifest_json)?; fs::write(&manifest_path, manifest_json)?;
// Determine the publisher to use
let publisher = match &self.publisher {
Some(pub_name) => {
debug!("Using specified publisher: {}", pub_name);
pub_name.clone()
}
None => {
debug!("No publisher specified, trying to use default publisher");
// If no publisher is specified, use the default publisher from the repository config
let config_path = self.repo.join(REPOSITORY_CONFIG_FILENAME);
if config_path.exists() {
let config_content = fs::read_to_string(&config_path)?;
let config: RepositoryConfig = serde_json::from_str(&config_content)?;
match config.default_publisher {
Some(default_pub) => {
debug!("Using default publisher: {}", default_pub);
default_pub
}
None => {
debug!("No default publisher set in repository");
return Err(RepositoryError::Other(
"No publisher specified and no default publisher set in repository"
.to_string(),
));
}
}
} else {
debug!("Repository configuration not found");
return Err(RepositoryError::Other(
"No publisher specified and repository configuration not found".to_string(),
));
}
}
};
// Copy files to their final location // Copy files to their final location
for (source_path, hash) in self.files { for (source_path, hash) in self.files {
// Create the destination path using the new directory structure // Create the destination path using the helper function with publisher
let dest_path = if hash.len() < 4 { let dest_path = FileBackend::construct_file_path_with_publisher(&self.repo, &publisher, &hash);
// Fallback for very short hashes (shouldn't happen with SHA256)
self.repo.join("file").join(&hash)
} else {
// Extract the first two and next two characters from the hash
let first_two = &hash[0..2];
let next_two = &hash[2..4];
// Create the path: $REPO/file/XX/YY/XXYY...
self.repo
.join("file")
.join(first_two)
.join(next_two)
.join(&hash)
};
// Create parent directories if they don't exist // Create parent directories if they don't exist
if let Some(parent) = dest_path.parent() { if let Some(parent) = dest_path.parent() {
@ -535,7 +556,7 @@ impl Transaction {
}; };
// Create the package directory if it doesn't exist // Create the package directory if it doesn't exist
let pkg_dir = self.repo.join("pkg").join(&publisher).join(&package_stem); let pkg_dir = FileBackend::construct_package_dir(&self.repo, &publisher, &package_stem);
debug!("Package directory: {}", pkg_dir.display()); debug!("Package directory: {}", pkg_dir.display());
if !pkg_dir.exists() { if !pkg_dir.exists() {
debug!("Creating package directory"); debug!("Creating package directory");
@ -627,8 +648,8 @@ impl ReadableRepository for FileBackend {
let mut publishers = Vec::new(); let mut publishers = Vec::new();
for publisher_name in &self.config.publishers { for publisher_name in &self.config.publishers {
// Count packages by scanning the pkg/<publisher> directory // Count packages by scanning the publisher's package directory
let publisher_pkg_dir = self.path.join("pkg").join(publisher_name); let publisher_pkg_dir = Self::construct_package_dir(&self.path, publisher_name, "");
let mut package_count = 0; let mut package_count = 0;
let mut latest_timestamp = SystemTime::UNIX_EPOCH; let mut latest_timestamp = SystemTime::UNIX_EPOCH;
@ -702,7 +723,7 @@ impl ReadableRepository for FileBackend {
// For each publisher, list packages // For each publisher, list packages
for pub_name in publishers { for pub_name in publishers {
// Get the publisher's package directory // Get the publisher's package directory
let publisher_pkg_dir = self.path.join("pkg").join(&pub_name); let publisher_pkg_dir = Self::construct_package_dir(&self.path, &pub_name, "");
// Check if the publisher directory exists // Check if the publisher directory exists
if publisher_pkg_dir.exists() { if publisher_pkg_dir.exists() {
@ -751,7 +772,7 @@ impl ReadableRepository for FileBackend {
// For each publisher, process packages // For each publisher, process packages
for pub_name in publishers { for pub_name in publishers {
// Get the publisher's package directory // Get the publisher's package directory
let publisher_pkg_dir = self.path.join("pkg").join(&pub_name); let publisher_pkg_dir = Self::construct_package_dir(&self.path, &pub_name, "");
// Check if the publisher directory exists // Check if the publisher directory exists
if publisher_pkg_dir.exists() { if publisher_pkg_dir.exists() {
@ -1324,8 +1345,8 @@ impl WritableRepository for FileBackend {
self.config.publishers.push(publisher.to_string()); self.config.publishers.push(publisher.to_string());
// Create publisher-specific directories // Create publisher-specific directories
fs::create_dir_all(self.path.join("catalog").join(publisher))?; fs::create_dir_all(Self::construct_catalog_path(&self.path, publisher))?;
fs::create_dir_all(self.path.join("pkg").join(publisher))?; fs::create_dir_all(Self::construct_package_dir(&self.path, publisher, ""))?;
// Set as the default publisher if no default publisher is set // Set as the default publisher if no default publisher is set
if self.config.default_publisher.is_none() { if self.config.default_publisher.is_none() {
@ -1346,8 +1367,8 @@ impl WritableRepository for FileBackend {
self.config.publishers.remove(pos); self.config.publishers.remove(pos);
// Remove publisher-specific directories and their contents recursively // Remove publisher-specific directories and their contents recursively
let catalog_dir = self.path.join("catalog").join(publisher); let catalog_dir = Self::construct_catalog_path(&self.path, publisher);
let pkg_dir = self.path.join("pkg").join(publisher); let pkg_dir = Self::construct_package_dir(&self.path, publisher, "");
// Remove the catalog directory if it exists // Remove the catalog directory if it exists
if catalog_dir.exists() { if catalog_dir.exists() {
@ -1492,18 +1513,91 @@ impl WritableRepository for FileBackend {
} }
impl FileBackend { impl FileBackend {
/// Helper method to construct a catalog path consistently
///
/// Format: base_path/publisher/publisher_name/catalog
pub fn construct_catalog_path(
base_path: &Path,
publisher: &str,
) -> PathBuf {
base_path.join("publisher").join(publisher).join("catalog")
}
/// Helper method to construct a manifest path consistently /// Helper method to construct a manifest path consistently
fn construct_manifest_path( ///
/// Format: base_path/publisher/publisher_name/pkg/stem/encoded_version
pub fn construct_manifest_path(
base_path: &Path, base_path: &Path,
publisher: &str, publisher: &str,
stem: &str, stem: &str,
version: &str, version: &str,
) -> PathBuf { ) -> PathBuf {
let pkg_dir = base_path.join("pkg").join(publisher).join(stem); let pkg_dir = Self::construct_package_dir(base_path, publisher, stem);
let encoded_version = Self::url_encode(version); let encoded_version = Self::url_encode(version);
pkg_dir.join(encoded_version) pkg_dir.join(encoded_version)
} }
/// Helper method to construct a package directory path consistently
///
/// Format: base_path/publisher/publisher_name/pkg/url_encoded_stem
pub fn construct_package_dir(
base_path: &Path,
publisher: &str,
stem: &str,
) -> PathBuf {
let encoded_stem = Self::url_encode(stem);
base_path.join("publisher").join(publisher).join("pkg").join(encoded_stem)
}
/// Helper method to construct a file path consistently
///
/// Format: base_path/file/XX/hash
/// Where XX is the first two characters of the hash
pub fn construct_file_path(
base_path: &Path,
hash: &str,
) -> PathBuf {
if hash.len() < 2 {
// Fallback for very short hashes (shouldn't happen with SHA256)
base_path.join("file").join(hash)
} else {
// Extract the first two characters from the hash
let first_two = &hash[0..2];
// Create the path: $REPO/file/XX/XXYY...
base_path
.join("file")
.join(first_two)
.join(hash)
}
}
/// Helper method to construct a file path consistently with publisher
///
/// Format: base_path/publisher/publisher_name/file/XX/hash
/// Where XX is the first two characters of the hash
pub fn construct_file_path_with_publisher(
base_path: &Path,
publisher: &str,
hash: &str,
) -> PathBuf {
if hash.len() < 2 {
// Fallback for very short hashes (shouldn't happen with SHA256)
base_path.join("publisher").join(publisher).join("file").join(hash)
} else {
// Extract the first two characters from the hash
let first_two = &hash[0..2];
// Create the path: $REPO/publisher/publisher_name/file/XX/XXYY...
base_path
.join("publisher")
.join(publisher)
.join("file")
.join(first_two)
.join(hash)
}
}
/// Recursively find manifest files in a directory and its subdirectories /// Recursively find manifest files in a directory and its subdirectories
fn find_manifests_recursive( fn find_manifests_recursive(
&self, &self,
@ -1642,10 +1736,9 @@ impl FileBackend {
/// Create the repository directories /// Create the repository directories
fn create_directories(&self) -> Result<()> { fn create_directories(&self) -> Result<()> {
// Create the main repository directories // Create the main repository directories
fs::create_dir_all(self.path.join("catalog"))?; fs::create_dir_all(self.path.join("publisher"))?;
fs::create_dir_all(self.path.join("file"))?; fs::create_dir_all(self.path.join("file"))?;
fs::create_dir_all(self.path.join("index"))?; fs::create_dir_all(self.path.join("index"))?;
fs::create_dir_all(self.path.join("pkg"))?;
fs::create_dir_all(self.path.join("trans"))?; fs::create_dir_all(self.path.join("trans"))?;
fs::create_dir_all(self.path.join("obsoleted"))?; fs::create_dir_all(self.path.join("obsoleted"))?;
@ -1655,16 +1748,12 @@ impl FileBackend {
/// Rebuild catalog for a publisher /// Rebuild catalog for a publisher
/// ///
/// This method generates catalog files for a publisher and stores them in the publisher's /// This method generates catalog files for a publisher and stores them in the publisher's
/// subdirectory within the catalog directory. /// catalog directory.
pub fn rebuild_catalog(&self, publisher: &str, create_update_log: bool) -> Result<()> { pub fn rebuild_catalog(&self, publisher: &str, create_update_log: bool) -> Result<()> {
info!("Rebuilding catalog for publisher: {}", publisher); info!("Rebuilding catalog for publisher: {}", publisher);
debug!(
"Catalog directory path: {}",
self.path.join("catalog").display()
);
// Create the catalog directory for the publisher if it doesn't exist // Create the catalog directory for the publisher if it doesn't exist
let catalog_dir = self.path.join("catalog").join(publisher); let catalog_dir = Self::construct_catalog_path(&self.path, publisher);
debug!("Publisher catalog directory: {}", catalog_dir.display()); debug!("Publisher catalog directory: {}", catalog_dir.display());
fs::create_dir_all(&catalog_dir)?; fs::create_dir_all(&catalog_dir)?;
debug!("Created publisher catalog directory"); debug!("Created publisher catalog directory");
@ -1936,36 +2025,25 @@ impl FileBackend {
Ok(()) Ok(())
} }
/// Generate the file path for a given hash using the new directory structure /// Generate the file path for a given hash using the new directory structure with publisher
/// The path will be $REPO/file/XX/YY/XXYY... where XX and YY are the first four letters of the hash /// This is a wrapper around the construct_file_path_with_publisher helper method
fn generate_file_path(&self, hash: &str) -> PathBuf { fn generate_file_path_with_publisher(&self, publisher: &str, hash: &str) -> PathBuf {
if hash.len() < 4 { Self::construct_file_path_with_publisher(&self.path, publisher, hash)
// Fallback for very short hashes (shouldn't happen with SHA256)
return self.path.join("file").join(hash);
}
// Extract the first two and next two characters from the hash
let first_two = &hash[0..2];
let next_two = &hash[2..4];
// Create the path: $REPO/file/XX/YY/XXYY...
self.path
.join("file")
.join(first_two)
.join(next_two)
.join(hash)
} }
/// Get or initialize the catalog manager /// Get or initialize the catalog manager
/// ///
/// This method returns a mutable reference to the catalog manager. /// This method returns a mutable reference to the catalog manager.
/// It uses interior mutability with RefCell to allow mutation through an immutable reference. /// It uses interior mutability with RefCell to allow mutation through an immutable reference.
///
/// The catalog manager is specific to the given publisher.
pub fn get_catalog_manager( pub fn get_catalog_manager(
&mut self, &mut self,
publisher: &str,
) -> Result<std::cell::RefMut<crate::repository::catalog::CatalogManager>> { ) -> Result<std::cell::RefMut<crate::repository::catalog::CatalogManager>> {
if self.catalog_manager.is_none() { if self.catalog_manager.is_none() {
let catalog_dir = self.path.join("catalog"); let publisher_dir = self.path.join("publisher");
let manager = crate::repository::catalog::CatalogManager::new(&catalog_dir)?; let manager = crate::repository::catalog::CatalogManager::new(&publisher_dir, publisher)?;
let refcell = std::cell::RefCell::new(manager); let refcell = std::cell::RefCell::new(manager);
self.catalog_manager = Some(refcell); self.catalog_manager = Some(refcell);
} }
@ -2015,7 +2093,7 @@ impl FileBackend {
let mut index = SearchIndex::new(); let mut index = SearchIndex::new();
// Get the publisher's package directory // Get the publisher's package directory
let publisher_pkg_dir = self.path.join("pkg").join(publisher); let publisher_pkg_dir = Self::construct_package_dir(&self.path, publisher, "");
// Check if the publisher directory exists // Check if the publisher directory exists
if publisher_pkg_dir.exists() { if publisher_pkg_dir.exists() {
@ -2278,7 +2356,8 @@ impl FileBackend {
// Verify the file was stored // Verify the file was stored
let hash = Transaction::calculate_file_hash(&test_file_path)?; let hash = Transaction::calculate_file_hash(&test_file_path)?;
let stored_file_path = self.generate_file_path(&hash); // Use the new method with publisher
let stored_file_path = self.generate_file_path_with_publisher(publisher, &hash);
if !stored_file_path.exists() { if !stored_file_path.exists() {
return Err(RepositoryError::Other( return Err(RepositoryError::Other(
@ -2288,11 +2367,9 @@ impl FileBackend {
// Verify the manifest was updated in the publisher-specific directory // Verify the manifest was updated in the publisher-specific directory
// The manifest should be named "unknown.manifest" since we didn't set a package name // The manifest should be named "unknown.manifest" since we didn't set a package name
let manifest_path = self // Use the construct_package_dir helper to get the base directory, then join with the manifest name
.path let pkg_dir = Self::construct_package_dir(&self.path, publisher, "unknown");
.join("pkg") let manifest_path = pkg_dir.join("manifest");
.join(publisher)
.join("unknown.manifest");
if !manifest_path.exists() { if !manifest_path.exists() {
return Err(RepositoryError::Other(format!( return Err(RepositoryError::Other(format!(
@ -2383,14 +2460,14 @@ impl FileBackend {
} }
/// Store a file in the repository /// Store a file in the repository
pub fn store_file<P: AsRef<Path>>(&self, file_path: P) -> Result<String> { pub fn store_file<P: AsRef<Path>>(&self, file_path: P, publisher: &str) -> Result<String> {
let file_path = file_path.as_ref(); let file_path = file_path.as_ref();
// Calculate the SHA256 hash of the file // Calculate the SHA256 hash of the file
let hash = Transaction::calculate_file_hash(file_path)?; let hash = Transaction::calculate_file_hash(file_path)?;
// Create the destination path using the new directory structure // Create the destination path using the new directory structure with publisher
let dest_path = self.generate_file_path(&hash); let dest_path = self.generate_file_path_with_publisher(publisher, &hash);
// Create parent directories if they don't exist // Create parent directories if they don't exist
if let Some(parent) = dest_path.parent() { if let Some(parent) = dest_path.parent() {

View file

@ -2513,20 +2513,20 @@ impl ObsoletedPackageManager {
} }
} }
/// URL encode a string /// URL encode a string for use in a filename
fn url_encode(s: &str) -> String { fn url_encode(s: &str) -> String {
let mut encoded = String::new(); let mut result = String::new();
for c in s.chars() { for c in s.chars() {
match c { match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => encoded.push(c), 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => result.push(c),
' ' => result.push('+'),
_ => { _ => {
for b in c.to_string().as_bytes() { result.push('%');
encoded.push_str(&format!("%{:02X}", b)); result.push_str(&format!("{:02X}", c as u8));
}
} }
} }
} }
encoded result
} }
#[cfg(test)] #[cfg(test)]

View file

@ -140,7 +140,7 @@ mod tests {
println!("Transaction committed successfully"); println!("Transaction committed successfully");
// Debug: Check if the package manifest was stored in the correct location // Debug: Check if the package manifest was stored in the correct location
let publisher_pkg_dir = repo.path.join("pkg").join(publisher); let publisher_pkg_dir = FileBackend::construct_package_dir(&repo.path, publisher, "");
println!( println!(
"Publisher package directory: {}", "Publisher package directory: {}",
publisher_pkg_dir.display() publisher_pkg_dir.display()
@ -181,10 +181,9 @@ mod tests {
// Check that the repository was created // Check that the repository was created
assert!(repo_path.exists()); assert!(repo_path.exists());
assert!(repo_path.join("catalog").exists()); assert!(repo_path.join("publisher").exists());
assert!(repo_path.join("file").exists()); assert!(repo_path.join("file").exists());
assert!(repo_path.join("index").exists()); assert!(repo_path.join("index").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());
@ -206,8 +205,8 @@ 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()));
assert!(repo_path.join("catalog").join("example.com").exists()); assert!(FileBackend::construct_catalog_path(&repo_path, "example.com").exists());
assert!(repo_path.join("pkg").join("example.com").exists()); assert!(FileBackend::construct_package_dir(&repo_path, "example.com", "").exists());
// Clean up // Clean up
cleanup_test_dir(&test_dir); cleanup_test_dir(&test_dir);
@ -217,20 +216,22 @@ mod tests {
fn test_catalog_manager() { fn test_catalog_manager() {
// Create a test directory // Create a test directory
let test_dir = create_test_dir("catalog_manager"); let test_dir = create_test_dir("catalog_manager");
let catalog_dir = test_dir.join("catalog"); let publisher_dir = test_dir.join("publisher");
let publisher_name = "test";
let catalog_dir = publisher_dir.join(publisher_name).join("catalog");
// Create the catalog directory // Create the catalog directory
fs::create_dir_all(&catalog_dir).unwrap(); fs::create_dir_all(&catalog_dir).unwrap();
// Create a catalog manager // Create a catalog manager with the publisher parameter
let mut catalog_manager = CatalogManager::new(&catalog_dir).unwrap(); let mut catalog_manager = CatalogManager::new(&publisher_dir, publisher_name).unwrap();
// Create a catalog part // Create a catalog part
let part = catalog_manager.create_part("test_part"); catalog_manager.create_part("test_part");
// Add a package to the part // Add a package to the part using the stored publisher
let fmri = Fmri::parse("pkg://test/example@1.0.0").unwrap(); let fmri = Fmri::parse("pkg://test/example@1.0.0").unwrap();
part.add_package("test", &fmri, None, None); catalog_manager.add_package_to_part("test_part", &fmri, None, None).unwrap();
// Save the part // Save the part
catalog_manager.save_part("test_part").unwrap(); catalog_manager.save_part("test_part").unwrap();
@ -239,7 +240,7 @@ mod tests {
assert!(catalog_dir.join("test_part").exists()); assert!(catalog_dir.join("test_part").exists());
// Create a new catalog manager and load the part // Create a new catalog manager and load the part
let mut new_catalog_manager = CatalogManager::new(&catalog_dir).unwrap(); let mut new_catalog_manager = CatalogManager::new(&publisher_dir, publisher_name).unwrap();
new_catalog_manager.load_part("test_part").unwrap(); new_catalog_manager.load_part("test_part").unwrap();
// Check that the part was loaded // Check that the part was loaded
@ -409,16 +410,10 @@ mod tests {
fs::write(&test_file_path, "This is a test file").unwrap(); fs::write(&test_file_path, "This is a test file").unwrap();
// Store the file in the repository // Store the file in the repository
let hash = repo.store_file(&test_file_path).unwrap(); let hash = repo.store_file(&test_file_path, "test").unwrap();
// Check if the file was stored in the correct directory structure // Check if the file was stored in the correct directory structure
let first_two = &hash[0..2]; let expected_path = FileBackend::construct_file_path_with_publisher(&repo_path, "test", &hash);
let next_two = &hash[2..4];
let expected_path = repo_path
.join("file")
.join(first_two)
.join(next_two)
.join(&hash);
// Verify that the file exists at the expected path // Verify that the file exists at the expected path
assert!( assert!(
@ -427,7 +422,7 @@ mod tests {
expected_path.display() expected_path.display()
); );
// Verify that the file does NOT exist at the old path // Verify that the file does NOT exist at the old path (with no directory prefixing)
let old_path = repo_path.join("file").join(&hash); let old_path = repo_path.join("file").join(&hash);
assert!( assert!(
!old_path.exists(), !old_path.exists(),

View file

@ -135,7 +135,7 @@ mod e2e_tests {
// Check that the repository was created // Check that the repository was created
assert!(repo_path.exists()); assert!(repo_path.exists());
assert!(repo_path.join("catalog").exists()); assert!(repo_path.join("publisher").exists());
assert!(repo_path.join("file").exists()); assert!(repo_path.join("file").exists());
assert!(repo_path.join("index").exists()); assert!(repo_path.join("index").exists());
assert!(repo_path.join("pkg").exists()); assert!(repo_path.join("pkg").exists());
@ -174,8 +174,9 @@ mod e2e_tests {
); );
// Check that the publisher was added // Check that the publisher was added
assert!(repo_path.join("catalog").join("example.com").exists()); assert!(repo_path.join("publisher").join("example.com").exists());
assert!(repo_path.join("pkg").join("example.com").exists()); assert!(repo_path.join("publisher").join("example.com").join("catalog").exists());
assert!(repo_path.join("publisher").join("example.com").join("pkg").exists());
// Clean up // Clean up
cleanup_test_dir(&test_dir); cleanup_test_dir(&test_dir);
@ -462,6 +463,24 @@ mod e2e_tests {
fmri fmri
}; };
// Print the FMRI and repo path for debugging
println!("FMRI: {}", fmri);
println!("Repo path: {}", repo_path.display());
// Check if the package exists in the repository
let pkg_dir = repo_path.join("publisher").join("test").join("pkg").join("example");
println!("Package directory: {}", pkg_dir.display());
println!("Package directory exists: {}", pkg_dir.exists());
// List files in the package directory if it exists
if pkg_dir.exists() {
println!("Files in package directory:");
for entry in std::fs::read_dir(&pkg_dir).unwrap() {
let entry = entry.unwrap();
println!(" {}", entry.path().display());
}
}
// Mark the package as obsoleted // Mark the package as obsoleted
let result = run_pkg6repo(&[ let result = run_pkg6repo(&[
"obsolete-package", "obsolete-package",
@ -471,6 +490,10 @@ mod e2e_tests {
"-m", "This package is obsoleted for testing purposes", "-m", "This package is obsoleted for testing purposes",
"-r", "pkg://test/example2@1.0" "-r", "pkg://test/example2@1.0"
]); ]);
// Print the result for debugging
println!("Result: {:?}", result);
assert!( assert!(
result.is_ok(), result.is_ok(),
"Failed to mark package as obsoleted: {:?}", "Failed to mark package as obsoleted: {:?}",

View file

@ -3,22 +3,6 @@ mod pkg5_import;
use error::{Pkg6RepoError, Result}; use error::{Pkg6RepoError, Result};
use pkg5_import::Pkg5Importer; use pkg5_import::Pkg5Importer;
/// URL encode a string for use in a filename
fn url_encode(s: &str) -> String {
let mut result = String::new();
for c in s.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => result.push(c),
' ' => result.push('+'),
_ => {
result.push('%');
result.push_str(&format!("{:02X}", c as u8));
}
}
}
result
}
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use libips::repository::{FileBackend, ReadableRepository, RepositoryVersion, WritableRepository}; use libips::repository::{FileBackend, ReadableRepository, RepositoryVersion, WritableRepository};
use serde::Serialize; use serde::Serialize;
@ -1306,10 +1290,18 @@ fn main() -> Result<()> {
// Parse the FMRI // Parse the FMRI
let parsed_fmri = libips::fmri::Fmri::parse(fmri)?; let parsed_fmri = libips::fmri::Fmri::parse(fmri)?;
// Get the manifest for the package // Get the manifest for the package using the helper method
let pkg_dir = repo.path.join("pkg").join(publisher).join(parsed_fmri.stem()); let manifest_path = FileBackend::construct_manifest_path(
let encoded_version = url_encode(&parsed_fmri.version()); &repo.path,
let manifest_path = pkg_dir.join(&encoded_version); publisher,
parsed_fmri.stem(),
&parsed_fmri.version()
);
println!("Looking for manifest at: {}", manifest_path.display());
println!("Publisher: {}", publisher);
println!("Stem: {}", parsed_fmri.stem());
println!("Version: {}", parsed_fmri.version());
if !manifest_path.exists() { if !manifest_path.exists() {
return Err(Pkg6RepoError::from(format!( return Err(Pkg6RepoError::from(format!(

View file

@ -462,7 +462,7 @@ impl Pkg5Importer {
// Debug the repository structure // Debug the repository structure
debug!( debug!(
"Publisher directory: {}", "Publisher directory: {}",
dest_repo.path.join("pkg").join(publisher).display() libips::repository::FileBackend::construct_package_dir(&dest_repo.path, publisher, "").display()
); );
// Extract files referenced in the manifest // Extract files referenced in the manifest

View file

@ -47,10 +47,9 @@ mod tests {
// Check that the repository was created // Check that the repository was created
assert!(repo_path.exists()); assert!(repo_path.exists());
assert!(repo_path.join("catalog").exists()); assert!(repo_path.join("publisher").exists());
assert!(repo_path.join("file").exists()); assert!(repo_path.join("file").exists());
assert!(repo_path.join("index").exists()); assert!(repo_path.join("index").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());
@ -72,8 +71,8 @@ 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()));
assert!(repo_path.join("catalog").join("example.com").exists()); assert!(FileBackend::construct_catalog_path(&repo_path, "example.com").exists());
assert!(repo_path.join("pkg").join("example.com").exists()); assert!(FileBackend::construct_package_dir(&repo_path, "example.com", "").exists());
// Clean up // Clean up
cleanup_test_dir(&test_dir); cleanup_test_dir(&test_dir);
@ -95,8 +94,8 @@ mod tests {
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 // Check that the publisher directories were created
let catalog_dir = repo_path.join("catalog").join("example.com"); let catalog_dir = FileBackend::construct_catalog_path(&repo_path, "example.com");
let pkg_dir = repo_path.join("pkg").join("example.com"); let pkg_dir = FileBackend::construct_package_dir(&repo_path, "example.com", "");
assert!( assert!(
catalog_dir.exists(), catalog_dir.exists(),
"Catalog directory should exist after adding publisher" "Catalog directory should exist after adding publisher"

View file

@ -1,57 +0,0 @@
use std::fs;
use std::path::Path;
use libips::repository::{FileBackend, WritableRepository, ReadableRepository, RepositoryVersion};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a temporary directory for the test
let test_dir = Path::new("/tmp/pkg6_file_structure_test");
if test_dir.exists() {
fs::remove_dir_all(test_dir)?;
}
fs::create_dir_all(test_dir)?;
println!("Created test directory: {}", test_dir.display());
// Create a new repository
let mut repo = FileBackend::create(test_dir, RepositoryVersion::V1)?;
// Add a publisher
repo.add_publisher("test")?;
println!("Created repository with publisher 'test'");
// Create a test file
let test_file_path = test_dir.join("test_file.txt");
fs::write(&test_file_path, "This is a test file")?;
println!("Created test file: {}", test_file_path.display());
// Store the file in the repository
let hash = repo.store_file(&test_file_path)?;
println!("Stored file with hash: {}", hash);
// Check if the file was stored in the correct directory structure
let first_two = &hash[0..2];
let next_two = &hash[2..4];
let expected_path = test_dir.join("file").join(first_two).join(next_two).join(&hash);
if expected_path.exists() {
println!("SUCCESS: File was stored at the correct path: {}", expected_path.display());
} else {
println!("ERROR: File was not stored at the expected path: {}", expected_path.display());
// Check if the file was stored in the old location
let old_path = test_dir.join("file").join(&hash);
if old_path.exists() {
println!("File was stored at the old path: {}", old_path.display());
} else {
println!("File was not stored at the old path either");
}
}
// Clean up
fs::remove_dir_all(test_dir)?;
Ok(())
}