Implement yabridgectl sync

This commit is contained in:
Robbert van der Helm
2020-07-14 22:26:23 +02:00
parent b94af4e7ae
commit 53ebfcd463
4 changed files with 177 additions and 39 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ the `--prune` option.
# Set up copies or symlinks of yabridge for all plugins under the watched directories
yabridgectl sync
# Set up yabridge, and also remove any '.so' still leftover after removing a plugin
yabridgectl sync --purge
yabridgectl sync --prune
```
## Building
+2 -2
View File
@@ -136,7 +136,7 @@ impl Config {
Ok(candidate)
} else {
Err(format!(
"Could not find {} in '{}'.",
"Could not find '{}' in '{}'.",
LIBYABRIDGE_NAME,
directory.display()
))
@@ -154,7 +154,7 @@ impl Config {
}
Err(format!(
"Could not find {} in either '{}' or '{}'. You can tell yabridgectl where \
"Could not find '{}' in either '{}' or '{}'. You can tell yabridgectl where \
to search for it using 'yabridgectl set --path=<path>'.",
LIBYABRIDGE_NAME,
system_path.display(),
+6 -1
View File
@@ -26,11 +26,14 @@ use walkdir::WalkDir;
/// Stores the results from searching for Windows VST plugin `.dll` files and native Linux `.so`
/// files inside of a directory. These `.so` files are kept track of so we can report the current
/// installation status and to be able to purge orphan files.
/// installation status and to be able to prune orphan files.
#[derive(Debug)]
pub struct SearchResults {
/// Absolute paths to the found VST2 `.dll` files.
pub vst2_files: Vec<PathBuf>,
/// The number of skipped `.dll` files. Only used for printing statistics, so we don't keep
/// track of the exact files.
pub num_skipped_files: usize,
/// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or
/// a regular file.
pub so_files: Vec<FoundFile>,
@@ -126,6 +129,7 @@ pub fn index(directory: &Path) -> Result<SearchResults, std::io::Error> {
AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]);
}
let dll_file_count = dll_files.len();
let vst2_files: Vec<PathBuf> = dll_files
.into_par_iter()
.map(|path| {
@@ -151,6 +155,7 @@ pub fn index(directory: &Path) -> Result<SearchResults, std::io::Error> {
.collect::<Result<_, std::io::Error>>()?;
Ok(SearchResults {
num_skipped_files: dll_file_count - vst2_files.len(),
vst2_files,
so_files,
})
+168 -35
View File
@@ -16,17 +16,18 @@
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;
use crate::config::{Config, InstallationMethod};
use crate::files::FoundFile;
mod config;
mod files;
// TODO: Add the different `yabridgectl set` options
// 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: Check for left over files when removing directory
@@ -74,6 +75,22 @@ fn main() {
)
.subcommand(App::new("list").about("List the plugin install locations"))
.subcommand(App::new("status").about("Show the installation status for all plugins"))
.subcommand(
App::new("sync")
.about("Set up or update yabridge for all plugins")
.arg(
Arg::with_name("prune")
.short('p')
.long("prune")
.about("Remove unrelated or leftover '.so' files"),
)
.arg(
Arg::with_name("verbose")
.short('v')
.long("verbose")
.about("Print every plugin yabridge has been set up for"),
),
)
.get_matches();
match matches.subcommand() {
@@ -83,6 +100,11 @@ fn main() {
}
("list", _) => list_directories(&config),
("status", _) => show_status(&config),
("sync", Some(options)) => do_sync(
&config,
options.is_present("prune"),
options.is_present("verbose"),
),
_ => unreachable!(),
}
}
@@ -117,46 +139,157 @@ 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_else(|| String::from("<auto>"))
);
println!(
"libyabridge.so: {}",
config
.libyabridge()
.map(|path| format!("'{}'", path.display()))
.unwrap_or_else(|_| format!("{}", "<not found>".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);
}
}
}
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("<auto>"))
);
println!(
"libyabridge.so: {}",
config
.libyabridge()
.map(|path| format!("'{}'", path.display()))
.unwrap_or_else(|_| format!("{}", "<not found>".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);
}
}
}
/// 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 num_skipped = 0;
let mut orphan_so_files: Vec<FoundFile> = Vec::new();
for (path, search_results) in results {
orphan_so_files.extend(search_results.orphans().into_iter().cloned());
num_installed += search_results.vst2_files.len();
num_skipped += search_results.num_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!();
}
}
if !orphan_so_files.is_empty() {
if prune {
println!("Removing {} leftover '.so' file(s):", orphan_so_files.len());
} else {
println!(
"Found {} leftover '.so' file(s), 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, skipped {} non-plugin '.dll' files.",
num_installed, num_skipped
)
}
/// Verify that a path exists, used for validating arguments.
fn validate_path(path: &str) -> Result<(), String> {
let path = Path::new(path);