use crate::{Error, PluginInfo, PluginScanner, PluginType, Result};
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::path::PathBuf;
use std::ptr::NonNull;
use super::ffi;
use super::instance::AudioUnitPlugin;
use super::util::{c_array_to_string, map_error};
pub struct AudioUnitScanner {
inner: NonNull<ffi::RackAUScanner>,
_not_sync: PhantomData<*const ()>,
}
unsafe impl Send for AudioUnitScanner {}
impl AudioUnitScanner {
pub fn new() -> Result<Self> {
unsafe {
let ptr = ffi::rack_au_scanner_new();
if ptr.is_null() {
return Err(Error::Other("Failed to allocate scanner".to_string()));
}
Ok(Self {
inner: NonNull::new_unchecked(ptr),
_not_sync: PhantomData,
})
}
}
fn scan_components(&self) -> Result<Vec<PluginInfo>> {
unsafe {
let count = ffi::rack_au_scanner_scan(self.inner.as_ptr(), std::ptr::null_mut(), 0);
if count < 0 {
return Err(map_error(count));
}
if count == 0 {
return Ok(Vec::new());
}
let count_usize = usize::try_from(count)
.map_err(|_| Error::Other("Plugin count exceeds usize".to_string()))?;
let mut plugins_c: Vec<MaybeUninit<ffi::RackAUPluginInfo>> =
Vec::with_capacity(count_usize);
plugins_c.resize_with(count_usize, MaybeUninit::uninit);
let actual_count = ffi::rack_au_scanner_scan(
self.inner.as_ptr(),
plugins_c.as_mut_ptr() as *mut ffi::RackAUPluginInfo,
count_usize,
);
if actual_count < 0 {
return Err(map_error(actual_count));
}
let actual_count_usize = usize::try_from(actual_count)
.map_err(|_| Error::Other("Actual plugin count exceeds usize".to_string()))?;
let valid_count = actual_count_usize.min(count_usize);
let plugins = plugins_c
.into_iter()
.take(valid_count)
.map(|p| {
let plugin_info = p.assume_init();
convert_plugin_info(&plugin_info)
})
.collect::<Result<Vec<_>>>()?;
Ok(plugins)
}
}
}
fn convert_plugin_info(c_info: &ffi::RackAUPluginInfo) -> Result<PluginInfo> {
unsafe {
let name = c_array_to_string(&c_info.name, "plugin name")?;
let manufacturer = c_array_to_string(&c_info.manufacturer, "manufacturer")?;
let path_str = c_array_to_string(&c_info.path, "path")?;
let unique_id = c_array_to_string(&c_info.unique_id, "unique_id")?;
let plugin_type = match c_info.plugin_type {
ffi::RackAUPluginType::Effect => PluginType::Effect,
ffi::RackAUPluginType::Instrument => PluginType::Instrument,
ffi::RackAUPluginType::Mixer => PluginType::Mixer,
ffi::RackAUPluginType::FormatConverter => PluginType::FormatConverter,
ffi::RackAUPluginType::Other => PluginType::Other,
};
Ok(PluginInfo::new(
name,
manufacturer,
c_info.version,
plugin_type,
PathBuf::from(path_str),
unique_id,
))
}
}
impl Drop for AudioUnitScanner {
fn drop(&mut self) {
unsafe {
ffi::rack_au_scanner_free(self.inner.as_ptr());
}
}
}
impl PluginScanner for AudioUnitScanner {
type Plugin = AudioUnitPlugin;
fn scan(&self) -> Result<Vec<PluginInfo>> {
self.scan_components()
}
fn scan_path(&self, _path: &std::path::Path) -> Result<Vec<PluginInfo>> {
self.scan()
}
fn load(&self, info: &PluginInfo) -> Result<Self::Plugin> {
AudioUnitPlugin::new(info)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scanner_creation() {
let result = AudioUnitScanner::new();
assert!(result.is_ok(), "Scanner creation should succeed");
}
#[test]
fn test_scanner_creation_returns_result() {
let scanner = AudioUnitScanner::new();
match scanner {
Ok(_) => (),
Err(e) => panic!("Scanner creation failed: {}", e),
}
}
#[test]
fn test_scan() {
let scanner = AudioUnitScanner::new().expect("Scanner creation should succeed");
let result = scanner.scan();
assert!(result.is_ok(), "Scan should succeed");
}
#[test]
fn test_scan_returns_plugins() {
let scanner = AudioUnitScanner::new().expect("Scanner creation should succeed");
let plugins = scanner.scan().expect("Scan should succeed");
println!("Found {} plugins", plugins.len());
}
#[test]
fn test_drop_behavior() {
{
let _scanner = AudioUnitScanner::new().expect("Scanner creation should succeed");
} }
#[test]
fn test_multiple_scans() {
let scanner = AudioUnitScanner::new().expect("Scanner creation should succeed");
let result1 = scanner.scan().expect("First scan should succeed");
let result2 = scanner.scan().expect("Second scan should succeed");
let count1 = result1.len();
let count2 = result2.len();
assert!(count1 > 0, "First scan should find plugins");
assert!(count2 > 0, "Second scan should find plugins");
let max_count = count1.max(count2);
let min_count = count1.min(count2);
let variance = (max_count - min_count) as f64 / max_count as f64;
assert!(
variance < 0.2,
"Scans should be stable: found {} then {} plugins ({}% variance)",
count1, count2, variance * 100.0
);
}
#[test]
fn test_plugin_info_fields() {
let scanner = AudioUnitScanner::new().expect("Scanner creation should succeed");
let plugins = scanner.scan().expect("Scan should succeed");
if let Some(plugin) = plugins.first() {
assert!(!plugin.name.is_empty(), "Plugin name should not be empty");
assert!(!plugin.manufacturer.is_empty(), "Manufacturer should not be empty");
assert!(!plugin.unique_id.is_empty(), "Unique ID should not be empty");
assert!(plugin.path.as_os_str().len() > 0, "Path should not be empty");
}
}
#[test]
fn test_scan_path_delegates_to_scan() {
let scanner = AudioUnitScanner::new().expect("Scanner creation should succeed");
let path = std::path::Path::new("/dummy/path");
let result = scanner.scan_path(path);
assert!(result.is_ok(), "scan_path should succeed");
}
}