ardour/libs/ardour/plugin_manager.cc

3401 lines
90 KiB
C++

/*
* Copyright (C) 2000-2018 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
* Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
* Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2012-2015 Tim Mayberry <mojofunk@gmail.com>
* Copyright (C) 2014-2018 John Emmas <john@creativepost.co.uk>
* Copyright (C) 2014-2023 Robin Gareus <robin@gareus.org>
* Copyright (C) 2018 Ben Loftis <ben@harrisonconsoles.com>
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef WAF_BUILD
#include "libardour-config.h"
#endif
#include <stdint.h>
#include <sys/types.h>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <glib.h>
#include "pbd/gstdio_compat.h"
#ifdef HAVE_LRDF
#include <lrdf.h>
#endif
#if defined PLATFORM_WINDOWS && defined VST3_SUPPORT
#include <windows.h>
#include <shlobj.h> // CSIDL_*
#include "pbd/windows_special_dirs.h"
#endif
#ifdef WINDOWS_VST_SUPPORT
#include "fst.h"
#include "pbd/basename.h"
#include <cstring>
#endif // WINDOWS_VST_SUPPORT
#ifdef LXVST_SUPPORT
#include "pbd/basename.h"
#include <cstring>
#endif //LXVST_SUPPORT
#ifdef MACVST_SUPPORT
#include "pbd/basename.h"
#include "pbd/pathexpand.h"
#include <cstring>
#endif //MACVST_SUPPORT
#include <glibmm/miscutils.h>
#include <glibmm/pattern.h>
#include <glibmm/fileutils.h>
#include "pbd/convert.h"
#include "pbd/file_utils.h"
#include "pbd/tokenizer.h"
#include "pbd/whitespace.h"
#include "ardour/directory_names.h"
#include "ardour/debug.h"
#include "ardour/filesystem_paths.h"
#include "ardour/ladspa.h"
#include "ardour/ladspa_plugin.h"
#include "ardour/luascripting.h"
#include "ardour/luaproc.h"
#include "ardour/lv2_plugin.h"
#include "ardour/plugin.h"
#include "ardour/plugin_manager.h"
#include "ardour/rc_configuration.h"
#include "ardour/search_paths.h"
#if (defined WINDOWS_VST_SUPPORT || defined MACVST_SUPPORT || defined LXVST_SUPPORT)
#include "ardour/system_exec.h"
#include "ardour/vst2_scan.h"
#endif
#ifdef WINDOWS_VST_SUPPORT
#include "ardour/windows_vst_plugin.h"
#endif
#ifdef LXVST_SUPPORT
#include "ardour/lxvst_plugin.h"
#include "ardour/linux_vst_support.h"
#endif
#ifdef MACVST_SUPPORT
#include "ardour/mac_vst_support.h"
#include "ardour/mac_vst_plugin.h"
#endif
#ifdef AUDIOUNIT_SUPPORT
#include "CAAudioUnit.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioUnitUtilities.h>
#include "ardour/audio_unit.h"
#include "ardour/auv2_scan.h"
#include "ardour/system_exec.h"
#include <Carbon/Carbon.h>
#endif
#ifdef VST3_SUPPORT
#include "ardour/system_exec.h"
#include "ardour/vst3_module.h"
#include "ardour/vst3_plugin.h"
#include "ardour/vst3_scan.h"
#endif
#include "pbd/error.h"
#include "pbd/stl_delete.h"
#include "pbd/i18n.h"
#include "ardour/debug.h"
using namespace ARDOUR;
using namespace PBD;
using namespace std;
PluginManager* PluginManager::_instance = 0;
std::string PluginManager::auv2_scanner_bin_path = "";
std::string PluginManager::vst2_scanner_bin_path = "";
std::string PluginManager::vst3_scanner_bin_path = "";
#ifdef VST3_SUPPORT
# if defined __x86_64__ || defined _M_X64
# define VST3_BLACKLIST "vst3_x64_blacklist.txt"
# elif defined __i386__ || defined _M_IX86
# define VST3_BLACKLIST "vst3_x86_blacklist.txt"
# elif defined __aarch64__
# define VST3_BLACKLIST "vst3_a64_blacklist.txt"
# elif defined __arm__
# define VST3_BLACKLIST "vst3_a32_blacklist.txt"
# else
# define VST3_BLACKLIST "vst3_blacklist.txt"
# endif
#endif
#if (defined WINDOWS_VST_SUPPORT || defined MACVST_SUPPORT || defined LXVST_SUPPORT)
# if ( defined(__x86_64__) || defined(_M_X64) )
# define VST2_BLACKLIST "vst2_x64_blacklist.txt"
# else
# define VST2_BLACKLIST "vst2_x86_blacklist.txt"
# endif
#endif
#ifdef __aarch64__
# define AUV2_BLACKLIST "auv2_a64_blacklist.txt"
#else
# define AUV2_BLACKLIST "auv2_blacklist.txt"
#endif
PluginManager&
PluginManager::instance()
{
if (!_instance) {
_instance = new PluginManager;
}
return *_instance;
}
PluginManager::PluginManager ()
: _windows_vst_plugin_info(0)
, _lxvst_plugin_info(0)
, _mac_vst_plugin_info(0)
, _vst3_plugin_info(0)
, _ladspa_plugin_info(0)
, _lv2_plugin_info(0)
, _au_plugin_info(0)
, _lua_plugin_info(0)
, _cancel_scan_one (false)
, _cancel_scan_all (false)
, _cancel_scan_timeout_one (false)
, _cancel_scan_timeout_all (false)
, _enable_scan_timeout (false)
{
char* s;
#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT || defined VST3_SUPPORT
// source-tree (ardev, etc)
PBD::Searchpath vstsp(Glib::build_filename(ARDOUR::ardour_dll_directory(), "fst"));
#ifdef PLATFORM_WINDOWS
// on windows the .exe needs to be in the same folder with libardour.dll
vstsp += Glib::build_filename(windows_package_directory_path(), "bin");
#else
// on Unices additional internal-use binaries are deployed to $libdir
vstsp += ARDOUR::ardour_dll_directory();
#endif
#if defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT
if (!PBD::find_file (vstsp,
#ifdef PLATFORM_WINDOWS
#ifdef DEBUGGABLE_SCANNER_APP
#if defined(DEBUG) || defined(_DEBUG)
"ardour-vst-scannerD.exe"
#else
"ardour-vst-scannerRDC.exe"
#endif
#else
"ardour-vst-scanner.exe"
#endif
#else
"ardour-vst-scanner"
#endif
, vst2_scanner_bin_path)) {
PBD::warning << "VST scanner app (ardour-vst-scanner) not found in path " << vstsp.to_string() << endmsg;
}
#endif // VST2
#ifdef VST3_SUPPORT
if (!PBD::find_file (vstsp,
#ifdef PLATFORM_WINDOWS
#ifdef DEBUGGABLE_SCANNER_APP
#if defined(DEBUG) || defined(_DEBUG)
"ardour-vst3-scannerD.exe"
#else
"ardour-vst3-scannerRDC.exe"
#endif
#else
"ardour-vst3-scanner.exe"
#endif
#else
"ardour-vst3-scanner"
#endif
, vst3_scanner_bin_path)) {
PBD::warning << "VST3 scanner app (ardour-vst3-scanner) not found in path " << vstsp.to_string() << endmsg;
}
#endif // VST3_SUPPORT
#endif // any VST
#ifdef AUDIOUNIT_SUPPORT
PBD::Searchpath ausp (Glib::build_filename(ARDOUR::ardour_dll_directory(), "auscan"));
ausp += ARDOUR::ardour_dll_directory();
if (!PBD::find_file (ausp, "ardour-au-scanner" , auv2_scanner_bin_path)) {
PBD::warning << "AUv2 scanner app (ardour-au-scanner) not found in path " << ausp.to_string() << endmsg;
}
#endif
load_statuses ();
load_tags ();
load_stats ();
Searchpath rdfs (ARDOUR::ardour_data_search_path());
if ((s = getenv ("LADSPA_RDF_PATH"))){
rdfs.add_subdirectory_to_paths ("rdf");
Searchpath t (s);
rdfs += t;
} else {
#ifndef PLATFORM_WINDOWS
rdfs += "/usr/local/share/ladspa";
rdfs += "/usr/share/ladspa";
#endif
rdfs.add_subdirectory_to_paths ("rdf");
}
add_lrdf_data (rdfs);
add_lrdf_presets ("ladspa");
#if 0 // dump RDF
lrdf_statement* r = lrdf_all_statements();
for (lrdf_statement* it = r; it != NULL; it = it->next) {
printf("LRDF: (%s, %s, %s)\n", it->subject, it->predicate, it->object);
}
#endif
if ((s = getenv ("VST_PATH"))) {
windows_vst_path = s;
} else if ((s = getenv ("VST_PLUGINS"))) {
windows_vst_path = s;
}
if (windows_vst_path.length() == 0) {
windows_vst_path = vst_search_path ();
}
if ((s = getenv ("LXVST_PATH"))) {
lxvst_path = s;
} else if ((s = getenv ("LXVST_PLUGINS"))) {
lxvst_path = s;
}
if (lxvst_path.length() == 0) {
lxvst_path = "/usr/local/lib64/lxvst:/usr/local/lib/lxvst:/usr/lib64/lxvst:/usr/lib/lxvst:"
"/usr/local/lib64/linux_vst:/usr/local/lib/linux_vst:/usr/lib64/linux_vst:/usr/lib/linux_vst:"
"/usr/lib/vst:/usr/local/lib/vst";
}
/* first time setup, use 'default' path */
if (Config->get_plugin_path_lxvst() == X_("@default@")) {
Config->set_plugin_path_lxvst(get_default_lxvst_path());
}
if (Config->get_plugin_path_vst() == X_("@default@")) {
Config->set_plugin_path_vst(get_default_windows_vst_path());
}
if (Config->get_plugin_path_vst3() == X_("@default@")) {
/* This path is currently only added to the existing path */
if ((s = getenv ("VST3_PATH"))) {
Config->set_plugin_path_vst3 (s);
} else {
Config->set_plugin_path_vst3 ("");
}
}
if (_instance == 0) {
_instance = this;
}
BootMessage (_("Discovering Plugins"));
LuaScripting::instance().scripts_changed.connect_same_thread (lua_refresh_connection, boost::bind (&PluginManager::lua_refresh_cb, this));
}
PluginManager::~PluginManager()
{
if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
// don't bother, just exit quickly.
delete _windows_vst_plugin_info;
delete _lxvst_plugin_info;
delete _mac_vst_plugin_info;
delete _ladspa_plugin_info;
delete _lv2_plugin_info;
delete _au_plugin_info;
delete _lua_plugin_info;
}
/* do drop VST3 Info in order to release any loaded modules */
delete _vst3_plugin_info;
}
bool
PluginManager::cache_valid () const
{
return Config->get_plugin_cache_version () >= cache_version ();
}
uint32_t
PluginManager::cache_version ()
{
return 7002; // 1000 * atoi (X_(PROGRAM_VERSION)) + 2;
}
struct PluginInfoPtrNameSorter {
bool operator () (PluginInfoPtr const& a, PluginInfoPtr const& b) const {
return PBD::downcase (a->name) < PBD::downcase (b->name);
}
};
void
PluginManager::detect_name_ambiguities (PluginInfoList* pil)
{
if (!pil) {
return;
}
pil->sort (PluginInfoPtrNameSorter ());
for (PluginInfoList::iterator i = pil->begin(); i != pil->end();) {
PluginInfoPtr& p = *i;
++i;
if (i != pil->end() && PBD::downcase ((*i)->name) == PBD::downcase (p->name)) {
/* mark name as ambiguous IFF ambiguity can be resolved
* by listing number of audio outputs.
* This is used in the instrument selector.
*/
bool r = p->max_configurable_outputs () != (*i)->max_configurable_outputs ();
p->multichannel_name_ambiguity = r;
(*i)->multichannel_name_ambiguity = r;
}
}
}
void
PluginManager::detect_type_ambiguities (PluginInfoList& pil)
{
PluginInfoList dup;
pil.sort (PluginInfoPtrNameSorter ());
for (PluginInfoList::iterator i = pil.begin(); i != pil.end(); ++i) {
switch (dup.size ()) {
case 0:
break;
case 1:
if (PBD::downcase (dup.back()->name) != PBD::downcase ((*i)->name)) {
dup.clear ();
}
break;
default:
if (PBD::downcase (dup.back()->name) != PBD::downcase ((*i)->name)) {
/* found multiple plugins with same name */
bool typediff = false;
bool chandiff = false;
for (PluginInfoList::iterator j = dup.begin(); j != dup.end(); ++j) {
if (dup.front()->type != (*j)->type) {
typediff = true;
}
chandiff |= (*j)->multichannel_name_ambiguity;
}
if (typediff) {
for (PluginInfoList::iterator j = dup.begin(); j != dup.end(); ++j) {
(*j)->plugintype_name_ambiguity = true;
/* show multi-channel information for consistency, when other types display it.
* eg. "Foo 8 outs, VST", "Foo 12 outs, VST", "Foo <=12 outs, AU"
*/
if (chandiff) {
(*j)->multichannel_name_ambiguity = true;
}
}
}
dup.clear ();
}
break;
}
dup.push_back (*i);
}
}
void
PluginManager::conceal_duplicates (ARDOUR::PluginInfoList* old, ARDOUR::PluginInfoList* nu)
{
if (!old || !nu) {
return;
}
for (PluginInfoList::const_iterator i = old->begin(); i != old->end(); ++i) {
for (PluginInfoList::const_iterator j = nu->begin(); j != nu->end(); ++j) {
if ((*i)->creator == (*j)->creator && (*i)->name == (*j)->name) {
PluginStatus ps ((*i)->type, (*i)->unique_id, Concealed);
if (find (statuses.begin(), statuses.end(), ps) == statuses.end()) {
statuses.erase (ps);
statuses.insert (ps);
}
}
}
}
}
void
PluginManager::refresh (bool cache_only)
{
Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK);
if (!lm.locked()) {
return;
}
load_scanlog ();
DEBUG_TRACE (DEBUG::PluginManager, "PluginManager::refresh\n");
reset_scan_cancel_state ();
BootMessage (_("Scanning LADSPA Plugins"));
ladspa_refresh ();
BootMessage (_("Scanning Lua DSP Processors"));
lua_refresh ();
BootMessage (_("Scanning LV2 Plugins"));
lv2_refresh ();
bool conceal_lv1 = Config->get_conceal_lv1_if_lv2_exists();
if (conceal_lv1) {
conceal_duplicates (_ladspa_plugin_info, _lv2_plugin_info);
}
#ifdef WINDOWS_VST_SUPPORT
if (Config->get_use_windows_vst()) {
if (cache_only) {
BootMessage (_("Scanning Windows VST Plugins"));
} else {
BootMessage (_("Discovering Windows VST Plugins"));
}
windows_vst_refresh (cache_only);
}
#endif // WINDOWS_VST_SUPPORT
#ifdef LXVST_SUPPORT
if(Config->get_use_lxvst()) {
if (cache_only) {
BootMessage (_("Scanning Linux VST Plugins"));
} else {
BootMessage (_("Discovering Linux VST Plugins"));
}
lxvst_refresh(cache_only);
}
#endif //Native linuxVST SUPPORT
#ifdef MACVST_SUPPORT
if(Config->get_use_macvst ()) {
if (cache_only) {
BootMessage (_("Scanning Mac VST Plugins"));
} else {
BootMessage (_("Discovering Mac VST Plugins"));
}
mac_vst_refresh (cache_only);
} else if (_mac_vst_plugin_info) {
_mac_vst_plugin_info->clear ();
} else {
_mac_vst_plugin_info = new ARDOUR::PluginInfoList();
}
#endif //Native Mac VST SUPPORT
#if !defined NDEBUG & (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
if (!cache_only) {
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), VST2_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("VST 2 Blacklist: %1\n", fn));
}
}
#endif
#ifdef VST3_SUPPORT
if(Config->get_use_vst3 ()) {
if (cache_only) {
BootMessage (_("Scanning VST3 Plugins"));
} else {
BootMessage (_("Discovering VST3 Plugins"));
}
vst3_refresh (cache_only);
}
#ifndef NDEBUG
if (!cache_only) {
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST3_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("VST3 Blacklist: %1\n", fn));
}
}
#endif
bool conceal_vst2 = Config->get_conceal_vst2_if_vst3_exists();
if (conceal_vst2) {
conceal_duplicates (_windows_vst_plugin_info, _vst3_plugin_info);
conceal_duplicates (_lxvst_plugin_info, _vst3_plugin_info);
conceal_duplicates (_mac_vst_plugin_info, _vst3_plugin_info);
}
#else
bool conceal_vst2 = false;
#endif
#ifdef AUDIOUNIT_SUPPORT
if (Config->get_use_audio_units ()) {
if (cache_only) {
BootMessage (_("Scanning AU Plugins"));
} else {
BootMessage (_("Discovering AU Plugins"));
}
au_refresh (cache_only);
}
#ifndef NDEBUG
if (!cache_only) {
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), AUV2_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("AUv2 Blacklist: %1\n", fn));
}
}
#endif
#endif
/* unset concealed plugins */
if (!conceal_lv1 || !conceal_vst2) {
for (PluginStatusList::iterator i = statuses.begin(); i != statuses.end();) {
PluginStatusList::iterator j = i++;
if ((*j).status != Concealed) {
continue;
}
if (!conceal_lv1 && (*j).type == LADSPA) {
statuses.erase (j);
}
if (!conceal_vst2 && ((*j).type == Windows_VST || (*j).type == LXVST || (*j).type == MacVST)) {
statuses.erase (j);
}
}
}
if (!cache_only && !cache_valid () && !cancelled ()) {
Config->set_plugin_cache_version (cache_version ());
Config->save_state();
}
BootMessage (_("Plugin Scan Complete..."));
reset_scan_cancel_state ();
PluginScanMessage(X_("closeme"), "", false);
BootMessage (_("Indexing Plugins..."));
detect_ambiguities ();
}
void
PluginManager::detect_ambiguities ()
{
detect_name_ambiguities (_windows_vst_plugin_info);
detect_name_ambiguities (_lxvst_plugin_info);
detect_name_ambiguities (_mac_vst_plugin_info);
detect_name_ambiguities (_au_plugin_info);
detect_name_ambiguities (_ladspa_plugin_info);
detect_name_ambiguities (_lv2_plugin_info);
detect_name_ambiguities (_lua_plugin_info);
detect_name_ambiguities (_vst3_plugin_info);
PluginInfoList all_plugs;
if (_windows_vst_plugin_info) {
all_plugs.insert(all_plugs.end(), _windows_vst_plugin_info->begin(), _windows_vst_plugin_info->end());
}
if (_lxvst_plugin_info) {
all_plugs.insert(all_plugs.end(), _lxvst_plugin_info->begin(), _lxvst_plugin_info->end());
}
if (_mac_vst_plugin_info) {
all_plugs.insert(all_plugs.end(), _mac_vst_plugin_info->begin(), _mac_vst_plugin_info->end());
}
if (_vst3_plugin_info) {
all_plugs.insert(all_plugs.end(), _vst3_plugin_info->begin(), _vst3_plugin_info->end());
}
if (_au_plugin_info) {
all_plugs.insert(all_plugs.end(), _au_plugin_info->begin(), _au_plugin_info->end());
}
if (_ladspa_plugin_info) {
all_plugs.insert(all_plugs.end(), _ladspa_plugin_info->begin(), _ladspa_plugin_info->end());
}
if (_lv2_plugin_info) {
all_plugs.insert(all_plugs.end(), _lv2_plugin_info->begin(), _lv2_plugin_info->end());
}
if (_lua_plugin_info) {
all_plugs.insert(all_plugs.end(), _lua_plugin_info->begin(), _lua_plugin_info->end());
}
detect_type_ambiguities (all_plugs);
save_scanlog ();
PluginListChanged (); /* EMIT SIGNAL */
}
void
PluginManager::enable_scan_timeout ()
{
_enable_scan_timeout = true;
}
void
PluginManager::cancel_scan_all ()
{
_cancel_scan_all = true;
}
void
PluginManager::cancel_scan_one ()
{
_cancel_scan_one = true;
}
void
PluginManager::cancel_scan_timeout_one ()
{
_cancel_scan_timeout_one = true;
}
void
PluginManager::cancel_scan_timeout_all ()
{
_cancel_scan_timeout_all = true;
}
void
PluginManager::reset_scan_cancel_state (bool single)
{
_cancel_scan_one = false;
_cancel_scan_timeout_one = false;
if (single) {
return;
}
_cancel_scan_all = false;
_cancel_scan_timeout_all = false;
_enable_scan_timeout = false;
}
void
PluginManager::clear_vst_cache ()
{
#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
{
string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "vst");
vector<string> v2i_files;
find_files_matching_regex (v2i_files, dn, "\\.v2i$", false);
for (vector<string>::iterator i = v2i_files.begin(); i != v2i_files.end (); ++i) {
::g_unlink(i->c_str());
}
}
Config->set_plugin_cache_version (0);
Config->save_state();
#endif
}
void
PluginManager::clear_vst_blacklist ()
{
#if (defined WINDOWS_VST_SUPPORT || defined LXVST_SUPPORT || defined MACVST_SUPPORT)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), VST2_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
::g_unlink (fn.c_str());
}
}
#endif
}
void
PluginManager::clear_au_cache ()
{
#ifdef AUDIOUNIT_SUPPORT
string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "auv2");
vector<string> a2i_files;
find_files_matching_regex (a2i_files, dn, "\\.a2i$", false);
for (vector<string>::iterator i = a2i_files.begin(); i != a2i_files.end (); ++i) {
::g_unlink(i->c_str());
}
Config->set_plugin_cache_version (0);
Config->save_state();
#endif
}
void
PluginManager::clear_au_blacklist ()
{
#ifdef AUDIOUNIT_SUPPORT
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), AUV2_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
::g_unlink(fn.c_str());
}
#endif
}
void
PluginManager::lua_refresh ()
{
if (_lua_plugin_info) {
_lua_plugin_info->clear ();
} else {
_lua_plugin_info = new ARDOUR::PluginInfoList ();
}
ARDOUR::LuaScriptList & _scripts (LuaScripting::instance ().scripts (LuaScriptInfo::DSP));
for (LuaScriptList::const_iterator s = _scripts.begin(); s != _scripts.end(); ++s) {
LuaPluginInfoPtr lpi (new LuaPluginInfo(*s));
_lua_plugin_info->push_back (lpi);
set_tags (lpi->type, lpi->unique_id, lpi->category, lpi->name, FromPlug);
}
}
void
PluginManager::lua_refresh_cb ()
{
Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK);
if (!lm.locked()) {
return;
}
lua_refresh ();
PluginListChanged (); /* EMIT SIGNAL */
}
void
PluginManager::ladspa_refresh ()
{
if (_ladspa_plugin_info) {
_ladspa_plugin_info->clear ();
} else {
_ladspa_plugin_info = new ARDOUR::PluginInfoList ();
}
/* allow LADSPA_PATH to augment, not override standard locations */
/* Only add standard locations to ladspa_path if it doesn't
* already contain them. Check for trailing G_DIR_SEPARATOR too.
*/
vector<string> ladspa_modules;
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("LADSPA: search along: [%1]\n", ladspa_search_path().to_string()));
find_files_matching_pattern (ladspa_modules, ladspa_search_path (), "*.so");
find_files_matching_pattern (ladspa_modules, ladspa_search_path (), "*.dylib");
find_files_matching_pattern (ladspa_modules, ladspa_search_path (), "*.dll");
size_t n = 1;
size_t all_modules = ladspa_modules.size ();
for (vector<std::string>::iterator i = ladspa_modules.begin(); i != ladspa_modules.end(); ++i, ++n) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("LADSPA: scanning: %1\n", *i));
ARDOUR::PluginScanMessage (string_compose (_("LADSPA (%1 / %2)"), n, all_modules), *i, false);
ladspa_discover (*i);
#ifdef MIXBUS
if (i->find ("harrison_channelstrip") != std::string::npos) {
PluginScanLog::iterator j = _plugin_scan_log.find (PSLEPtr (new PluginScanLogEntry (LADSPA, *i)));
if (j != _plugin_scan_log.end ()) {
_plugin_scan_log.erase (j);
}
}
#endif
}
}
#ifdef HAVE_LRDF
static bool rdf_filter (const string &str, void* /*arg*/)
{
return str[0] != '.' &&
((str.find(".rdf") == (str.length() - 4)) ||
(str.find(".rdfs") == (str.length() - 5)) ||
(str.find(".n3") == (str.length() - 3)) ||
(str.find(".ttl") == (str.length() - 4)));
}
#endif
void
PluginManager::add_lrdf_presets(string domain)
{
#ifdef HAVE_LRDF
vector<string> presets;
vector<string>::iterator x;
#ifdef PLATFORM_WINDOWS
string path = Glib::build_filename (ARDOUR::user_cache_directory (), domain, "rdf");
#else
if (Glib::get_home_dir ().empty ()) {
return;
}
string path = Glib::build_filename (Glib::get_home_dir (), "." + domain, "rdf");
#endif
find_files_matching_filter (presets, path, rdf_filter, 0, false, true);
for (x = presets.begin(); x != presets.end (); ++x) {
const string uri (Glib::filename_to_uri(*x));
#ifndef NDEBUG
info << string_compose (_("Reading RDF %1"), uri) << endmsg;
#endif
if (lrdf_read_file(uri.c_str())) {
warning << string_compose (_("Could not parse RDF %1"), uri) << endmsg;
}
}
#endif
}
void
PluginManager::add_lrdf_data (Searchpath const& path)
{
#ifdef HAVE_LRDF
vector<string> rdf_files;
vector<string>::iterator x;
info << "add_lrdf_data '" << path.to_string () << "'" << endmsg;
find_files_matching_filter (rdf_files, path, rdf_filter, 0, false, true);
for (x = rdf_files.begin(); x != rdf_files.end (); ++x) {
const string uri (Glib::filename_to_uri(*x));
info << "read rdf_file '" << uri << "'" << endmsg;
if (lrdf_read_file(uri.c_str())) {
warning << "Could not parse rdf file: " << uri << endmsg;
}
}
#endif
}
int
PluginManager::ladspa_discover (string path)
{
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Checking for LADSPA plugin at %1\n", path));
PSLEPtr psle (scan_log_entry (LADSPA, path));
psle->reset ();
#ifdef MIXBUS
if (path.find ("harrison_channelstrip") != std::string::npos) {
// always scan, even when cancelled
} else
#endif
if (cancelled ()) {
psle->msg (PluginScanLogEntry::New, "Scan was cancelled.");
return -1;
}
Glib::Module module (path);
const LADSPA_Descriptor *descriptor;
LADSPA_Descriptor_Function dfunc;
void* func = 0;
if (!module) {
psle->msg (PluginScanLogEntry::Error, string_compose(_("Cannot load module \"%1\" (%2)"), path, Glib::Module::get_last_error()));
return -1;
}
if (!module.get_symbol("ladspa_descriptor", func)) {
psle->msg (PluginScanLogEntry::Error, string_compose(_("LADSPA module \"%1\" has no descriptor function (%2)."), path, Glib::Module::get_last_error()));
return -1;
}
dfunc = (LADSPA_Descriptor_Function)func;
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("LADSPA plugin found at %1\n", path));
psle->msg (PluginScanLogEntry::OK, string_compose ("LADSPA plugin found at %1\n", path));
uint32_t i = 0;
for (i = 0; ; ++i) {
/* if a ladspa plugin allocates memory here
* it is never free()ed (or plugin-dependent only when unloading).
* For some plugins memory allocated is incremental, we should
* avoid re-scanning plugins and file bug reports.
*/
if ((descriptor = dfunc (i)) == 0) {
break;
}
if (!ladspa_plugin_whitelist.empty()) {
if (find (ladspa_plugin_whitelist.begin(), ladspa_plugin_whitelist.end(), descriptor->UniqueID) == ladspa_plugin_whitelist.end()) {
psle->msg (PluginScanLogEntry::OK, string_compose(_("LADSPA ignored blacklisted unique-id %1."), descriptor->UniqueID));
continue;
}
}
PluginInfoPtr info(new LadspaPluginInfo);
info->name = descriptor->Name;
info->category = get_ladspa_category(descriptor->UniqueID);
info->path = path;
info->index = i;
info->n_inputs = ChanCount();
info->n_outputs = ChanCount();
info->type = ARDOUR::LADSPA;
string::size_type pos = 0;
string creator = descriptor->Maker;
/* stupid LADSPA creator strings */
#ifdef PLATFORM_WINDOWS
while (pos < creator.length() && creator[pos] > -2 && creator[pos] < 256 && (isalnum (creator[pos]) || isspace (creator[pos]) || creator[pos] == '.')) ++pos;
#else
while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]) || creator[pos] == '.')) ++pos;
#endif
/* If there were too few characters to create a
* meaningful name, mark this creator as 'Unknown'
*/
if (creator.length() < 2 || pos < 3) {
info->creator = "Unknown";
} else{
info->creator = creator.substr (0, pos);
strip_whitespace_edges (info->creator);
}
char buf[32];
snprintf (buf, sizeof (buf), "%lu", descriptor->UniqueID);
info->unique_id = buf;
for (uint32_t n=0; n < descriptor->PortCount; ++n) {
if (LADSPA_IS_PORT_AUDIO (descriptor->PortDescriptors[n])) {
if (LADSPA_IS_PORT_INPUT (descriptor->PortDescriptors[n])) {
info->n_inputs.set_audio(info->n_inputs.n_audio() + 1);
}
else if (LADSPA_IS_PORT_OUTPUT (descriptor->PortDescriptors[n])) {
info->n_outputs.set_audio(info->n_outputs.n_audio() + 1);
}
}
}
/* Ensure that the plugin is not already in the plugin list. */
bool found = false;
for (PluginInfoList::const_iterator i = _ladspa_plugin_info->begin(); i != _ladspa_plugin_info->end(); ++i) {
if(0 == info->unique_id.compare((*i)->unique_id)){
found = true;
}
}
if (!found) {
_ladspa_plugin_info->push_back (info);
psle->add (info);
set_tags (info->type, info->unique_id, info->category, info->name, FromPlug);
psle->msg (PluginScanLogEntry::OK, string_compose(_("Found LADSPA plugin, id: %1 name: %2, Inputs: %3, Outputs: %4"), info->unique_id, info->name, info->n_inputs, info->n_outputs));
} else {
psle->msg (PluginScanLogEntry::OK, string_compose(_("LADSPA ignored plugin with duplicate id %1."), descriptor->UniqueID));
}
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Found LADSPA plugin, id: %1 name: %2, Inputs: %3, Outputs: %4\n",
info->unique_id, info->name, info->n_inputs, info->n_outputs));
}
if (i == 0) {
psle->msg (PluginScanLogEntry::Error, _("LADSPA no plugins found in module."));
}
return 0;
}
string
PluginManager::get_ladspa_category (uint32_t plugin_id)
{
#ifdef HAVE_LRDF
char buf[256];
lrdf_statement pattern;
snprintf(buf, sizeof(buf), "%s%" PRIu32, LADSPA_BASE, plugin_id);
pattern.subject = buf;
pattern.predicate = const_cast<char*>(RDF_TYPE);
pattern.object = 0;
pattern.object_type = lrdf_uri;
lrdf_statement* matches1 = lrdf_matches (&pattern);
if (!matches1) {
return "Unknown";
}
pattern.subject = matches1->object;
pattern.predicate = const_cast<char*>(LADSPA_BASE "hasLabel");
pattern.object = 0;
pattern.object_type = lrdf_literal;
lrdf_statement* matches2 = lrdf_matches (&pattern);
lrdf_free_statements(matches1);
if (!matches2) {
return ("Unknown");
}
string label = matches2->object;
lrdf_free_statements(matches2);
/* Kludge LADSPA class names to be singular and match LV2 class names.
This avoids duplicate plugin menus for every class, which is necessary
to make the plugin category menu at all usable, but is obviously a
filthy kludge.
In the short term, lrdf could be updated so the labels match and a new
release made. To support both specs, we should probably be mapping the
URIs to the same category in code and perhaps tweaking that hierarchy
dynamically to suit the user. Personally, I (drobilla) think that time
is better spent replacing the little-used LRDF.
In the longer term, we will abandon LRDF entirely in favour of LV2 and
use that class hierarchy. Aside from fixing this problem properly, that
will also allow for translated labels. SWH plugins have been LV2 for
ages; TAP needs porting. I don't know of anything else with LRDF data.
*/
if (label == "Utilities") {
return "Utility";
} else if (label == "Pitch shifters") {
return "Pitch Shifter";
} else if (label != "Dynamics" && label != "Chorus"
&&label[label.length() - 1] == 's'
&& label[label.length() - 2] != 's') {
return label.substr(0, label.length() - 1);
} else {
return label;
}
#else
return ("Unknown");
#endif
}
void
PluginManager::lv2_plugin (std::string const& uri, PluginScanLogEntry::PluginScanResult sr, std::string const& msg, bool reset)
{
PSLEPtr psle (scan_log_entry (LV2, uri));
if (reset) {
psle->reset ();
}
psle->msg (sr, msg);
}
void
PluginManager::lv2_refresh ()
{
DEBUG_TRACE (DEBUG::PluginManager, "LV2: refresh\n");
delete _lv2_plugin_info;
_lv2_plugin_info = LV2PluginInfo::discover (sigc::mem_fun (*this, &PluginManager::lv2_plugin));
for (PluginInfoList::iterator i = _lv2_plugin_info->begin(); i != _lv2_plugin_info->end(); ++i) {
PSLEPtr psle (scan_log_entry (LV2, (*i)->unique_id));
psle->add (*i);
set_tags ((*i)->type, (*i)->unique_id, (*i)->category, (*i)->name, FromPlug);
}
}
#ifdef AUDIOUNIT_SUPPORT
static void
auv2_blacklist (std::string const& id)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), AUV2_BLACKLIST);
FILE* f = NULL;
if (! (f = g_fopen (fn.c_str (), "a"))) {
error << "Cannot append to AU blacklist for '" << id <<"'\n";
return;
}
assert (id.find ("\n") == string::npos);
fprintf (f, "%s\n", id.c_str ());
::fclose (f);
}
static void
auv2_whitelist (std::string id)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), AUV2_BLACKLIST);
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return;
}
::g_unlink (fn.c_str ());
assert (id.find("\n") == string::npos);
id += "\n"; // add separator
const size_t rpl = bl.find (id);
if (rpl != string::npos) {
bl.replace (rpl, id.size (), "");
}
if (bl.empty ()) {
return;
}
Glib::file_set_contents (fn, bl);
}
static bool
auv2_is_blacklisted (std::string const& id)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory(), AUV2_BLACKLIST);
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return false;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return false;
}
return bl.find (id + "\n") != string::npos;
}
/* ****************************************************************************/
static void auv2_scanner_log (std::string msg, std::stringstream* ss)
{
*ss << msg;
}
bool
PluginManager::run_auv2_scanner_app (CAComponentDescription const& desc, AUv2DescStr const& d, PSLEPtr psle) const
{
char **argp= (char**) calloc (8, sizeof (char*));
argp[0] = strdup (auv2_scanner_bin_path.c_str ());
argp[1] = strdup ("-f");
if (Config->get_verbose_plugin_scan()) {
argp[2] = strdup ("-v");
} else {
argp[2] = strdup ("-f");
}
argp[3] = strdup ("--");
argp[4] = strdup (d.type.c_str());
argp[5] = strdup (d.subt.c_str());
argp[6] = strdup (d.manu.c_str());
argp[7] = 0;
stringstream scan_log;
ARDOUR::SystemExec scanner (auv2_scanner_bin_path, argp);
PBD::ScopedConnection c;
scanner.ReadStdout.connect_same_thread (c, boost::bind (&auv2_scanner_log, _1, &scan_log));
if (scanner.start (ARDOUR::SystemExec::MergeWithStdin)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot launch AU scanner app '%1': %2"), auv2_scanner_bin_path, strerror (errno)));
return false;
}
int timeout = _enable_scan_timeout ? 1 + Config->get_plugin_scan_timeout() : 0; /* deciseconds */
bool notime = (timeout <= 0);
while (scanner.is_running () && (notime || timeout > 0)) {
if (!notime && no_timeout ()) {
notime = true;
timeout = -1;
} else if (notime && !no_timeout() && _enable_scan_timeout) {
notime = false;
timeout = 1 + Config->get_plugin_scan_timeout ();
}
if (timeout > -864000) {
--timeout;
}
ARDOUR::PluginScanTimeout (timeout);
Glib::usleep (100000);
if (cancelled () || (!notime && timeout == 0)) {
scanner.terminate ();
psle->msg (PluginScanLogEntry::OK, scan_log.str());
if (cancelled ()) {
psle->msg (PluginScanLogEntry::New, "Scan was cancelled.");
} else {
psle->msg (PluginScanLogEntry::TimeOut, "Scan Timed Out.");
}
/* may be partially written */
g_unlink (auv2_cache_file (desc).c_str ());
auv2_whitelist (d.to_s ());
return false;
}
}
psle->msg (PluginScanLogEntry::OK, scan_log.str());
return true;
}
void
PluginManager::auv2_plugin (CAComponentDescription const& desc, AUv2Info const& nfo)
{
PSLEPtr psle (scan_log_entry (AudioUnit, auv2_stringify_descriptor (desc)));
AUPluginInfoPtr info (new AUPluginInfo (std::shared_ptr<CAComponentDescription> (new CAComponentDescription (desc))));
psle->msg (PluginScanLogEntry::OK);
info->unique_id = nfo.id;
info->name = nfo.name;
info->creator = nfo.creator;
info->category = nfo.category;
info->version = nfo.version;
info->max_outputs = nfo.max_outputs;
info->io_configs = nfo.io_configs;
_au_plugin_info->push_back (info);
psle->add (info);
}
int
PluginManager::auv2_discover (AUv2DescStr const& d, bool cache_only)
{
if (!d.valid ()) {
return -1;
}
std::string dstr = d.to_s ();
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("checking AU plugin at %1\n", dstr));
PSLEPtr psle (scan_log_entry (AudioUnit, dstr));
if (auv2_is_blacklisted (dstr)) {
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
CAComponentDescription desc (d.desc ());
bool run_scan = false;
bool is_new = false;
string cache_file = auv2_valid_cache_file (desc, false, &is_new);
if (!cache_only && auv2_scanner_bin_path.empty () && cache_file.empty ()) {
/* scan in host context */
psle->reset ();
auv2_blacklist (dstr);
psle->msg (PluginScanLogEntry::OK, "(internal scan)");
if (!auv2_scan_and_cache (desc, sigc::mem_fun (*this, &PluginManager::auv2_plugin), false)) {
psle->msg (PluginScanLogEntry::Error, "Cannot load AUv2");
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
psle->msg (PluginScanLogEntry::OK, string_compose (_("Saved AUV2 plugin cache to %1"), auv2_cache_file (desc)));
auv2_whitelist (dstr);
return 0;
}
XMLTree tree;
if (cache_file.empty ()) {
run_scan = true;
} else if (tree.read (cache_file)) {
/* valid cache file was found, now check version */
int cf_version = 0;
if (!tree.root()->get_property ("version", cf_version) || cf_version < 2) {
run_scan = true;
}
} else {
/* failed to parse XML */
run_scan = true;
}
if (!cache_only && run_scan) {
/* re/generate cache file */
psle->reset ();
auv2_blacklist (dstr);
if (!run_auv2_scanner_app (desc, d, psle)) {
return -1;
}
cache_file = auv2_cache_file (desc);
if (cache_file.empty ()) {
psle->msg (PluginScanLogEntry::Error, _("Scan Failed."));
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
/* re-read cache file */
if (!tree.read (cache_file)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot parse AUv2 cache file '%1' for plugin '%2'"), cache_file, dstr));
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
run_scan = false; // mark as scanned
}
if (cache_file.empty () || run_scan) {
/* cache file does not exist and cache_only == true,
* or cache file is invalid (scan needed)
*/
psle->msg (is_new ? PluginScanLogEntry::New : PluginScanLogEntry::Updated);
return -1;
}
auv2_whitelist (dstr);
psle->set_result (PluginScanLogEntry::OK);
uint32_t discovered = 0;
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
try {
AUv2Info nfo (**i);
if (nfo.id != dstr) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cache file %1 ID mismatch '%2' vs '%3'"), cache_file, nfo.id, dstr));
continue;
}
auv2_plugin (desc, nfo);
++discovered;
} catch (...) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Corrupt AUv2 cache file '%1'"), cache_file));
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load AUv2 '%1'\n", dstr));
}
}
return discovered;
}
void
PluginManager::au_refresh (bool cache_only)
{
assert (Config->get_use_audio_units ());
DEBUG_TRACE (DEBUG::PluginManager, "AU: refresh\n");
delete _au_plugin_info;
_au_plugin_info = new ARDOUR::PluginInfoList();
ARDOUR::PluginScanMessage(_("AUv2"), _("Indexing"), false);
/* disable AU in case indexing crashes */
Config->set_use_audio_units (false);
Config->save_state();
string aucrsh = Glib::build_filename (ARDOUR::user_cache_directory(), "au_crash");
g_file_set_contents (aucrsh.c_str(), "", -1, NULL);
std::vector<AUv2DescStr> audesc;
auv2_list_plugins (audesc);
/* successful, re-enabled AU support */
Config->set_use_audio_units (true);
Config->save_state();
::g_unlink (aucrsh.c_str());
size_t n = 1;
size_t all_modules = audesc.size ();
for (std::vector<AUv2DescStr>::const_iterator i = audesc.begin (); i != audesc.end (); ++i, ++n) {
reset_scan_cancel_state (true);
ARDOUR::PluginScanMessage (string_compose (_("AUv2 (%1 / %2)"), n, all_modules), i->to_s(), !cache_only && !cancelled());
auv2_discover (*i, cache_only || cancelled ());
}
for (PluginInfoList::iterator i = _au_plugin_info->begin(); i != _au_plugin_info->end(); ++i) {
set_tags ((*i)->type, (*i)->unique_id, (*i)->category, (*i)->name, FromPlug);
}
}
#endif
#if (defined WINDOWS_VST_SUPPORT || defined MACVST_SUPPORT || defined LXVST_SUPPORT)
static bool vst2_is_blacklisted (string const& module_path)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST2_BLACKLIST);
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return false;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return false;
}
return bl.find (module_path + "\n") != string::npos;
}
static void vst2_blacklist (string const& module_path)
{
if (module_path.empty () || vst2_is_blacklisted (module_path)) {
return;
}
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST2_BLACKLIST);
FILE* f = NULL;
if (! (f = g_fopen (fn.c_str (), "a"))) {
PBD::error << string_compose (_("Cannot write to VST2 blacklist file '%1'"), fn) << endmsg;
return;
}
fprintf (f, "%s\n", module_path.c_str ());
::fclose (f);
}
static void vst2_whitelist (string module_path)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST2_BLACKLIST);
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return;
}
::g_unlink (fn.c_str ());
module_path += "\n"; // add separator
const size_t rpl = bl.find (module_path);
if (rpl != string::npos) {
bl.replace (rpl, module_path.size (), "");
}
if (bl.empty ()) {
return;
}
Glib::file_set_contents (fn, bl);
}
static void vst2_scanner_log (std::string msg, std::stringstream* ss)
{
*ss << msg;
}
bool
PluginManager::run_vst2_scanner_app (std::string path, PSLEPtr psle) const
{
char **argp= (char**) calloc (5, sizeof (char*));
argp[0] = strdup (vst2_scanner_bin_path.c_str ());
argp[1] = strdup ("-f");
if (Config->get_verbose_plugin_scan()) {
argp[2] = strdup ("-v");
} else {
argp[2] = strdup ("-f");
}
argp[3] = strdup (path.c_str ());
argp[4] = 0;
stringstream scan_log;
ARDOUR::SystemExec scanner (vst2_scanner_bin_path, argp);
PBD::ScopedConnection c;
scanner.ReadStdout.connect_same_thread (c, boost::bind (&vst2_scanner_log, _1, &scan_log));
if (scanner.start (ARDOUR::SystemExec::MergeWithStdin)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot launch VST scanner app '%1': %2"), vst2_scanner_bin_path, strerror (errno)));
return false;
}
int timeout = _enable_scan_timeout ? 1 + Config->get_plugin_scan_timeout() : 0; /* deciseconds */
bool notime = (timeout <= 0);
while (scanner.is_running () && (notime || timeout > 0)) {
if (!notime && no_timeout ()) {
notime = true;
timeout = -1;
} else if (notime && !no_timeout() && _enable_scan_timeout) {
notime = false;
timeout = 1 + Config->get_plugin_scan_timeout ();
}
if (timeout > -864000) {
--timeout;
}
ARDOUR::PluginScanTimeout (timeout);
Glib::usleep (100000);
if (cancelled () || (!notime && timeout == 0)) {
scanner.terminate ();
psle->msg (PluginScanLogEntry::OK, scan_log.str());
if (cancelled ()) {
psle->msg (PluginScanLogEntry::New, "Scan was cancelled.");
} else {
psle->msg (PluginScanLogEntry::TimeOut, "Scan Timed Out.");
}
/* may be partially written */
g_unlink (vst2_cache_file (path).c_str ());
vst2_whitelist (path);
return false;
}
}
psle->msg (PluginScanLogEntry::OK, scan_log.str());
return true;
}
bool
PluginManager::vst2_plugin (string const& path, PluginType type, VST2Info const& nfo)
{
PSLEPtr psle (scan_log_entry (type, path));
if (!nfo.can_process_replace) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("plugin '%1' does not support processReplacing, and so cannot be used in %2 at this time"), nfo.name, PROGRAM_NAME));
return false;
}
PluginInfoPtr info;
ARDOUR::PluginInfoList* plist;
switch (type) {
#ifdef WINDOWS_VST_SUPPORT
case ARDOUR::Windows_VST:
info = PluginInfoPtr(new WindowsVSTPluginInfo (nfo));
plist = _windows_vst_plugin_info;
break;
#endif
#ifdef LXVST_SUPPORT
case ARDOUR::LXVST:
info = PluginInfoPtr(new LXVSTPluginInfo (nfo));
plist = _lxvst_plugin_info;
break;
#endif
#ifdef MACVST_SUPPORT
case ARDOUR::MacVST:
info = PluginInfoPtr(new MacVSTPluginInfo (nfo));
plist = _mac_vst_plugin_info;
break;
#endif
default:
assert (0);
return false;
}
info->path = path;
/* what a joke freeware VST is */
if (!strcasecmp ("The Unnamed plugin", info->name.c_str())) {
info->name = PBD::basename_nosuffix (path);
}
/* Make sure we don't find the same plugin in more than one place along
* the LXVST_PATH We can't use a simple 'find' because the path is included
* in the PluginInfo, and that is the one thing we can be sure MUST be
* different if a duplicate instance is found. So we just compare the type
* and unique ID (which for some VSTs isn't actually unique...)
*/
bool duplicate = false;
if (!plist->empty()) {
for (PluginInfoList::iterator i =plist->begin(); i != plist->end(); ++i) {
if ((info->type == (*i)->type) && (info->unique_id == (*i)->unique_id)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Ignoring plugin '%1'. VST-ID conflicts with other plugin '%2' files: '%3' vs '%4'"), info->name, (*i)->name, info->path, (*i)->path));
duplicate = true;
continue;
}
}
}
if (duplicate) {
return false;
}
plist->push_back (info);
psle->add (info);
if (!info->category.empty ()) {
set_tags (info->type, info->unique_id, info->category, info->name, FromPlug);
}
return true;
}
int
PluginManager::vst2_discover (string path, ARDOUR::PluginType type, bool cache_only)
{
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("checking apparent VST plugin at %1\n", path));
PSLEPtr psle (scan_log_entry (type, path));
if (vst2_is_blacklisted (path)) {
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
bool run_scan = false;
bool is_new = false;
string cache_file = vst2_valid_cache_file (path, false, &is_new);
if (!cache_only && vst2_scanner_bin_path.empty () && cache_file.empty ()) {
/* scan in host context */
psle->reset ();
vst2_blacklist (path);
psle->msg (PluginScanLogEntry::OK, string_compose ("VST2 plugin: '%1' (internal scan)", path));
if (!vst2_scan_and_cache (path, type, sigc::mem_fun (*this, &PluginManager::vst2_plugin))) {
psle->msg (PluginScanLogEntry::Error, "Cannot load VST2");
psle->msg (PluginScanLogEntry::Blacklisted);
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST2 at '%1'\n", path));
return -1;
}
psle->msg (PluginScanLogEntry::OK, string_compose (_("Saved VST2 plugin cache to '%1'"), vst2_cache_file (path)));
vst2_whitelist (path);
return 0;
}
XMLTree tree;
if (cache_file.empty ()) {
run_scan = true;
} else if (tree.read (cache_file)) {
/* valid cache file was found, now check version */
int cf_version = 0;
if (!tree.root()->get_property ("version", cf_version) || cf_version < 1) {
run_scan = true;
}
} else {
/* failed to parse XML */
run_scan = true;
}
if (!cache_only && run_scan) {
/* re/generate cache file */
psle->reset ();
vst2_blacklist (path);
if (!run_vst2_scanner_app (path, psle)) {
return -1;
}
cache_file = vst2_valid_cache_file (path);
if (cache_file.empty ()) {
psle->msg (PluginScanLogEntry::Error, _("Scan Failed."));
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
/* re-read cache file */
if (!tree.read (cache_file)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot parse VST2 cache file '%1' for plugin '%2'"), cache_file, path));
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
run_scan = false; // mark as scanned
}
if (cache_file.empty () || run_scan) {
/* cache file does not exist and cache_only == true,
* or cache file is invalid (scan needed)
*/
psle->msg (is_new ? PluginScanLogEntry::New : PluginScanLogEntry::Updated);
return -1;
}
std::string binary;
if (!tree.root()->get_property ("binary", binary) || binary != path) {
psle->msg (PluginScanLogEntry::Incompatible, string_compose (_("Invalid VST2 cache file '%1'"), cache_file)); // XXX log as error msg
psle->msg (PluginScanLogEntry::Blacklisted);
vst2_blacklist (path);
return -1;
}
std::string arch;
if (!tree.root()->get_property ("arch", arch) || arch != vst2_arch ()) {
vst2_blacklist (path);
psle->msg (PluginScanLogEntry::Blacklisted);
psle->msg (PluginScanLogEntry::Incompatible, string_compose (_("VST2 architecture mismatches '%1'"), arch));
return -1;
}
vst2_whitelist (path);
psle->set_result (PluginScanLogEntry::OK);
uint32_t discovered = 0;
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
try {
VST2Info nfo (**i);
if (vst2_plugin (path, type, nfo)) {
++discovered;
} else {
psle->msg (PluginScanLogEntry::Blacklisted);
vst2_blacklist (path);
}
} catch (...) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Corrupt VST2 cache file '%1'"), cache_file));
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST2 at '%1'\n", path));
continue;
}
}
return discovered;
}
#endif
#ifdef WINDOWS_VST_SUPPORT
void
PluginManager::windows_vst_refresh (bool cache_only)
{
if (_windows_vst_plugin_info) {
_windows_vst_plugin_info->clear ();
} else {
_windows_vst_plugin_info = new ARDOUR::PluginInfoList();
}
windows_vst_discover_from_path (Config->get_plugin_path_vst(), cache_only);
if (!cache_only) {
/* ensure that VST path is flushed to disk */
Config->save_state();
}
}
static bool windows_vst_filter (const string& str, void * /*arg*/)
{
/* Not a dotfile, has a prefix before a period, suffix is "dll" */
return str[0] != '.' && str.length() > 4 && strings_equal_ignore_case (".dll", str.substr(str.length() - 4));
}
int
PluginManager::windows_vst_discover_from_path (string path, bool cache_only)
{
vector<string> plugin_objects;
vector<string>::iterator x;
int ret = 0;
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Discovering Windows VST plugins along %1\n", path));
if (Session::get_disable_all_loaded_plugins ()) {
info << _("Disabled WindowsVST scan (safe mode)") << endmsg;
return -1;
}
find_files_matching_filter (plugin_objects, path, windows_vst_filter, 0, false, true, true);
sort (plugin_objects.begin (), plugin_objects.end ());
plugin_objects.erase (unique (plugin_objects.begin (), plugin_objects.end ()), plugin_objects.end ());
size_t n = 1;
size_t all_modules = plugin_objects.size ();
for (x = plugin_objects.begin(); x != plugin_objects.end (); ++x, ++n) {
reset_scan_cancel_state (true);
ARDOUR::PluginScanMessage (string_compose (_("VST2 (%1 / %2)"), n, all_modules), *x, !cache_only && !cancelled());
vst2_discover (*x, Windows_VST, cache_only || cancelled());
}
return ret;
}
#endif // WINDOWS_VST_SUPPORT
#ifdef MACVST_SUPPORT
void
PluginManager::mac_vst_refresh (bool cache_only)
{
if (_mac_vst_plugin_info) {
_mac_vst_plugin_info->clear ();
} else {
_mac_vst_plugin_info = new ARDOUR::PluginInfoList();
}
mac_vst_discover_from_path ("~/Library/Audio/Plug-Ins/VST:/Library/Audio/Plug-Ins/VST", cache_only);
if (!cache_only) {
/* ensure that VST path is flushed to disk */
Config->save_state();
}
}
static bool mac_vst_filter (const string& str)
{
string plist = Glib::build_filename (str, "Contents", "Info.plist");
if (!Glib::file_test (plist, Glib::FILE_TEST_IS_REGULAR)) {
return false;
}
return str[0] != '.' && str.length() > 4 && strings_equal_ignore_case (".vst", str.substr(str.length() - 4));
}
int
PluginManager::mac_vst_discover_from_path (string path, bool cache_only)
{
vector<string> plugin_objects;
vector<string>::iterator x;
if (Session::get_disable_all_loaded_plugins ()) {
info << _("Disabled MacVST scan (safe mode)") << endmsg;
return -1;
}
Searchpath paths (path);
/* customized version of run_functor_for_paths() */
for (vector<string>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
string expanded_path = path_expand (*i);
if (!Glib::file_test (expanded_path, Glib::FILE_TEST_IS_DIR)) continue;
try {
Glib::Dir dir(expanded_path);
for (Glib::DirIterator di = dir.begin(); di != dir.end(); di++) {
string fullpath = Glib::build_filename (expanded_path, *di);
/* we're only interested in bundles */
if (!Glib::file_test (fullpath, Glib::FILE_TEST_IS_DIR)) {
continue;
}
if (mac_vst_filter (fullpath)) {
plugin_objects.push_back (fullpath);
continue;
}
/* don't descend into AU bundles in the VST dir */
if (fullpath[0] == '.' || (fullpath.length() > 10 && strings_equal_ignore_case (".component", fullpath.substr(fullpath.length() - 10)))) {
continue;
}
/* recurse */
mac_vst_discover_from_path (fullpath, cache_only);
}
} catch (Glib::FileError& err) { }
}
sort (plugin_objects.begin (), plugin_objects.end ());
plugin_objects.erase (unique (plugin_objects.begin (), plugin_objects.end ()), plugin_objects.end ());
size_t n = 1;
size_t all_modules = plugin_objects.size ();
for (x = plugin_objects.begin(); x != plugin_objects.end (); ++x, ++n) {
reset_scan_cancel_state (true);
ARDOUR::PluginScanMessage (string_compose (_("VST2 (%1 / %2)"), n, all_modules), *x, !cache_only && !cancelled());
vst2_discover (*x, MacVST, cache_only || cancelled());
}
return 0;
}
#endif // MAC_VST_SUPPORT
#ifdef LXVST_SUPPORT
void
PluginManager::lxvst_refresh (bool cache_only)
{
if (_lxvst_plugin_info) {
_lxvst_plugin_info->clear ();
} else {
_lxvst_plugin_info = new ARDOUR::PluginInfoList();
}
lxvst_discover_from_path (Config->get_plugin_path_lxvst(), cache_only);
if (!cache_only) {
/* ensure that VST path is flushed to disk */
Config->save_state();
}
}
static bool lxvst_filter (const string& str, void *)
{
/* Not a dotfile, has a prefix before a period, suffix is "so" */
return str[0] != '.' && (str.length() > 3 && str.find (".so") == (str.length() - 3));
}
int
PluginManager::lxvst_discover_from_path (string path, bool cache_only)
{
vector<string> plugin_objects;
vector<string>::iterator x;
if (Session::get_disable_all_loaded_plugins ()) {
info << _("Disabled LinuxVST scan (safe mode)") << endmsg;
return -1;
}
#ifndef NDEBUG
(void) path;
#endif
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Discovering linuxVST plugins along %1\n", path));
find_files_matching_filter (plugin_objects, Config->get_plugin_path_lxvst(), lxvst_filter, 0, false, true, true);
sort (plugin_objects.begin (), plugin_objects.end ());
plugin_objects.erase (unique (plugin_objects.begin (), plugin_objects.end ()), plugin_objects.end ());
size_t n = 1;
size_t all_modules = plugin_objects.size ();
for (x = plugin_objects.begin(); x != plugin_objects.end (); ++x, ++n) {
reset_scan_cancel_state (true);
ARDOUR::PluginScanMessage (string_compose (_("VST2 (%1 / %2)"), n, all_modules), *x, !cache_only && !cancelled());
vst2_discover (*x, LXVST, cache_only || cancelled());
}
return 0;
}
#endif // LXVST_SUPPORT
void
PluginManager::clear_vst3_cache ()
{
#ifdef VST3_SUPPORT
# if defined __APPLE__ && defined __aarch64__
string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "vst-arm64");
# else
string dn = Glib::build_filename (ARDOUR::user_cache_directory(), "vst");
# endif
vector<string> v3i_files;
find_files_matching_regex (v3i_files, dn, "\\.v3i$", false);
for (vector<string>::iterator i = v3i_files.begin(); i != v3i_files.end (); ++i) {
::g_unlink(i->c_str());
}
Config->set_plugin_cache_version (0);
Config->save_state();
#endif
}
void
PluginManager::clear_vst3_blacklist ()
{
#ifdef VST3_SUPPORT
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST3_BLACKLIST);
if (Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
::g_unlink(fn.c_str());
}
#endif
}
#ifdef VST3_SUPPORT
static bool vst3_is_blacklisted (string const& module_path)
{
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST3_BLACKLIST);
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return false;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return false;
}
return bl.find (module_path + "\n") != string::npos;
}
static void vst3_blacklist (string const& module_path)
{
if (module_path.empty () || vst3_is_blacklisted (module_path)) {
return;
}
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST3_BLACKLIST);
FILE* f = NULL;
if (! (f = g_fopen (fn.c_str (), "a"))) {
PBD::error << string_compose (_("Cannot write to VST3 blacklist file '%1'"), fn) << endmsg;
return;
}
fprintf (f, "%s\n", module_path.c_str ());
::fclose (f);
}
static void vst3_whitelist (string module_path)
{
if (module_path.empty ()) {
return;
}
string fn = Glib::build_filename (ARDOUR::user_cache_directory (), VST3_BLACKLIST);
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
return;
}
std::string bl;
try {
bl = Glib::file_get_contents (fn);
} catch (Glib::FileError const& err) {
return;
}
::g_unlink (fn.c_str ());
module_path += "\n"; // add separator
const size_t rpl = bl.find (module_path);
if (rpl != string::npos) {
bl.replace (rpl, module_path.size (), "");
}
if (bl.empty ()) {
return;
}
Glib::file_set_contents (fn, bl);
}
static bool vst3_filter (const string& str, void*)
{
return str[0] != '.' && (str.length() > 4 && str.find (".vst3") == (str.length() - 5));
}
void
PluginManager::vst3_refresh (bool cache_only)
{
if (_vst3_plugin_info) {
_vst3_plugin_info->clear ();
} else {
_vst3_plugin_info = new ARDOUR::PluginInfoList();
}
#ifdef __APPLE__
vst3_discover_from_path ("~/Library/Audio/Plug-Ins/VST3:/Library/Audio/Plug-Ins/VST3", cache_only);
#elif defined PLATFORM_WINDOWS
std::string prog = PBD::get_win_special_folder_path (CSIDL_PROGRAM_FILES);
vst3_discover_from_path (Glib::build_filename (prog, "Common Files", "VST3"), cache_only);
#else
vst3_discover_from_path ("~/.vst3:/usr/local/lib/vst3:/usr/lib/vst3", cache_only);
#endif
}
int
PluginManager::vst3_discover_from_path (string const& path, bool cache_only)
{
if (Session::get_disable_all_loaded_plugins ()) {
info << _("Disabled VST3 scan (safe mode)") << endmsg;
return -1;
}
Searchpath paths (path);
if (!Config->get_plugin_path_vst3().empty ()) {
Searchpath custom (Config->get_plugin_path_vst3 ());
paths += custom;
}
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("VST3: search along: [%1]\n", paths.to_string ()));
vector<string> plugin_objects;
find_paths_matching_filter (plugin_objects, paths, vst3_filter, 0, false, true, true);
size_t n = 1;
size_t all_modules = plugin_objects.size ();
for (vector<string>::iterator i = plugin_objects.begin(); i != plugin_objects.end (); ++i, ++n) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("VST3: discover '%1'\n", *i));
reset_scan_cancel_state (true);
ARDOUR::PluginScanMessage (string_compose (_("VST3 (%1 / %2)"), n, all_modules), *i, !cache_only && !cancelled());
vst3_discover (*i, cache_only || cancelled ());
}
return cancelled() ? -1 : 0;
}
void
PluginManager::vst3_plugin (string const& module_path, string const& bundle_path, VST3Info const& i)
{
PluginInfoPtr info (new VST3PluginInfo ());
info->path = module_path;
info->index = i.index;
info->unique_id = i.uid;
info->name = i.name;
info->category = i.category; // TODO process split at "|" -> tags
info->creator = i.vendor;
info->n_inputs = ChanCount();
info->n_outputs = ChanCount();
info->n_inputs.set_audio (i.n_inputs + i.n_aux_inputs);
info->n_inputs.set_midi (i.n_midi_inputs);
info->n_outputs.set_audio (i.n_outputs + i.n_aux_outputs);
info->n_outputs.set_midi (i.n_midi_outputs);
_vst3_plugin_info->push_back (info);
scan_log_entry (VST3, bundle_path)->add (info);
if (!info->category.empty ()) {
set_tags (info->type, info->unique_id, info->category, info->name, FromPlug);
}
}
int
PluginManager::vst3_discover (string const& path, bool cache_only)
{
string module_path = module_path_vst3 (path);
if (module_path.empty ()) {
PSLEPtr psl = PSLEPtr (new PluginScanLogEntry (VST3, path));
psl->msg (PluginScanLogEntry::Error, string_compose ("Invalid VST3 Module Path: '%1'", module_path));
_plugin_scan_log.erase (psl);
_plugin_scan_log.insert (psl);
return -1;
}
/* ignore - e.g. .vst3 DLL inside .vst3 bundle */
if (module_path == "-1") {
return -1;
}
PSLEPtr psle (scan_log_entry (VST3, path));
if (vst3_is_blacklisted (module_path)) {
psle->msg (PluginScanLogEntry::Blacklisted);
return -1;
}
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("VST3: discover %1 (%2)\n", path, module_path));
bool run_scan = false;
bool is_new = false;
string cache_file = vst3_valid_cache_file (module_path, false, &is_new);
if (!cache_only && vst3_scanner_bin_path.empty () && cache_file.empty ()) {
/* scan in host context */
psle->reset ();
vst3_blacklist (module_path);
psle->msg (PluginScanLogEntry::OK, string_compose ("VST3 module-path: '%1' (internal scan)", module_path));
if (! vst3_scan_and_cache (module_path, path, sigc::mem_fun (*this, &PluginManager::vst3_plugin))) {
psle->msg (PluginScanLogEntry::Error, "Cannot load VST3");
psle->msg (PluginScanLogEntry::Blacklisted);
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST3 at '%1'\n", path));
return -1;
}
psle->msg (PluginScanLogEntry::OK, string_compose (_("Saved VST3 plugin cache to '%1'"), vst3_cache_file (module_path)));
vst3_whitelist (module_path);
return 0;
}
XMLTree tree;
if (cache_file.empty ()) {
run_scan = true;
} else if (!tree.read (cache_file)) {
/* failed to parse XML */
run_scan = true;
}
if (!cache_only && run_scan) {
/* re/generate cache file */
psle->reset ();
vst3_blacklist (module_path);
psle->msg (PluginScanLogEntry::OK, string_compose ("VST3 module-path '%1'", module_path));
if (!run_vst3_scanner_app (path, psle)) {
return -1;
}
cache_file = vst3_valid_cache_file (module_path);
if (cache_file.empty ()) {
psle->msg (PluginScanLogEntry::Blacklisted);
psle->msg (PluginScanLogEntry::Error, _("Scan Failed."));
return -1;
}
/* re-read cache file */
if (!tree.read (cache_file)) {
psle->msg (PluginScanLogEntry::Blacklisted);
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot parse VST3 cache file '%1' for plugin '%2'"), cache_file, module_path));
return -1;
}
run_scan = false; // mark as scanned
}
if (cache_file.empty () || run_scan) {
/* cache file does not exist and cache_only == true,
* or cache file is invalid (scan needed)
*/
psle->msg (is_new ? PluginScanLogEntry::New : PluginScanLogEntry::Updated);
return -1;
}
std::string module;
if (!tree.root()->get_property ("module", module) || module != module_path) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Invalid VST3 cache file '%1'"), cache_file));
psle->msg (PluginScanLogEntry::Blacklisted);
if (!vst3_is_blacklisted (path)) {
vst3_blacklist (module_path);
}
return -1;
}
vst3_whitelist (module_path);
psle->set_result (PluginScanLogEntry::OK);
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
try {
VST3Info nfo (**i);
vst3_plugin (module_path, path, nfo);
} catch (...) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Corrupt VST3 cache file '%1'"), cache_file));
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Cannot load VST3 at '%1'\n", module_path));
continue;
}
}
return 0;
}
static void vst3_scanner_log (std::string msg, std::stringstream* ss)
{
*ss << msg;
}
bool
PluginManager::run_vst3_scanner_app (std::string bundle_path, PSLEPtr psle) const
{
char **argp= (char**) calloc (5, sizeof (char*));
argp[0] = strdup (vst3_scanner_bin_path.c_str ());
argp[1] = strdup ("-f");
if (Config->get_verbose_plugin_scan()) {
argp[2] = strdup ("-v");
} else {
argp[2] = strdup ("-f");
}
argp[3] = strdup (bundle_path.c_str ());
argp[4] = 0;
stringstream scan_log;
ARDOUR::SystemExec scanner (vst3_scanner_bin_path, argp);
PBD::ScopedConnection c;
scanner.ReadStdout.connect_same_thread (c, boost::bind (&vst3_scanner_log, _1, &scan_log));
if (scanner.start (ARDOUR::SystemExec::MergeWithStdin)) {
psle->msg (PluginScanLogEntry::Error, string_compose (_("Cannot launch VST scanner app '%1': %2"), vst3_scanner_bin_path, strerror (errno)));
return false;
}
int timeout = _enable_scan_timeout ? 1 + Config->get_plugin_scan_timeout() : 0; /* deciseconds */
bool notime = (timeout <= 0);
while (scanner.is_running () && (notime || timeout > 0)) {
if (!notime && no_timeout ()) {
notime = true;
timeout = -1;
} else if (notime && !no_timeout() && _enable_scan_timeout) {
notime = false;
timeout = 1 + Config->get_plugin_scan_timeout ();
}
if (timeout > -864000) {
--timeout;
}
ARDOUR::PluginScanTimeout (timeout);
Glib::usleep (100000);
if (cancelled () || (!notime && timeout == 0)) {
scanner.terminate ();
psle->msg (PluginScanLogEntry::OK, scan_log.str());
if (cancelled ()) {
psle->msg (PluginScanLogEntry::New, "Scan was cancelled.");
} else {
psle->msg (PluginScanLogEntry::TimeOut, "Scan Timed Out.");
}
/* may be partially written */
std::string module_path = module_path_vst3 (bundle_path);
if (!module_path.empty ()) {
g_unlink (vst3_cache_file (module_path).c_str ());
}
vst3_whitelist (module_path);
return false;
}
}
psle->msg (PluginScanLogEntry::OK, scan_log.str());
return true;
}
#endif // VST3_SUPPORT
PluginManager::PluginStatusType
PluginManager::get_status (const PluginInfoPtr& pi) const
{
PluginStatus ps (pi->type, pi->unique_id);
PluginStatusList::const_iterator i = find (statuses.begin(), statuses.end(), ps);
if (i == statuses.end()) {
return Normal;
} else {
return i->status;
}
}
void
PluginManager::save_statuses ()
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_statuses");
stringstream ofs;
for (PluginStatusList::iterator i = statuses.begin(); i != statuses.end(); ++i) {
if ((*i).status == Concealed) {
continue;
}
switch ((*i).type) {
case LADSPA:
ofs << "LADSPA";
break;
case AudioUnit:
ofs << "AudioUnit";
break;
case LV2:
ofs << "LV2";
break;
case Windows_VST:
ofs << "Windows-VST";
break;
case LXVST:
ofs << "LXVST";
break;
case MacVST:
ofs << "MacVST";
break;
case VST3:
ofs << "VST3";
break;
case Lua:
ofs << "Lua";
break;
}
ofs << ' ';
switch ((*i).status) {
case Normal:
ofs << "Normal";
break;
case Favorite:
ofs << "Favorite";
break;
case Hidden:
ofs << "Hidden";
break;
case Concealed:
ofs << "Hidden";
assert (0);
break;
}
ofs << ' ';
ofs << (*i).unique_id;;
ofs << endl;
}
g_file_set_contents (path.c_str(), ofs.str().c_str(), -1, NULL);
}
void
PluginManager::load_statuses ()
{
std::string path;
find_file (plugin_metadata_search_path(), "plugin_statuses", path); // note: if no user folder is found, this will find the resources path
gchar *fbuf = NULL;
if (!g_file_get_contents (path.c_str(), &fbuf, NULL, NULL)) {
return;
}
stringstream ifs (fbuf);
g_free (fbuf);
std::string stype;
std::string sstatus;
std::string id;
PluginType type;
PluginStatusType status;
char buf[1024];
while (ifs) {
ifs >> stype;
if (!ifs) {
break;
}
ifs >> sstatus;
if (!ifs) {
break;
}
/* rest of the line is the plugin ID */
ifs.getline (buf, sizeof (buf), '\n');
if (!ifs) {
break;
}
if (sstatus == "Normal") {
status = Normal;
} else if (sstatus == "Favorite") {
status = Favorite;
} else if (sstatus == "Hidden") {
status = Hidden;
} else {
error << string_compose (_("unknown plugin status type \"%1\" - all entries ignored"), sstatus) << endmsg;
statuses.clear ();
break;
}
if (stype == "LADSPA") {
type = LADSPA;
} else if (stype == "AudioUnit") {
type = AudioUnit;
} else if (stype == "LV2") {
type = LV2;
} else if (stype == "Windows-VST") {
type = Windows_VST;
} else if (stype == "LXVST") {
type = LXVST;
} else if (stype == "MacVST") {
type = MacVST;
} else if (stype == "Lua") {
type = Lua;
} else if (stype == "VST3") {
type = VST3;
} else {
error << string_compose (_("unknown plugin type \"%1\" - ignored"), stype)
<< endmsg;
continue;
}
id = buf;
strip_whitespace_edges (id);
set_status (type, id, status);
}
}
void
PluginManager::set_status (PluginType t, string const& id, PluginStatusType status)
{
PluginStatus ps (t, id, status);
statuses.erase (ps);
if (status != Normal && status != Concealed) {
statuses.insert (ps);
}
PluginStatusChanged (t, id, status); /* EMIT SIGNAL */
}
void
PluginManager::save_stats ()
{
// TODO: consider thinning out the list
// (LRU > 1 year? or LRU > 2 weeks && use_count < 10% of avg use-count)
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_stats");
XMLNode* root = new XMLNode (X_("PluginStats"));
for (PluginStatsList::iterator i = statistics.begin(); i != statistics.end(); ++i) {
XMLNode* node = root->add_child (X_("Plugin"));
node->set_property (X_("type"), (*i).type);
node->set_property (X_("id"), (*i).unique_id);
node->set_property (X_("lru"), (*i).lru);
node->set_property (X_("use-count"), (*i).use_count);
}
XMLTree tree;
tree.set_root (root);
if (!tree.write (path)) {
error << string_compose (_("Could not save Plugin Statistics to %1"), path) << endmsg;
}
}
void
PluginManager::load_stats ()
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_stats");
if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
return;
}
info << string_compose (_("Loading plugin statistics file %1"), path) << endmsg;
XMLTree tree;
if (!tree.read (path)) {
error << string_compose (_("Cannot parse plugin statistics from %1"), path) << endmsg;
return;
}
PluginStatsList stats;
float avg_age = 0;
float avg_cnt = 0;
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i)
{
PluginType type;
string id;
int64_t lru;
uint64_t use_count;
if (!(*i)->get_property (X_("type"), type) ||
!(*i)->get_property (X_("id"), id) ||
!(*i)->get_property (X_("lru"), lru) ||
!(*i)->get_property (X_("use-count"), use_count)) {
continue;
}
avg_age += lru;
avg_cnt += use_count;
PluginStats ps (type, id, lru, use_count);
stats.insert (ps);
}
if (stats.size () > 0) {
avg_age /= stats.size ();
avg_cnt /= stats.size ();
}
statistics.clear ();
for (PluginStatsList::iterator i = stats.begin(); i != stats.end(); ++i) {
/* we use average age in case ardour has not been used for a while,
* ignoring old plugins changes the average age, so we only flush
* the least use plugins.
*/
if (i->lru + 2592000 /*30 days*/ < avg_age && i->use_count < avg_cnt / 2) {
#ifndef NDEBUG
std::cout << "- Zero stats of plugin '" << i->unique_id << "' use-count: " << i->use_count << " LRU: " << (time(NULL) - i->lru) / 3600 << std::endl;
#endif
continue;
}
if (i->lru + 604800 /*7 days*/ < avg_age && i->use_count < 2) {
#ifndef NDEBUG
std::cout << "- Zero stats of plugin '" << i->unique_id << "' use-count: " << i->use_count << " LRU: " << (time(NULL) - i->lru) / 3600 << std::endl;
#endif
continue;
}
statistics.insert (*i);
}
}
void
PluginManager::reset_stats ()
{
statistics.clear ();
PluginStatsChanged (); /* EMIT SIGNAL */
save_stats ();
}
void
PluginManager::stats_use_plugin (PluginInfoPtr const& pip)
{
PluginStats ps (pip->type, pip->unique_id, time (NULL));
PluginStatsList::const_iterator i = find (statistics.begin(), statistics.end(), ps);
if (i == statistics.end()) {
ps.use_count = 1;
statistics.insert (ps);
} else {
ps.use_count = i->use_count + 1;
statistics.erase (ps);
statistics.insert (ps);
}
PluginStatsChanged (); /* EMIT SIGNAL */
save_stats (); // XXX
}
bool
PluginManager::stats (PluginInfoPtr const& pip, int64_t& lru, uint64_t& use_count) const
{
PluginStats ps (pip->type, pip->unique_id, time (NULL));
PluginStatsList::const_iterator i = find (statistics.begin(), statistics.end(), ps);
if (i == statistics.end()) {
return false;
}
lru = i->lru;
use_count = i->use_count;
return true;
}
PluginType
PluginManager::to_generic_vst (const PluginType t)
{
switch (t) {
case Windows_VST:
case LXVST:
case MacVST:
return LXVST;
default:
break;
}
return t;
}
std::string
PluginManager::plugin_type_name (const PluginType t, bool short_name)
{
#if defined WINDOWS_VST_SUPPORT && defined LXVST_SUPPORT
switch (t) {
case Windows_VST:
return short_name ? "VST" : "Windows-VST";
case LXVST:
return short_name ? "LXVST" : "Linux-VST";
default:
break;
}
#endif
switch (t) {
case Windows_VST:
case LXVST:
case MacVST:
return short_name ? "VST" : "VST2";
case AudioUnit:
return short_name ? "AU" : enum_2_string (t);
case LADSPA:
return short_name ? "LV1" : enum_2_string (t);
default:
return enum_2_string (t);
}
}
struct SortByTag {
bool operator() (std::string a, std::string b) {
return a.compare (b) < 0;
}
};
vector<std::string>
PluginManager::get_tags (const PluginInfoPtr& pi) const
{
vector<std::string> tags;
PluginTag ps (to_generic_vst(pi->type), pi->unique_id, "", "", FromPlug);
PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
if (i != ptags.end ()) {
PBD::tokenize (i->tags, string(" "), std::back_inserter (tags), true);
SortByTag sorter;
sort (tags.begin(), tags.end(), sorter);
}
return tags;
}
std::string
PluginManager::get_tags_as_string (PluginInfoPtr const& pi) const
{
std::string ret;
vector<std::string> tags = get_tags(pi);
for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
if (t != tags.begin ()) {
ret.append(" ");
}
ret.append(*t);
}
return ret;
}
std::string
PluginManager::user_plugin_metadata_dir () const
{
std::string dir = Glib::build_filename (user_config_directory(), plugin_metadata_dir_name);
g_mkdir_with_parents (dir.c_str(), 0744);
return dir;
}
bool
PluginManager::load_plugin_order_file (XMLNode &n) const
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_order");
info << string_compose (_("Loading plugin order file %1"), path) << endmsg;
if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
return false;
}
XMLTree tree;
if (tree.read (path)) {
n = *(tree.root());
return true;
} else {
error << string_compose (_("Cannot parse Plugin Order info from %1"), path) << endmsg;
return false;
}
}
void
PluginManager::save_plugin_order_file (XMLNode &elem) const
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_order");
XMLTree tree;
tree.set_root (&elem);
if (!tree.write (path)) {
error << string_compose (_("Could not save Plugin Order info to %1"), path) << endmsg;
}
tree.set_root (0); // note: must disconnect the elem from XMLTree, or it will try to delete memory it didn't allocate
}
std::string
PluginManager::dump_untagged_plugins ()
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "untagged_plugins");
XMLNode* root = new XMLNode (X_("PluginTags"));
for (PluginTagList::iterator i = ptags.begin(); i != ptags.end(); ++i) {
#ifdef MIXBUS
if ((*i).type == LADSPA) {
uint32_t id = atoi ((*i).unique_id);
if (id >= 9300 && id <= 9399) {
continue; /* do not write mixbus channelstrip ladspa's in the tagfile */
}
}
#endif
if ((*i).tagtype == FromPlug) {
XMLNode* node = new XMLNode (X_("Plugin"));
node->set_property (X_("type"), to_generic_vst ((*i).type));
node->set_property (X_("id"), (*i).unique_id);
node->set_property (X_("tags"), (*i).tags);
node->set_property (X_("name"), (*i).name);
root->add_child_nocopy (*node);
}
}
XMLTree tree;
tree.set_root (root);
if (tree.write (path)) {
return path;
} else {
return "";
}
}
void
PluginManager::save_tags ()
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_tags");
XMLNode* root = new XMLNode (X_("PluginTags"));
for (PluginTagList::iterator i = ptags.begin(); i != ptags.end(); ++i) {
#ifdef MIXBUS
if ((*i).type == LADSPA) {
uint32_t id = atoi ((*i).unique_id);
if (id >= 9300 && id <= 9399) {
continue; /* do not write mixbus channelstrip ladspa's in the tagfile */
}
}
#endif
if ((*i).tagtype <= FromFactoryFile) {
/* user file should contain only plugins that are user-tagged */
continue;
}
XMLNode* node = new XMLNode (X_("Plugin"));
node->set_property (X_("type"), to_generic_vst ((*i).type));
node->set_property (X_("id"), (*i).unique_id);
node->set_property (X_("tags"), (*i).tags);
node->set_property (X_("name"), (*i).name);
node->set_property (X_("user-set"), "1");
root->add_child_nocopy (*node);
}
XMLTree tree;
tree.set_root (root);
if (!tree.write (path)) {
error << string_compose (_("Could not save Plugin Tags info to %1"), path) << endmsg;
}
}
void
PluginManager::load_tags ()
{
vector<std::string> tmp;
find_files_matching_pattern (tmp, plugin_metadata_search_path (), "plugin_tags");
for (vector<std::string>::const_reverse_iterator p = tmp.rbegin ();
p != (vector<std::string>::const_reverse_iterator)tmp.rend(); ++p) {
std::string path = *p;
info << string_compose (_("Loading plugin meta data file %1"), path) << endmsg;
if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
return;
}
XMLTree tree;
if (!tree.read (path)) {
error << string_compose (_("Cannot parse plugin tag info from %1"), path) << endmsg;
return;
}
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
PluginType type;
string id;
string tags;
string name;
bool user_set;
if (!(*i)->get_property (X_("type"), type) ||
!(*i)->get_property (X_("id"), id) ||
!(*i)->get_property (X_("tags"), tags) ||
!(*i)->get_property (X_("name"), name)) {
continue;
}
if (!(*i)->get_property (X_("user-set"), user_set)) {
user_set = false;
}
strip_whitespace_edges (tags);
set_tags (type, id, tags, name, user_set ? FromUserFile : FromFactoryFile);
}
}
}
void
PluginManager::set_tags (PluginType t, string id, string tag, std::string name, TagType ttype)
{
string sanitized = sanitize_tag (tag);
PluginTag ps (to_generic_vst (t), id, sanitized, name, ttype);
PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
if (i == ptags.end()) {
ptags.insert (ps);
} else if ((uint32_t) ttype >= (uint32_t) (*i).tagtype) { // only overwrite if we are more important than the existing. Gui > UserFile > FactoryFile > Plugin
ptags.erase (ps);
ptags.insert (ps);
}
if (ttype == FromFactoryFile) {
if (find (ftags.begin(), ftags.end(), ps) != ftags.end()) {
ftags.erase (ps);
}
ftags.insert (ps);
}
if (ttype == FromGui) {
PluginTagChanged (t, id, sanitized); /* EMIT SIGNAL */
}
}
void
PluginManager::reset_tags (PluginInfoPtr const& pi)
{
PluginTag ps (pi->type, pi->unique_id, pi->category, pi->name, FromPlug);
PluginTagList::const_iterator j = find (ftags.begin(), ftags.end(), ps);
if (j != ftags.end()) {
ps.tags = j->tags;
ps.tagtype = j->tagtype;
}
PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
if (i != ptags.end()) {
ptags.erase (ps);
ptags.insert (ps);
PluginTagChanged (ps.type, ps.unique_id, ps.tags); /* EMIT SIGNAL */
}
}
std::string
PluginManager::sanitize_tag (const std::string to_sanitize) const
{
if (to_sanitize.empty ()) {
return "";
}
string sanitized = to_sanitize;
vector<string> tags;
if (!PBD::tokenize (sanitized, string(" ,\n"), std::back_inserter (tags), true)) {
#ifndef NDEBUG
cerr << _("PluginManager::sanitize_tag could not tokenize string: ") << sanitized << endmsg;
#endif
return "";
}
/* convert tokens to lower-case, space-separated list */
sanitized = "";
for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
if (t != tags.begin ()) {
sanitized.append(" ");
}
sanitized.append (downcase (*t));
}
return sanitized;
}
std::vector<std::string>
PluginManager::get_all_tags (TagFilter tag_filter) const
{
std::vector<std::string> ret;
PluginTagList::const_iterator pt;
for (pt = ptags.begin(); pt != ptags.end(); ++pt) {
if ((*pt).tags.empty ()) {
continue;
}
/* if favorites_only then we need to check the info ptr and maybe skip */
if (tag_filter == OnlyFavorites) {
PluginStatus stat ((*pt).type, (*pt).unique_id);
PluginStatusList::const_iterator i = find (statuses.begin(), statuses.end(), stat);
if ((i != statuses.end()) && (i->status == Favorite)) {
/* it's a favorite! */
} else {
continue;
}
}
if (tag_filter == NoHidden) {
PluginStatus stat ((*pt).type, (*pt).unique_id);
PluginStatusList::const_iterator i = find (statuses.begin(), statuses.end(), stat);
if ((i != statuses.end()) && ((i->status == Hidden) || (i->status == Concealed))) {
continue;
}
}
/* parse each plugin's tag string into separate tags */
vector<string> tags;
if (!PBD::tokenize ((*pt).tags, string(" "), std::back_inserter (tags), true)) {
#ifndef NDEBUG
cerr << _("PluginManager: Could not tokenize string: ") << (*pt).tags << endmsg;
#endif
continue;
}
/* maybe add the tags we've found */
for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
/* if this tag isn't already in the list, add it */
vector<string>::iterator i = find (ret.begin(), ret.end(), *t);
if (i == ret.end()) {
ret.push_back (*t);
}
}
}
/* sort in alphabetical order */
SortByTag sorter;
sort (ret.begin(), ret.end(), sorter);
return ret;
}
const ARDOUR::PluginInfoList&
PluginManager::windows_vst_plugin_info ()
{
#ifdef WINDOWS_VST_SUPPORT
if (_windows_vst_plugin_info) {
return *_windows_vst_plugin_info;
}
#endif
return _empty_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::mac_vst_plugin_info ()
{
#ifdef MACVST_SUPPORT
if (_mac_vst_plugin_info) {
return *_mac_vst_plugin_info;
}
#endif
return _empty_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::lxvst_plugin_info ()
{
#ifdef LXVST_SUPPORT
if(_lxvst_plugin_info) {
return *_lxvst_plugin_info;
}
#endif
return _empty_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::ladspa_plugin_info ()
{
assert(_ladspa_plugin_info);
return *_ladspa_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::lv2_plugin_info ()
{
assert(_lv2_plugin_info);
return *_lv2_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::au_plugin_info ()
{
#ifdef AUDIOUNIT_SUPPORT
if (_au_plugin_info) {
return *_au_plugin_info;
}
#endif
return _empty_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::vst3_plugin_info ()
{
#ifdef VST3_SUPPORT
if (_vst3_plugin_info) {
return *_vst3_plugin_info;
}
#endif
return _empty_plugin_info;
}
const ARDOUR::PluginInfoList&
PluginManager::lua_plugin_info ()
{
assert(_lua_plugin_info);
return *_lua_plugin_info;
}
/* ****************************************************************************/
void
PluginManager::blacklist (ARDOUR::PluginType type, std::string const& path_uid)
{
PluginInfoList* pil = 0;
switch (type) {
case AudioUnit:
#ifdef AUDIOUNIT_SUPPORT
auv2_blacklist (path_uid);
pil = _au_plugin_info;
#endif
break;
case Windows_VST:
#ifdef WINDOWS_VST_SUPPORT
vst2_blacklist (path_uid);
pil = _windows_vst_plugin_info;
#endif
break;
case LXVST:
#ifdef LXVST_SUPPORT
vst2_blacklist (path_uid);
pil = _lxvst_plugin_info;
#endif
break;
case MacVST:
#ifdef MACVST_SUPPORT
vst2_blacklist (path_uid);
pil = _mac_vst_plugin_info;
#endif
break;
case VST3:
#ifdef VST3_SUPPORT
vst3_blacklist (module_path_vst3 (path_uid));
pil = _vst3_plugin_info;
#endif
break;
default:
return;
}
PSLEPtr psle (scan_log_entry (type, path_uid));
psle->msg (PluginScanLogEntry::Blacklisted);
save_scanlog ();
if (!pil) {
return;
}
/* remove existing info */
PluginScanLog::iterator i = _plugin_scan_log.find (PSLEPtr (new PluginScanLogEntry (type, path_uid)));
if (i != _plugin_scan_log.end ()) {
PluginInfoList const& plugs ((*i)->nfo ());
for (PluginInfoList::const_iterator j = plugs.begin(); j != plugs.end(); ++j) {
PluginInfoList::iterator k = std::find (pil->begin(), pil->end(), *j);
if (k != pil->end()) {
pil->erase (k);
}
}
}
PluginListChanged (); /* EMIT SIGNAL */
}
bool
PluginManager::whitelist (ARDOUR::PluginType type, std::string const& path_uid, bool force)
{
if (!force) {
PluginScanLog::iterator i = _plugin_scan_log.find (PSLEPtr (new PluginScanLogEntry (type, path_uid)));
if (i == _plugin_scan_log.end ()) {
/* plugin was not found */
return false;
}
/* only allow manually blacklisted (no error) */
if ((*i)->result () != PluginScanLogEntry::Blacklisted) {
return false;
}
}
switch (type) {
case AudioUnit:
#ifdef AUDIOUNIT_SUPPORT
auv2_whitelist (path_uid);
return true;
#endif
break;
case Windows_VST:
case LXVST:
case MacVST:
#if (defined WINDOWS_VST_SUPPORT || defined MACVST_SUPPORT || defined LXVST_SUPPORT)
vst2_whitelist (path_uid);
return true;
#endif
break;
case VST3:
#ifdef VST3_SUPPORT
vst3_whitelist (module_path_vst3 (path_uid));
return true;
#endif
break;
default:
break;
}
return false;
}
std::string
PluginManager::cache_file (ARDOUR::PluginType type, std::string const& path_uid)
{
std::string fn;
switch (type) {
case AudioUnit:
#ifdef AUDIOUNIT_SUPPORT
{
AUv2DescStr aud (path_uid);
fn = auv2_cache_file (aud.desc ());
}
#endif
break;
case Windows_VST:
case LXVST:
case MacVST:
#if (defined WINDOWS_VST_SUPPORT || defined MACVST_SUPPORT || defined LXVST_SUPPORT)
fn = ARDOUR::vst2_cache_file (path_uid);
#endif
break;
case VST3:
#ifdef VST3_SUPPORT
fn = ARDOUR::vst3_cache_file (module_path_vst3 (path_uid));
#endif
break;
default:
break;
}
if (!fn.empty () && !Glib::file_test (fn, Glib::FileTest (Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_REGULAR))) {
return "";
}
return fn;
}
bool
PluginManager::rescan_plugin (ARDOUR::PluginType type, std::string const& path_uid, size_t num, size_t den)
{
PluginInfoList* pil = 0;
switch (type) {
case AudioUnit:
pil = _au_plugin_info;
break;
case Windows_VST:
pil = _windows_vst_plugin_info;
break;
case LXVST:
pil = _lxvst_plugin_info;
break;
case MacVST:
pil = _mac_vst_plugin_info;
break;
case VST3:
pil = _vst3_plugin_info;
// XXX resolve VST module to bundle
// string module_path = module_path_vst3 (uid);
break;
case LADSPA:
pil = _ladspa_plugin_info;
break;
default:
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Invalid plugin type for rescan: %1\n", type));
return false;
}
if (!pil) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("Rescan of '%1' without initial scan\n", type));
return false;
}
bool erased = false;
/* remove existing info */
PluginScanLog::iterator i = _plugin_scan_log.find (PSLEPtr (new PluginScanLogEntry (type, path_uid)));
if (i != _plugin_scan_log.end ()) {
PluginInfoList const& plugs ((*i)->nfo ());
for (PluginInfoList::const_iterator j = plugs.begin(); j != plugs.end(); ++j) {
PluginInfoList::iterator k = std::find (pil->begin(), pil->end(), *j);
if (k != pil->end()) {
pil->erase (k);
}
erased = true;
}
_plugin_scan_log.erase (i);
}
reset_scan_cancel_state (den < 2 ? false : true);
whitelist (type, path_uid, true);
/* force re-scan, remove cache file */
std::string fn = cache_file (type, path_uid);
if (!fn.empty ()) {
::g_unlink (fn.c_str ());
}
int rv = -1;
switch (type) {
case AudioUnit:
#ifdef AUDIOUNIT_SUPPORT
{
AUv2DescStr aud (path_uid);
if (den > 1) {
ARDOUR::PluginScanMessage (string_compose (_("AUv2 (%1 / %2)"), num, den), aud.to_s(), !cancelled());
} else {
ARDOUR::PluginScanMessage (_("AUv2"), aud.to_s(), !cancelled());
}
rv = auv2_discover (aud, false);
}
#endif
break;
case Windows_VST:
case LXVST:
case MacVST:
#if (defined WINDOWS_VST_SUPPORT || defined MACVST_SUPPORT || defined LXVST_SUPPORT)
if (den > 1) {
ARDOUR::PluginScanMessage (string_compose (_("VST2 (%1 / %2)"), num, den), path_uid, !cancelled());
} else {
ARDOUR::PluginScanMessage (_("VST2"), path_uid, !cancelled());
}
rv = vst2_discover (path_uid, type, false);
#endif
break;
case VST3:
#ifdef VST3_SUPPORT
if (den > 1) {
ARDOUR::PluginScanMessage (string_compose (_("VST3 (%1 / %2)"), num, den), path_uid, !cancelled());
} else {
ARDOUR::PluginScanMessage (_("VST3"), path_uid, !cancelled());
}
rv = vst3_discover (path_uid, false);
#endif
break;
case LADSPA:
if (den > 1) {
ARDOUR::PluginScanMessage (string_compose (_("LADSPA (%1 / %2)"), num, den), path_uid, !cancelled());
} else {
ARDOUR::PluginScanMessage (_("LADSPA"), path_uid, !cancelled());
}
rv = ladspa_discover (path_uid);
break;
default:
return false;
}
if (den > 1) {
return (rv >= 0 || erased);
}
reset_scan_cancel_state ();
if (rv < 0) {
save_scanlog ();
if (erased) {
PluginListChanged (); /* EMIT SIGNAL */
} else {
PluginScanLogChanged (); /* EMIT SIGNAL */
}
PluginScanMessage(X_("closeme"), "", false);
return false;
}
PluginScanMessage(X_("closeme"), "", false);
detect_ambiguities ();
return true;
}
void
PluginManager::rescan_faulty ()
{
bool changed = false;
/* rescan can change _plugin_scan_log, so we need to make a copy first */
PluginScanLog psl;
for (PluginScanLog::const_iterator i = _plugin_scan_log.begin(); i != _plugin_scan_log.end(); ++i) {
if (!(*i)->recent() || ((*i)->result() & PluginScanLogEntry::Faulty) != PluginScanLogEntry::OK) {
psl.insert (*i);
}
}
reset_scan_cancel_state ();
size_t n = 1;
size_t all_modules = psl.size ();
for (PluginScanLog::const_iterator i = psl.begin(); i != psl.end(); ++i, ++n) {
changed |= rescan_plugin ((*i)->type (), (*i)->path (), n, all_modules);
if (_cancel_scan_all) {
break;
}
}
reset_scan_cancel_state ();
PluginScanMessage(X_("closeme"), "", false);
if (changed) {
detect_ambiguities ();
} else {
save_scanlog ();
PluginScanLogChanged ();/* EMIT SIGNAL */
}
}
/* ****************************************************************************/
void
PluginManager::scan_log (std::vector<std::shared_ptr<PluginScanLogEntry> >& l) const
{
for (PluginScanLog::const_iterator i = _plugin_scan_log.begin(); i != _plugin_scan_log.end(); ++i) {
l.push_back (*i);
}
}
void
PluginManager::clear_stale_log ()
{
bool erased = false;
for (PluginScanLog::const_iterator i = _plugin_scan_log.begin(); i != _plugin_scan_log.end();) {
if (!(*i)->recent()) {
whitelist ((*i)->type (), (*i)->path (), true);
std::string fn = cache_file ((*i)->type (), (*i)->path ());
if (!fn.empty ()) {
DEBUG_TRACE (DEBUG::PluginManager, string_compose ("unlink stale cache: %1\n", fn));
::g_unlink (fn.c_str ());
}
i = _plugin_scan_log.erase (i);
erased = true;
} else {
++i;
}
}
if (erased) {
save_scanlog ();
PluginScanLogChanged ();/* EMIT SIGNAL */
}
}
void
PluginManager::load_scanlog ()
{
_plugin_scan_log.clear ();
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "scan_log");
if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
return;
}
XMLTree tree;
if (!tree.read (path)) {
error << string_compose (_("Cannot load Plugin Scan Log from '%1'."), path) << endmsg;
return;
}
for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
try {
_plugin_scan_log.insert (PSLEPtr (new PluginScanLogEntry (**i)));
} catch (...) {
error << string_compose (_("Plugin Scan Log '%1' contains invalid information."), path) << endmsg;
}
}
}
void
PluginManager::save_scanlog ()
{
std::string path = Glib::build_filename (user_plugin_metadata_dir(), "scan_log");
XMLNode* root = new XMLNode (X_("PluginScanLog"));
root->set_property ("version", 1);
for (PluginScanLog::const_iterator i = _plugin_scan_log.begin(); i != _plugin_scan_log.end(); ++i) {
XMLNode& node = (*i)->state ();
root->add_child_nocopy (node);
}
XMLTree tree;
tree.set_root (root);
if (!tree.write (path)) {
error << string_compose (_("Could not save Plugin Scan Log to %1"), path) << endmsg;
}
}