mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Implement yabridgectl sync
This commit is contained in:
@@ -63,7 +63,7 @@ the `--prune` option.
|
|||||||
# Set up copies or symlinks of yabridge for all plugins under the watched directories
|
# Set up copies or symlinks of yabridge for all plugins under the watched directories
|
||||||
yabridgectl sync
|
yabridgectl sync
|
||||||
# Set up yabridge, and also remove any '.so' still leftover after removing a plugin
|
# Set up yabridge, and also remove any '.so' still leftover after removing a plugin
|
||||||
yabridgectl sync --purge
|
yabridgectl sync --prune
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ impl Config {
|
|||||||
Ok(candidate)
|
Ok(candidate)
|
||||||
} else {
|
} else {
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"Could not find {} in '{}'.",
|
"Could not find '{}' in '{}'.",
|
||||||
LIBYABRIDGE_NAME,
|
LIBYABRIDGE_NAME,
|
||||||
directory.display()
|
directory.display()
|
||||||
))
|
))
|
||||||
@@ -154,7 +154,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(format!(
|
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>'.",
|
to search for it using 'yabridgectl set --path=<path>'.",
|
||||||
LIBYABRIDGE_NAME,
|
LIBYABRIDGE_NAME,
|
||||||
system_path.display(),
|
system_path.display(),
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ use walkdir::WalkDir;
|
|||||||
|
|
||||||
/// Stores the results from searching for Windows VST plugin `.dll` files and native Linux `.so`
|
/// 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
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub struct SearchResults {
|
pub struct SearchResults {
|
||||||
/// Absolute paths to the found VST2 `.dll` files.
|
/// Absolute paths to the found VST2 `.dll` files.
|
||||||
pub vst2_files: Vec<PathBuf>,
|
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
|
/// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or
|
||||||
/// a regular file.
|
/// a regular file.
|
||||||
pub so_files: Vec<FoundFile>,
|
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"]);
|
AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dll_file_count = dll_files.len();
|
||||||
let vst2_files: Vec<PathBuf> = dll_files
|
let vst2_files: Vec<PathBuf> = dll_files
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
@@ -151,6 +155,7 @@ pub fn index(directory: &Path) -> Result<SearchResults, std::io::Error> {
|
|||||||
.collect::<Result<_, std::io::Error>>()?;
|
.collect::<Result<_, std::io::Error>>()?;
|
||||||
|
|
||||||
Ok(SearchResults {
|
Ok(SearchResults {
|
||||||
|
num_skipped_files: dll_file_count - vst2_files.len(),
|
||||||
vst2_files,
|
vst2_files,
|
||||||
so_files,
|
so_files,
|
||||||
})
|
})
|
||||||
|
|||||||
+168
-35
@@ -16,17 +16,18 @@
|
|||||||
|
|
||||||
use clap::{app_from_crate, App, AppSettings, Arg};
|
use clap::{app_from_crate, App, AppSettings, Arg};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use std::fs;
|
||||||
|
use std::os::unix::fs::symlink;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::{Config, InstallationMethod};
|
||||||
use crate::files::FoundFile;
|
use crate::files::FoundFile;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod files;
|
mod files;
|
||||||
|
|
||||||
// TODO: Add the different `yabridgectl set` options
|
// TODO: Add the different `yabridgectl set` options
|
||||||
// TODO: Add `yabridgectl sync`
|
|
||||||
// TODO: Naming and descriptions could be made clearer
|
// 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: 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
|
// 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("list").about("List the plugin install locations"))
|
||||||
.subcommand(App::new("status").about("Show the installation status for all plugins"))
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
@@ -83,6 +100,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
("list", _) => list_directories(&config),
|
("list", _) => list_directories(&config),
|
||||||
("status", _) => show_status(&config),
|
("status", _) => show_status(&config),
|
||||||
|
("sync", Some(options)) => do_sync(
|
||||||
|
&config,
|
||||||
|
options.is_present("prune"),
|
||||||
|
options.is_present("verbose"),
|
||||||
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,46 +139,157 @@ fn list_directories(config: &Config) {
|
|||||||
|
|
||||||
/// Print the current configuration and the installation status for all found plugins.
|
/// Print the current configuration and the installation status for all found plugins.
|
||||||
fn show_status(config: &Config) {
|
fn show_status(config: &Config) {
|
||||||
match config.index_directories() {
|
let results = match config.index_directories() {
|
||||||
Ok(results) => {
|
Ok(results) => 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error while searching for plugins: {}", err);
|
eprintln!("Error while searching for plugins: {}", err);
|
||||||
exit(1);
|
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.
|
/// Verify that a path exists, used for validating arguments.
|
||||||
fn validate_path(path: &str) -> Result<(), String> {
|
fn validate_path(path: &str) -> Result<(), String> {
|
||||||
let path = Path::new(path);
|
let path = Path::new(path);
|
||||||
|
|||||||
Reference in New Issue
Block a user