diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index c05c3344..90a9ac07 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -70,6 +70,17 @@ dependencies = [ "syn", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -440,6 +451,7 @@ version = "1.2.1" dependencies = [ "aho-corasick", "clap", + "colored", "lazy_static", "rayon", "serde", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index b67b1f93..cf7aa630 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -11,6 +11,7 @@ license = "GPL-3.0-or-later" [dependencies] aho-corasick = "0.7.13" +colored = "2.0.0" clap = "3.0.0-beta.1" lazy_static = "1.4.0" rayon = "1.3.1" diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index cdbdad2b..97f460d7 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -22,7 +22,7 @@ to use a custom installation directory instead. yabridgectl set --path= ``` -### Installation modes +### Installation methods By default, yabridgectl will use the copy-based installation method for yabridge since this installation method works everywhere. If you are using a DAW that @@ -30,7 +30,7 @@ supports individually sandboxed plugins, then you can choose between using copies and symlinks using the command below. ```shell -yabridgectl set --mode= +yabridgectl set --method= ``` ### Managing directories diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 65cea29d..d14a3f0d 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -14,12 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use rayon::prelude::*; use serde_derive::{Deserialize, Serialize}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt::Display; use std::fs; use std::path::{Path, PathBuf}; use xdg::BaseDirectories; +use crate::files::{self, SearchResults}; + /// The name of the config file, relative to `$XDG_CONFIG_HOME/CONFIG_PREFIX`. const CONFIG_FILE_NAME: &str = "config.toml"; /// The name of the XDG base directory prefix for yabridgectl, relative to `$XDG_CONFIG_HOME` and @@ -62,6 +66,15 @@ pub enum InstallationMethod { Symlink, } +impl Display for InstallationMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + InstallationMethod::Copy => write!(f, "copy"), + InstallationMethod::Symlink => write!(f, "symlink"), + } + } +} + impl Config { /// Try to read the config file, creating a new default file if necessary. This will fail if the /// file could not be created or if it could not be parsed. @@ -150,6 +163,15 @@ impl Config { } } } + + /// Search for VST2 plugins in all of the registered plugins directories. This will return an + /// error if `winedump` could not be called. + pub fn index_directories(&self) -> Result, std::io::Error> { + self.plugin_dirs + .par_iter() + .map(|path| files::index(path).map(|search_results| (path.as_path(), search_results))) + .collect() + } } /// Fetch the XDG base directories for yabridge's own files, converting any error messages if this diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 9bc9bc4a..a1a73939 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -15,20 +15,21 @@ // along with this program. If not, see . use clap::{app_from_crate, App, AppSettings, Arg}; -use config::Config; +use colored::Colorize; use std::path::{Path, PathBuf}; use std::process::exit; +use crate::config::Config; +use crate::files::FoundFile; + mod config; mod files; // TODO: Add the different `yabridgectl set` options -// TODO: Add `yabridgectl status` // TODO: Add `yabridgectl sync` // TODO: Naming and descriptions could be made clearer // TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell -// TODO: Also give a good error if winedump cannot be found -// TODO: Check for left over files when removign directory +// TODO: Check for left over files when removing directory // TODO: Warn about left over files if not using --prune // TODO: Reward parts of the readme // TODO: Record .dll files processed, .dll files skipped and orphan .so files. Print a summary of @@ -73,6 +74,7 @@ fn main() { ), ) .subcommand(App::new("list").about("List the plugin install locations")) + .subcommand(App::new("status").about("Show the installation status for all plugins")) .get_matches(); match matches.subcommand() { @@ -81,6 +83,7 @@ fn main() { remove_directory(&mut config, &options.value_of_t_or_exit::("path")) } ("list", _) => list_directories(&config), + ("status", _) => show_status(&config), _ => unreachable!(), } } @@ -112,6 +115,41 @@ fn list_directories(config: &Config) { } } +/// Print the current configuration and the installation status for all found plugins. +fn show_status(config: &Config) { + match config.index_directories() { + Ok(results) => { + println!( + "yabridge path: {}", + config + .yabridge_home + .as_ref() + .map(|path| format!("'{}'", path.display())) + .unwrap_or(String::from("")) + ); + 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); + } + } + } + Err(err) => { + eprintln!("Error while searching for plugins: {}", err); + exit(1); + } + } +} + /// Verify that a path exists, used for validating arguments. fn validate_path(path: &str) -> Result<(), String> { let path = Path::new(path);