use crate::error::{PluginError, Result};
use crate::plugin::{CompressionAlgorithm, PluginMetadata, COMPRESSION_ALGORITHMS};
use std::collections::HashMap;
use std::sync::RwLock;
static PLUGIN_REGISTRY: RwLock<Option<PluginRegistry>> = RwLock::new(None);
struct PluginRegistry {
plugins: HashMap<[u8; 4], &'static dyn CompressionAlgorithm>,
initialized: bool,
}
impl PluginRegistry {
fn new() -> Self {
Self {
plugins: HashMap::new(),
initialized: false,
}
}
fn clear(&mut self) {
self.plugins.clear();
self.initialized = false;
}
fn register(&mut self, plugin: &'static dyn CompressionAlgorithm) -> Result<()> {
let metadata = plugin.metadata();
if metadata.name.is_empty() {
return Err(
PluginError::InvalidMetadata("Plugin name cannot be empty".to_string()).into(),
);
}
if metadata.version.is_empty() {
return Err(
PluginError::InvalidMetadata("Plugin version cannot be empty".to_string()).into(),
);
}
if metadata.throughput <= 0.0 {
return Err(PluginError::InvalidMetadata(format!(
"Plugin {} has invalid throughput: {}",
metadata.name, metadata.throughput
))
.into());
}
if metadata.compression_ratio <= 0.0 || metadata.compression_ratio > 1.0 {
return Err(PluginError::InvalidMetadata(format!(
"Plugin {} has invalid compression ratio: {}",
metadata.name, metadata.compression_ratio
))
.into());
}
if let Some(existing) = self.plugins.get(&metadata.magic_number) {
let existing_metadata = existing.metadata();
eprintln!(
"Warning: Duplicate magic number {:02X?} detected. \
Plugin '{}' conflicts with '{}'. \
Using first-registered plugin '{}'.",
metadata.magic_number,
metadata.name,
existing_metadata.name,
existing_metadata.name
);
return Ok(()); }
self.plugins.insert(metadata.magic_number, plugin);
Ok(())
}
fn list(&self) -> Vec<PluginMetadata> {
self.plugins
.values()
.map(|plugin| plugin.metadata())
.collect()
}
fn get(&self, magic: [u8; 4]) -> Option<&'static dyn CompressionAlgorithm> {
self.plugins.get(&magic).copied()
}
}
pub fn init_plugins() -> Result<()> {
let mut guard = PLUGIN_REGISTRY
.write()
.map_err(|_| PluginError::OperationFailed("Failed to acquire registry lock".to_string()))?;
let registry = guard.get_or_insert_with(PluginRegistry::new);
if registry.initialized {
registry.clear();
}
for &plugin in COMPRESSION_ALGORITHMS {
registry.register(plugin)?;
}
registry.initialized = true;
Ok(())
}
#[must_use]
pub fn list_plugins() -> Vec<PluginMetadata> {
PLUGIN_REGISTRY
.read()
.ok()
.and_then(|guard| guard.as_ref().map(PluginRegistry::list))
.unwrap_or_default()
}
pub(crate) fn get_plugin_by_magic(magic: [u8; 4]) -> Option<&'static dyn CompressionAlgorithm> {
PLUGIN_REGISTRY
.read()
.ok()
.and_then(|guard| guard.as_ref().and_then(|registry| registry.get(magic)))
}
pub(crate) fn get_default_plugin() -> Option<&'static dyn CompressionAlgorithm> {
get_plugin_by_magic([0x43, 0x52, 0x01, 0x00])
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! make_invalid_plugin {
($name:expr, $version:expr, $magic:expr, $throughput:expr, $ratio:expr) => {{
struct InvalidPlugin;
impl CompressionAlgorithm for InvalidPlugin {
fn name(&self) -> &'static str {
"test_invalid"
}
fn metadata(&self) -> PluginMetadata {
PluginMetadata {
name: $name,
version: $version,
magic_number: $magic,
throughput: $throughput,
compression_ratio: $ratio,
description: "Test",
}
}
fn compress(
&self,
_input: &[u8],
_cancel_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
) -> crate::error::Result<Vec<u8>> {
Ok(vec![])
}
fn decompress(
&self,
_input: &[u8],
_cancel_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
) -> crate::error::Result<Vec<u8>> {
Ok(vec![])
}
fn detect(&self, _file_header: &[u8]) -> bool {
false
}
}
let plugin: &'static dyn CompressionAlgorithm = Box::leak(Box::new(InvalidPlugin));
plugin
}};
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_init_plugins() {
init_plugins().unwrap();
let plugins = list_plugins();
assert!(
!plugins.is_empty(),
"Should discover at least DEFLATE plugin"
);
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_get_default_plugin() {
init_plugins().unwrap();
let plugin = get_default_plugin();
assert!(
plugin.is_some(),
"Default DEFLATE plugin should be available"
);
let plugin = plugin.unwrap();
assert_eq!(plugin.name(), "deflate");
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_reinitialization() {
init_plugins().unwrap();
let count1 = list_plugins().len();
init_plugins().unwrap();
let count2 = list_plugins().len();
assert_eq!(
count1, count2,
"Re-initialization should maintain plugin count"
);
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_registry_validation_empty_name() {
let mut registry = PluginRegistry::new();
let plugin = make_invalid_plugin!("", "1.0.0", [0x43, 0x52, 0x01, 0xFF], 100.0, 0.5);
let result = registry.register(plugin);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("name cannot be empty"));
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_registry_validation_empty_version() {
let mut registry = PluginRegistry::new();
let plugin = make_invalid_plugin!("test", "", [0x43, 0x52, 0x01, 0xFE], 100.0, 0.5);
let result = registry.register(plugin);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("version cannot be empty"));
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_registry_validation_invalid_throughput() {
let mut registry = PluginRegistry::new();
let plugin = make_invalid_plugin!("test", "1.0.0", [0x43, 0x52, 0x01, 0xFD], -10.0, 0.5);
let result = registry.register(plugin);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("invalid throughput"));
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_registry_validation_invalid_compression_ratio() {
let mut registry = PluginRegistry::new();
let plugin = make_invalid_plugin!("test", "1.0.0", [0x43, 0x52, 0x01, 0xFC], 100.0, 1.5);
let result = registry.register(plugin);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("invalid compression ratio"));
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_get_plugin_by_magic() {
init_plugins().unwrap();
let plugin = get_plugin_by_magic([0x43, 0x52, 0x01, 0x00]);
assert!(plugin.is_some());
assert_eq!(plugin.unwrap().name(), "deflate");
let plugin = get_plugin_by_magic([0xFF, 0xFF, 0xFF, 0xFF]);
assert!(plugin.is_none());
}
#[test]
fn test_list_plugins_before_init() {
let plugins = list_plugins();
let _ = plugins.len();
}
#[test]
#[allow(clippy::unwrap_used)]
fn test_registry_clear() {
init_plugins().unwrap();
let count1 = list_plugins().len();
assert!(count1 > 0);
init_plugins().unwrap();
let count2 = list_plugins().len();
assert_eq!(count1, count2);
}
}