use crate::{Error, PluginInfo, PluginScanner, PluginType, Result};
use std::ffi::CString;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
use super::ffi;
use super::instance::Vst3Plugin;
use super::util::{c_array_to_string, map_error};
pub struct Vst3Scanner {
inner: NonNull<ffi::RackVST3Scanner>,
_not_sync: PhantomData<*const ()>,
}
unsafe impl Send for Vst3Scanner {}
impl Vst3Scanner {
pub fn new() -> Result<Self> {
unsafe {
let ptr = ffi::rack_vst3_scanner_new();
if ptr.is_null() {
return Err(Error::Other("Failed to allocate VST3 scanner".to_string()));
}
let result = ffi::rack_vst3_scanner_add_default_paths(ptr);
if result != ffi::RACK_VST3_OK {
ffi::rack_vst3_scanner_free(ptr);
return Err(map_error(result));
}
Ok(Self {
inner: NonNull::new(ptr).expect("pointer is non-null after null check"),
_not_sync: PhantomData,
})
}
}
fn new_empty() -> Result<Self> {
unsafe {
let ptr = ffi::rack_vst3_scanner_new();
if ptr.is_null() {
return Err(Error::Other("Failed to allocate VST3 scanner".to_string()));
}
Ok(Self {
inner: NonNull::new(ptr).expect("pointer is non-null after null check"),
_not_sync: PhantomData,
})
}
}
pub fn add_path(&mut self, path: &Path) -> Result<()> {
let path_str = path.to_str()
.ok_or_else(|| Error::Other("Path contains invalid UTF-8".to_string()))?;
let path_cstr = CString::new(path_str)
.map_err(|_| Error::Other("Path contains null byte".to_string()))?;
unsafe {
let result = ffi::rack_vst3_scanner_add_path(self.inner.as_ptr(), path_cstr.as_ptr());
if result != ffi::RACK_VST3_OK {
return Err(map_error(result));
}
}
Ok(())
}
fn scan_plugins(&self) -> Result<Vec<PluginInfo>> {
unsafe {
let count = ffi::rack_vst3_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::RackVST3PluginInfo>> =
Vec::with_capacity(count_usize);
plugins_c.resize_with(count_usize, MaybeUninit::uninit);
let actual_count = ffi::rack_vst3_scanner_scan(
self.inner.as_ptr(),
plugins_c.as_mut_ptr() as *mut ffi::RackVST3PluginInfo,
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::RackVST3PluginInfo) -> 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::RackVST3PluginType::Effect => PluginType::Effect,
ffi::RackVST3PluginType::Instrument => PluginType::Instrument,
ffi::RackVST3PluginType::Analyzer => PluginType::Analyzer,
ffi::RackVST3PluginType::Spatial => PluginType::Spatial,
ffi::RackVST3PluginType::Other => PluginType::Other,
};
Ok(PluginInfo::new(
name,
manufacturer,
c_info.version,
plugin_type,
PathBuf::from(path_str),
unique_id,
))
}
}
impl Drop for Vst3Scanner {
fn drop(&mut self) {
unsafe {
ffi::rack_vst3_scanner_free(self.inner.as_ptr());
}
}
}
impl PluginScanner for Vst3Scanner {
type Plugin = Vst3Plugin;
fn scan(&self) -> Result<Vec<PluginInfo>> {
self.scan_plugins()
}
fn scan_path(&self, path: &Path) -> Result<Vec<PluginInfo>> {
let mut scanner = Self::new_empty()?;
scanner.add_path(path)?;
scanner.scan_plugins()
}
fn load(&self, info: &PluginInfo) -> Result<Self::Plugin> {
Vst3Plugin::new(info)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scanner_creation() {
let result = Vst3Scanner::new();
assert!(result.is_ok(), "Scanner creation should succeed");
}
#[test]
fn test_scanner_creation_returns_result() {
let scanner = Vst3Scanner::new();
match scanner {
Ok(_) => (),
Err(e) => panic!("Scanner creation failed: {}", e),
}
}
#[test]
fn test_scan() {
let scanner = Vst3Scanner::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 = Vst3Scanner::new().expect("Scanner creation should succeed");
let plugins = scanner.scan().expect("Scan should succeed");
println!("Found {} VST3 plugins", plugins.len());
}
#[test]
fn test_drop_behavior() {
{
let _scanner = Vst3Scanner::new().expect("Scanner creation should succeed");
} }
#[test]
fn test_multiple_scans() {
let scanner = Vst3Scanner::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_eq!(count1, count2, "Multiple scans should return same count");
}
#[test]
fn test_plugin_info_fields() {
let scanner = Vst3Scanner::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_add_path() {
let mut scanner = Vst3Scanner::new().expect("Scanner creation should succeed");
let path = Path::new("/tmp");
let result = scanner.add_path(path);
assert!(result.is_ok(), "Adding path should succeed");
}
}