diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs
new file mode 100644
index 00000000..4aeeb965
--- /dev/null
+++ b/tools/yabridgectl/src/actions.rs
@@ -0,0 +1,280 @@
+// yabridge: a Wine VST bridge
+// Copyright (C) 2020 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Handlers for the subcommands, just to keep `main.rs` clean.
+
+use clap::ArgMatches;
+use colored::Colorize;
+use std::fs;
+use std::os::unix::fs::symlink;
+use std::path::{Path, PathBuf};
+use std::process::exit;
+
+use crate::config::{Config, InstallationMethod};
+use crate::files;
+use crate::files::FoundFile;
+
+/// Add a direcotry to the plugin locations. Duplicates get ignord because we're using ordered sets.
+pub fn add_directory(config: &mut Config, path: PathBuf) {
+ config.plugin_dirs.insert(path);
+ config.write().unwrap_or_else(|err| {
+ eprintln!("Error while writing config file: {}", err);
+ exit(1);
+ });
+}
+
+/// Remove a direcotry to the plugin locations. The path is assumed to be part of
+/// `config.plugin_dirs`, otherwise this si silently ignored.
+pub fn remove_directory(config: &mut Config, path: &Path) {
+ // We've already verified that this path is in `config.plugin_dirs`
+ // XXS: Would it be a good idea to warn about leftover .so files?
+ config.plugin_dirs.remove(path);
+ config.write().unwrap_or_else(|err| {
+ eprintln!("Error while writing config file: {}", err);
+ exit(1);
+ });
+
+ // Ask the user to remove any leftover files to prevent possible future problems and out of date
+ // copies
+ let orphan_files = files::index_so_files(path);
+ if !orphan_files.is_empty() {
+ println!(
+ "WARNING: Found {} leftover '.so' files still in this directory:",
+ orphan_files.len()
+ );
+
+ for file in &orphan_files {
+ println!("- {}", file.path().display());
+ }
+
+ match promptly::prompt_opt::(
+ "\nWould you like to remove these files? Entering anything other than YES will leave \
+ these files intact",
+ ) {
+ Ok(Some(answer)) if answer == "YES" => {
+ for file in &orphan_files {
+ fs::remove_file(file.path()).unwrap_or_else(|err| {
+ eprintln!("Could not remove '{}': {}", file.path().display(), err);
+ exit(1);
+ });
+ }
+
+ println!("\nRemoved {} files.", orphan_files.len());
+ }
+ _ => {}
+ }
+ }
+}
+
+/// List the plugin locations.
+pub fn list_directories(config: &Config) {
+ for directory in &config.plugin_dirs {
+ println!("{}", directory.display());
+ }
+}
+
+/// Print the current configuration and the installation status for all found plugins.
+pub fn show_status(config: &Config) {
+ let results = match config.index_directories() {
+ Ok(results) => results,
+ Err(err) => {
+ eprintln!("Error while searching for plugins: {}", err);
+ exit(1);
+ }
+ };
+
+ println!(
+ "yabridge path: {}",
+ config
+ .yabridge_home
+ .as_ref()
+ .map(|path| format!("'{}'", path.display()))
+ .unwrap_or_else(|| String::from(""))
+ );
+ println!(
+ "libyabridge.so: {}",
+ config
+ .libyabridge()
+ .map(|path| format!("'{}'", path.display()))
+ .unwrap_or_else(|_| format!("{}", "".red()))
+ );
+ println!("installation method: {}", config.method);
+
+ for (path, search_results) in results {
+ println!("\n{}:", path.display());
+
+ for (plugin, status) in search_results.installation_status() {
+ let status_str = match status {
+ Some(FoundFile::Regular(_)) => "copy".green(),
+ Some(FoundFile::Symlink(_)) => "symlink".green(),
+ None => "not installed".red(),
+ };
+
+ println!(" {} :: {}", plugin.display(), status_str);
+ }
+ }
+}
+
+/// Change configuration settings. The actual options are defined in the clap [app](clap::App).
+pub fn set_settings(config: &mut Config, options: &ArgMatches) {
+ match options.value_of("method") {
+ Some("copy") => config.method = InstallationMethod::Copy,
+ Some("symlink") => config.method = InstallationMethod::Symlink,
+ Some(s) => unimplemented!("Unexpected installation method '{}'", s),
+ None => (),
+ }
+
+ match options.value_of_t("path") {
+ Ok(path) => config.yabridge_home = Some(path),
+ Err(clap::Error {
+ kind: clap::ErrorKind::ArgumentNotFound,
+ ..
+ }) => (),
+ // I don't think we can get any parsing errors here since we already validated that the
+ // argument has to be a valid path, but you never know
+ Err(err) => err.exit(),
+ }
+
+ config.write().unwrap_or_else(|err| {
+ eprintln!("Error while writing config file: {}", err);
+ exit(1);
+ });
+}
+
+/// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan
+/// `.so` files if the prune option is set.
+pub fn do_sync(config: &Config, prune: bool, verbose: bool) {
+ let libyabridge_path = match config.libyabridge() {
+ Ok(path) => {
+ println!("Using '{}'\n", path.display());
+ path
+ }
+ Err(err) => {
+ // The error messages here are already formatted
+ eprintln!("{}", err);
+ exit(1);
+ }
+ };
+
+ let results = match config.index_directories() {
+ Ok(results) => results,
+ Err(err) => {
+ eprintln!("Error while searching for plugins: {}", err);
+ exit(1);
+ }
+ };
+
+ // Keep track of some global statistics
+ let mut num_installed = 0;
+ let mut skipped_dll_files: Vec = Vec::new();
+ let mut orphan_so_files: Vec = Vec::new();
+ for (path, search_results) in results {
+ num_installed += search_results.vst2_files.len();
+ orphan_so_files.extend(search_results.orphans().into_iter().cloned());
+ skipped_dll_files.extend(search_results.skipped_files);
+
+ if verbose {
+ println!("{}:", path.display());
+ }
+ for plugin in search_results.vst2_files {
+ // If the target file already exists, we'll remove it first to prevent issues with
+ // mixing symlinks and regular files
+ let target_path = plugin.with_extension("so");
+ if target_path.exists() {
+ fs::remove_file(&target_path).unwrap_or_else(|err| {
+ eprintln!("Could not remove '{}': {}", target_path.display(), err);
+ exit(1);
+ });
+ }
+
+ match config.method {
+ InstallationMethod::Copy => {
+ fs::copy(&libyabridge_path, &target_path).unwrap_or_else(|err| {
+ eprintln!(
+ "Error copying '{}' to '{}': {}",
+ libyabridge_path.display(),
+ target_path.display(),
+ err
+ );
+ exit(1);
+ });
+ }
+ InstallationMethod::Symlink => {
+ symlink(&libyabridge_path, &target_path).unwrap_or_else(|err| {
+ eprintln!(
+ "Error symlinking '{}' to '{}': {}",
+ libyabridge_path.display(),
+ target_path.display(),
+ err
+ );
+ exit(1);
+ });
+ }
+ }
+
+ if verbose {
+ println!(" {}", plugin.display());
+ }
+ }
+ if verbose {
+ println!();
+ }
+ }
+
+ // We'll print the skipped files all at once to prevetn clutter
+ let num_skipped_files = skipped_dll_files.len();
+ if verbose && !skipped_dll_files.is_empty() {
+ println!("Skipped files:");
+ for path in skipped_dll_files {
+ println!("- {}", path.display());
+ }
+ println!();
+ }
+
+ // Always warn about leftover files sicne those might cause warnings or errors when a VST host
+ // tries to load them
+ if !orphan_so_files.is_empty() {
+ if prune {
+ println!("Removing {} leftover '.so' files:", orphan_so_files.len());
+ } else {
+ println!(
+ "Found {} leftover '.so' files, rerun with the '--prune' option to remove them:",
+ orphan_so_files.len()
+ );
+ }
+
+ for file in orphan_so_files {
+ let path = file.path();
+
+ println!("- {}", path.display());
+ if prune {
+ fs::remove_file(path).unwrap_or_else(|err| {
+ eprintln!("Error while trying to remove '{}': {}", path.display(), err);
+ exit(1);
+ });
+ }
+ }
+
+ println!();
+ }
+
+ println!(
+ "Finished setting up {} plugins using {}, skipped {} non-plugin '.dll' files.",
+ num_installed,
+ config.method.plural(),
+ num_skipped_files
+ )
+}
diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs
index d09b0318..1b378ebb 100644
--- a/tools/yabridgectl/src/config.rs
+++ b/tools/yabridgectl/src/config.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+//! Utilities for managing yabrigectl's configuration.
+
use rayon::prelude::*;
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs
index d4a50a0c..4a23b60b 100644
--- a/tools/yabridgectl/src/main.rs
+++ b/tools/yabridgectl/src/main.rs
@@ -14,16 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use clap::{app_from_crate, App, AppSettings, Arg, ArgMatches};
+use clap::{app_from_crate, App, AppSettings, Arg};
use colored::Colorize;
-use std::fs;
-use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
-use std::process::exit;
-use crate::config::{Config, InstallationMethod};
-use crate::files::FoundFile;
+use crate::config::Config;
+mod actions;
mod config;
mod files;
@@ -122,14 +119,16 @@ fn main() {
.get_matches();
match matches.subcommand() {
- ("add", Some(options)) => add_directory(&mut config, options.value_of_t_or_exit("path")),
- ("rm", Some(options)) => {
- remove_directory(&mut config, &options.value_of_t_or_exit::("path"))
+ ("add", Some(options)) => {
+ actions::add_directory(&mut config, options.value_of_t_or_exit("path"))
}
- ("list", _) => list_directories(&config),
- ("status", _) => show_status(&config),
- ("set", Some(options)) => set_settings(&mut config, options),
- ("sync", Some(options)) => do_sync(
+ ("rm", Some(options)) => {
+ actions::remove_directory(&mut config, &options.value_of_t_or_exit::("path"))
+ }
+ ("list", _) => actions::list_directories(&config),
+ ("status", _) => actions::show_status(&config),
+ ("set", Some(options)) => actions::set_settings(&mut config, options),
+ ("sync", Some(options)) => actions::do_sync(
&config,
options.is_present("prune"),
options.is_present("verbose"),
@@ -138,258 +137,6 @@ fn main() {
}
}
-/// Add a direcotry to the plugin locations. Duplicates get ignord because we're using ordered sets.
-fn add_directory(config: &mut Config, path: PathBuf) {
- config.plugin_dirs.insert(path);
- config.write().unwrap_or_else(|err| {
- eprintln!("Error while writing config file: {}", err);
- exit(1);
- });
-}
-
-/// Remove a direcotry to the plugin locations. The path is assumed to be part of
-/// `config.plugin_dirs`, otherwise this si silently ignored.
-fn remove_directory(config: &mut Config, path: &Path) {
- // We've already verified that this path is in `config.plugin_dirs`
- // XXS: Would it be a good idea to warn about leftover .so files?
- config.plugin_dirs.remove(path);
- config.write().unwrap_or_else(|err| {
- eprintln!("Error while writing config file: {}", err);
- exit(1);
- });
-
- // Ask the user to remove any leftover files to prevent possible future problems and out of date
- // copies
- let orphan_files = files::index_so_files(path);
- if !orphan_files.is_empty() {
- println!(
- "WARNING: Found {} leftover '.so' files still in this directory:",
- orphan_files.len()
- );
-
- for file in &orphan_files {
- println!("- {}", file.path().display());
- }
-
- match promptly::prompt_opt::(
- "\nWould you like to remove these files? Entering anything other than YES will leave \
- these files intact",
- ) {
- Ok(Some(answer)) if answer == "YES" => {
- for file in &orphan_files {
- fs::remove_file(file.path()).unwrap_or_else(|err| {
- eprintln!("Could not remove '{}': {}", file.path().display(), err);
- exit(1);
- });
- }
-
- println!("\nRemoved {} files.", orphan_files.len());
- }
- _ => {}
- }
- }
-}
-
-/// List the plugin locations.
-fn list_directories(config: &Config) {
- for directory in &config.plugin_dirs {
- println!("{}", directory.display());
- }
-}
-
-/// Print the current configuration and the installation status for all found plugins.
-fn show_status(config: &Config) {
- let results = match config.index_directories() {
- Ok(results) => results,
- Err(err) => {
- eprintln!("Error while searching for plugins: {}", err);
- exit(1);
- }
- };
-
- println!(
- "yabridge path: {}",
- config
- .yabridge_home
- .as_ref()
- .map(|path| format!("'{}'", path.display()))
- .unwrap_or_else(|| String::from(""))
- );
- println!(
- "libyabridge.so: {}",
- config
- .libyabridge()
- .map(|path| format!("'{}'", path.display()))
- .unwrap_or_else(|_| format!("{}", "".red()))
- );
- println!("installation method: {}", config.method);
-
- for (path, search_results) in results {
- println!("\n{}:", path.display());
-
- for (plugin, status) in search_results.installation_status() {
- let status_str = match status {
- Some(FoundFile::Regular(_)) => "copy".green(),
- Some(FoundFile::Symlink(_)) => "symlink".green(),
- None => "not installed".red(),
- };
-
- println!(" {} :: {}", plugin.display(), status_str);
- }
- }
-}
-
-/// Change configuration settings. The actual options are defined in the clap [app](clap::App).
-fn set_settings(config: &mut Config, options: &ArgMatches) {
- match options.value_of("method") {
- Some("copy") => config.method = InstallationMethod::Copy,
- Some("symlink") => config.method = InstallationMethod::Symlink,
- Some(s) => unimplemented!("Unexpected installation method '{}'", s),
- None => (),
- }
-
- match options.value_of_t("path") {
- Ok(path) => config.yabridge_home = Some(path),
- Err(clap::Error {
- kind: clap::ErrorKind::ArgumentNotFound,
- ..
- }) => (),
- // I don't think we can get any parsing errors here since we already validated that the
- // argument has to be a valid path, but you never know
- Err(err) => err.exit(),
- }
-
- config.write().unwrap_or_else(|err| {
- eprintln!("Error while writing config file: {}", err);
- exit(1);
- });
-}
-
-/// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan
-/// `.so` files if the prune option is set.
-fn do_sync(config: &Config, prune: bool, verbose: bool) {
- let libyabridge_path = match config.libyabridge() {
- Ok(path) => {
- println!("Using '{}'\n", path.display());
- path
- }
- Err(err) => {
- // The error messages here are already formatted
- eprintln!("{}", err);
- exit(1);
- }
- };
-
- let results = match config.index_directories() {
- Ok(results) => results,
- Err(err) => {
- eprintln!("Error while searching for plugins: {}", err);
- exit(1);
- }
- };
-
- // Keep track of some global statistics
- let mut num_installed = 0;
- let mut skipped_dll_files: Vec = Vec::new();
- let mut orphan_so_files: Vec = Vec::new();
- for (path, search_results) in results {
- num_installed += search_results.vst2_files.len();
- orphan_so_files.extend(search_results.orphans().into_iter().cloned());
- skipped_dll_files.extend(search_results.skipped_files);
-
- if verbose {
- println!("{}:", path.display());
- }
- for plugin in search_results.vst2_files {
- // If the target file already exists, we'll remove it first to prevent issues with
- // mixing symlinks and regular files
- let target_path = plugin.with_extension("so");
- if target_path.exists() {
- fs::remove_file(&target_path).unwrap_or_else(|err| {
- eprintln!("Could not remove '{}': {}", target_path.display(), err);
- exit(1);
- });
- }
-
- match config.method {
- InstallationMethod::Copy => {
- fs::copy(&libyabridge_path, &target_path).unwrap_or_else(|err| {
- eprintln!(
- "Error copying '{}' to '{}': {}",
- libyabridge_path.display(),
- target_path.display(),
- err
- );
- exit(1);
- });
- }
- InstallationMethod::Symlink => {
- symlink(&libyabridge_path, &target_path).unwrap_or_else(|err| {
- eprintln!(
- "Error symlinking '{}' to '{}': {}",
- libyabridge_path.display(),
- target_path.display(),
- err
- );
- exit(1);
- });
- }
- }
-
- if verbose {
- println!(" {}", plugin.display());
- }
- }
- if verbose {
- println!();
- }
- }
-
- // We'll print the skipped files all at once to prevetn clutter
- let num_skipped_files = skipped_dll_files.len();
- if verbose && !skipped_dll_files.is_empty() {
- println!("Skipped files:");
- for path in skipped_dll_files {
- println!("- {}", path.display());
- }
- println!();
- }
-
- // Always warn about leftover files sicne those might cause warnings or errors when a VST host
- // tries to load them
- if !orphan_so_files.is_empty() {
- if prune {
- println!("Removing {} leftover '.so' files:", orphan_so_files.len());
- } else {
- println!(
- "Found {} leftover '.so' files, rerun with the '--prune' option to remove them:",
- orphan_so_files.len()
- );
- }
-
- for file in orphan_so_files {
- let path = file.path();
-
- println!("- {}", path.display());
- if prune {
- fs::remove_file(path).unwrap_or_else(|err| {
- eprintln!("Error while trying to remove '{}': {}", path.display(), err);
- exit(1);
- });
- }
- }
-
- println!();
- }
-
- println!(
- "Finished setting up {} plugins using {}, skipped {} non-plugin '.dll' files.",
- num_installed,
- config.method.plural(),
- num_skipped_files
- )
-}
-
/// Verify that a path exists, used for validating arguments.
fn validate_path(path: &str) -> Result<(), String> {
let path = Path::new(path);