diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 9154a51c..70292a58 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -24,7 +24,7 @@ use std::path::{Path, PathBuf}; use walkdir::WalkDir; use crate::config::{yabridge_vst3_home, Config, InstallationMethod, YabridgeFiles}; -use crate::files::{self, LibArchitecture, NativeFile}; +use crate::files::{self, LibArchitecture, NativeFile, Plugin, Vst2Plugin}; use crate::utils; use crate::utils::{verify_path_setup, verify_wine_setup}; @@ -121,15 +121,15 @@ pub fn show_status(config: &Config) -> Result<()> { // be added both with and without a trailing slash println!("\n{}", path.join("").display()); - for (plugin, (status, vst3_module)) in search_results.installation_status() { - let plugin_type = match vst3_module { - Some(module) => format!( + for (plugin_path, (plugin, status)) in search_results.installation_status() { + let plugin_type = match plugin { + Plugin::Vst2(_) => "VST2".cyan().to_string(), + Plugin::Vst3(module) => format!( "{}, {}, {}", "VST3".magenta(), module.type_str(), module.architecture ), - None => "VST2".cyan().to_string(), }; let status_str = match status { @@ -141,7 +141,10 @@ pub fn show_status(config: &Config) -> Result<()> { println!( " {} :: {}, {}", - plugin.strip_prefix(path).unwrap_or(&plugin).display(), + plugin_path + .strip_prefix(path) + .unwrap_or(&plugin_path) + .display(), plugin_type, status_str ); @@ -228,8 +231,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // modules in `~/.vst3/yabridge`. let mut yabridge_vst3_bundles: BTreeMap> = BTreeMap::new(); for (path, search_results) in results { - num_installed += search_results.vst2_files.len(); - num_installed += search_results.vst3_modules.len(); + num_installed += search_results.plugins.len(); orphan_files.extend(search_results.vst2_orphans().into_iter().cloned()); skipped_dll_files.extend(search_results.skipped_files); @@ -239,44 +241,46 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { println!("{}", path.join("").display()); } - // We'll set up the copies or symlinks for VST2 plugins - for plugin in search_results.vst2_files { - let target_path = plugin.with_extension("so"); + for plugin in search_results.plugins { + // If verbose mode is enabled we'll print the path to the plugin after setting it up + let plugin_path: PathBuf = match plugin { + // We'll set up the copies or symlinks for VST2 plugins + Plugin::Vst2(Vst2Plugin { + path: plugin_path, .. + }) => { + let target_path = plugin_path.with_extension("so"); - // Since we skip some files, we'll also keep track of how many new file we've actually - // set up - if install_file( - options.force, - config.method, - &files.libyabridge_vst2, - Some(libyabridge_vst2_hash), - &target_path, - )? { - num_new += 1; - } + // Since we skip some files, we'll also keep track of how many new file we've + // actually set up + if install_file( + options.force, + config.method, + &files.libyabridge_vst2, + Some(libyabridge_vst2_hash), + &target_path, + )? { + num_new += 1; + } - if options.verbose { - println!( - " {}", - plugin.strip_prefix(path).unwrap_or(&plugin).display() - ); - } - } + plugin_path.clone() + } + // And then create merged bundles for the VST3 plugins: + // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles + Plugin::Vst3(module) => { + // Only set up VST3 plugins when yabridge has been compiled with VST3 support + if libyabridge_vst3_hash.is_none() { + continue; + } - // And then create merged bundles for the VST3 plugins: - // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles - if let Some(libyabridge_vst3_hash) = libyabridge_vst3_hash { - for module in search_results.vst3_modules { - // Check if we already set up the same architecture version of the plugin (since - // 32-bit and 64-bit versions of the plugin cna live inside of the same bundle), and - // show a warning if we come across any duplicates. - let already_installed_architectures = yabridge_vst3_bundles - .entry(module.target_bundle_home()) - .or_insert_with(BTreeSet::new); - if !already_installed_architectures.insert(module.architecture) { - eprintln!( - "{}", - utils::wrap(&format!( + // 32-bit and 64-bit versions of the plugin cna live inside of the same + // bundle), and show a warning if we come across any duplicates. + let already_installed_architectures = yabridge_vst3_bundles + .entry(module.target_bundle_home()) + .or_insert_with(BTreeSet::new); + if !already_installed_architectures.insert(module.architecture) { + eprintln!( + "{}", + utils::wrap(&format!( "{}: The {} version of '{}' has already been provided by another Wine \ prefix, skipping '{}'\n", "WARNING".red(), @@ -284,54 +288,63 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { module.target_bundle_home().display(), module.original_module_path().display(), )) - ); + ); - continue; - } + continue; + } - // We're building a merged VST3 bundle containing both a copy or symlink to - // `libyabridge-vst3.so` and the Windows VST3 plugin - let native_module_path = module.target_native_module_path(); - utils::create_dir_all(native_module_path.parent().unwrap())?; - if install_file( - options.force, - config.method, - files.libyabridge_vst3.as_ref().unwrap(), - Some(libyabridge_vst3_hash), - &native_module_path, - )? { - num_new += 1; - } + // We're building a merged VST3 bundle containing both a copy or symlink to + // `libyabridge-vst3.so` and the Windows VST3 plugin + let native_module_path = module.target_native_module_path(); + utils::create_dir_all(native_module_path.parent().unwrap())?; + if install_file( + options.force, + config.method, + files.libyabridge_vst3.as_ref().unwrap(), + libyabridge_vst3_hash, + &native_module_path, + )? { + num_new += 1; + } - // We'll then symlink the Windows VST3 module to that bundle to create a merged - // bundle: https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles - let windows_module_path = module.target_windows_module_path(); - utils::create_dir_all(windows_module_path.parent().unwrap())?; - install_file( - true, - InstallationMethod::Symlink, - &module.original_module_path(), - None, - &windows_module_path, - )?; - - // If `module` is a bundle, then it may contain a `Resources` directory with - // screenshots and documentation - // TODO: Also symlink presets, but this is a bit more involved. See - // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#win7preset - if let Some(original_resources_dir) = module.original_resources_dir() { + // We'll then symlink the Windows VST3 module to that bundle to create a merged + // bundle: https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles + let windows_module_path = module.target_windows_module_path(); + utils::create_dir_all(windows_module_path.parent().unwrap())?; install_file( - false, + true, InstallationMethod::Symlink, - &original_resources_dir, + &module.original_module_path(), None, - &module.target_resources_dir(), + &windows_module_path, )?; - } - if options.verbose { - println!(" {}", module.original_path().display()); + // If `module` is a bundle, then it may contain a `Resources` directory with + // screenshots and documentation + // TODO: Also symlink presets, but this is a bit more involved. See + // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#win7preset + if let Some(original_resources_dir) = module.original_resources_dir() { + install_file( + false, + InstallationMethod::Symlink, + &original_resources_dir, + None, + &module.target_resources_dir(), + )?; + } + + module.original_path().to_path_buf() } + }; + + if options.verbose { + println!( + " {}", + plugin_path + .strip_prefix(path) + .unwrap_or(&plugin_path) + .display() + ); } } diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 2a0babbd..f99f70ea 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -32,14 +32,12 @@ use crate::utils::get_file_type; /// Stores the results from searching through a directory. We'll search for Windows VST2 plugin /// `.dll` files, Windows VST3 plugin modules, and native Linux `.so` files inside of a directory. /// These `.so` files are kept track of so we can report the current installation status of VST2 -/// plugins and to be able to prune orphan files. Since VST3 plugins have to be instaleld in +/// plugins and to be able to prune orphan files. Since VST3 plugins have to be installed in /// `~/.vst3`, these orphan files are only relevant for VST2 plugins. #[derive(Debug)] pub struct SearchResults { - /// Absolute paths to the found VST2 `.dll` files. - pub vst2_files: Vec, - /// Absolute paths to found VST3 modules. Either legacy `.vst3` DLL files or VST 3.6.10 bundles. - pub vst3_modules: Vec, + /// The plugins found during the search. This contains both VST2 plugins and VST3 modules. + pub plugins: Vec, /// `.dll` files skipped over during the search. Used for printing statistics and shown when /// running `yabridgectl sync --verbose`. pub skipped_files: Vec, @@ -82,10 +80,26 @@ impl NativeFile { } } -/// VST3 modules we found during a serach. +/// A plugin as found during the search. This can be either a VST2 plugin or a VST3 module. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Plugin { + Vst2(Vst2Plugin), + Vst3(Vst3Module), +} + +/// VST2 plugins we found during a search along with their architecture. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Vst2Plugin { + /// The absolute path to the VST2 plugin `.dll` file. + pub path: PathBuf, + /// The architecture of the VST2 plugin. + pub architecture: LibArchitecture, +} + +/// VST3 modules we found during a search. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Vst3Module { - /// The actual VST3 module and its type. + /// The absolute path to the actual VST3 module and its type. pub module: Vst3ModuleType, /// The architecture of the VST3 module. pub architecture: LibArchitecture, @@ -251,45 +265,38 @@ impl LibArchitecture { } impl SearchResults { - /// For every found VST2 plugin and VST3 module, find the associated copy or symlink of - /// `libyabridge-{vst2,vst3}.so`. The returned hashmap will contain a `None` value for plugins - /// that have not yet been set up. For VST3 modules we'll also return the actual module since - /// this contains a lot of additional information we might want to print during `yabridgectl - /// status`. - pub fn installation_status( - &self, - ) -> BTreeMap, Option<&Vst3Module>)> { + /// Create a map out of all found plugins based on their file path that contains both a + /// reference to the plugin (so we can print information about it) and the current installation + /// status. The installation status will be `None` if the plugin has not yet been set up. + pub fn installation_status(&self) -> BTreeMap)> { let so_files: HashMap<&Path, &NativeFile> = self .so_files .iter() .map(|file| (file.path(), file)) .collect(); - // Do this for the VST2 plugins - let mut installation_status: BTreeMap = self - .vst2_files + self.plugins .iter() - .map( - |path| match so_files.get(path.with_extension("so").as_path()) { - Some(&file_type) => (path.clone(), (Some(file_type.clone()), None)), - None => (path.clone(), (None, None)), - }, - ) - .collect(); - - // And for VST3 modules. We have not stored the paths to the corresponding `.so` files yet - // because they are not in any of the directories we're indexing. - installation_status.extend(self.vst3_modules.iter().map(|module| { - ( - module.original_path().to_owned(), - ( - get_file_type(module.target_native_module_path()), - Some(module), + .map(|plugin| match plugin { + Plugin::Vst2(Vst2Plugin { path, .. }) => { + // For VST2 plugins we'll just look at the similarly named `.so` file right next + // to the plugin `.dll` file. + match so_files.get(path.with_extension("so").as_path()) { + Some(&file_type) => (path.clone(), (plugin, Some(file_type.clone()))), + None => (path.clone(), (plugin, None)), + } + } + // We have not stored the paths to the corresponding `.so` files yet for VST3 + // modules because they are not in any of the directories we're indexing + Plugin::Vst3(vst3_module) => ( + vst3_module.original_path().to_owned(), + ( + plugin, + get_file_type(vst3_module.target_native_module_path()), + ), ), - ) - })); - - installation_status + }) + .collect() } /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. @@ -303,8 +310,10 @@ impl SearchResults { .map(|file_type| (file_type.path(), file_type)) .collect(); - for vst2_path in &self.vst2_files { - orphans.remove(vst2_path.with_extension("so").as_path()); + for plugin in &self.plugins { + if let Plugin::Vst2(Vst2Plugin { path, .. }) = plugin { + orphans.remove(path.with_extension("so").as_path()); + } } orphans.values().cloned().collect() @@ -389,12 +398,18 @@ impl SearchIndex { // We'll have to figure out which `.dll` files are VST2 plugins and which should be skipped // by checking whether the file contains one of the VST2 entry point functions. This vector // will contain an `Err(path)` if `path` was not a valid VST2 plugin. - let is_vst2_plugin: Vec> = self + let is_vst2_plugin: Vec> = self .dll_files .into_par_iter() .map(|path| { + let architecture = if DLL32_AUTOMATON.is_match(pe32_info(&path)?) { + LibArchitecture::Dll32 + } else { + LibArchitecture::Dll64 + }; + if VST2_AUTOMATON.is_match(exported_functions(&path)?) { - Ok(Ok(path)) + Ok(Ok(Vst2Plugin { path, architecture })) } else { Ok(Err(path)) } @@ -481,27 +496,25 @@ impl SearchIndex { }) .collect::>()?; + let mut plugins: Vec = Vec::new(); let mut skipped_files: Vec = Vec::new(); - let mut vst2_files: Vec = Vec::new(); for dandidate in is_vst2_plugin { match dandidate { - Ok(path) => vst2_files.push(path), + Ok(plugin) => plugins.push(Plugin::Vst2(plugin)), Err(path) => skipped_files.push(path), } } - let mut vst3_modules: Vec = Vec::new(); for candidate in is_vst3_module { match candidate { - Ok(module) => vst3_modules.push(module), + Ok(module) => plugins.push(Plugin::Vst3(module)), Err(path) => skipped_files.push(path), } } Ok(SearchResults { - vst2_files, - vst3_modules, + plugins, skipped_files, so_files: self.so_files, })