Audacity 3.2.0
ModuleManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ModuleManager.cpp
6
7 Dominic Mazzoni
8 James Crook
9
10
11*******************************************************************//*******************************************************************/
20
21#include "ModuleManager.h"
22#include "PluginProvider.h"
23
24#include "BasicUI.h"
25
26#include <wx/dynlib.h>
27#include <wx/log.h>
28#include <wx/string.h>
29#include <wx/filename.h>
30
31#include "FileNames.h"
32#include "MemoryX.h"
33
34#include "PluginInterface.h"
35
36#ifdef EXPERIMENTAL_MODULE_PREFS
37#include "Prefs.h"
38#include "ModuleSettings.h"
39#endif
40
41#define initFnName "ExtensionModuleInit"
42#define versionFnName "GetVersionString"
43
44//typedef wxString (*tVersionFn)();
45typedef wxChar * (*tVersionFn)();
46
48 : mName{ name }
49{
50 mLib = std::make_unique<wxDynamicLibrary>();
51 mDispatch = NULL;
52}
53
55{
56}
57
59{
60 using namespace BasicUI;
61 return ShowMessageBox(msg,
62 MessageBoxOptions{}.Caption(XO("Module Unsuitable")));
63}
64
65void Module::ShowLoadFailureError(const wxString &Error)
66{
67 auto ShortName = wxFileName(mName).GetName();
69 XO("Unable to load the \"%s\" module.\n\nError: %s")
70 .Format(ShortName, Error));
71 wxLogMessage(wxT("Unable to load the module \"%s\". Error: %s"), mName, Error);
72}
73
74bool Module::Load(wxString &deferredErrorMessage)
75{
76 deferredErrorMessage.clear();
77 // Will this ever happen???
78 if (mLib->IsLoaded()) {
79 if (mDispatch) {
80 return true;
81 }
82
83 // Any messages should have already been generated the first time it was loaded.
84 return false;
85 }
86
87 auto ShortName = wxFileName(mName).GetName();
88
89 if (!mLib->Load(mName, wxDL_NOW | wxDL_QUIET | wxDL_GLOBAL)) {
90 // For this failure path, only, there is a possibility of retrial
91 // after some other dependency of this module is loaded. So the
92 // error is not immediately reported.
93 deferredErrorMessage = wxString(wxSysErrorMsg());
94 return false;
95 }
96
97 // Check version string matches. (For now, they must match exactly)
98 tVersionFn versionFn = (tVersionFn)(mLib->GetSymbol(wxT(versionFnName)));
99 if (versionFn == NULL){
101 XO("The module \"%s\" does not provide a version string.\n\nIt will not be loaded.")
102 .Format( ShortName));
103 wxLogMessage(wxT("The module \"%s\" does not provide a version string. It will not be loaded."), mName);
104 mLib->Unload();
105 return false;
106 }
107
108 wxString moduleVersion = versionFn();
109 if( moduleVersion != AUDACITY_VERSION_STRING) {
111 XO("The module \"%s\" is matched with Audacity version \"%s\".\n\nIt will not be loaded.")
112 .Format(ShortName, moduleVersion));
113 wxLogMessage(wxT("The module \"%s\" is matched with Audacity version \"%s\". It will not be loaded."), mName, moduleVersion);
114 mLib->Unload();
115 return false;
116 }
117
119 if (!mDispatch) {
120 // Module does not provide a dispatch function.
121 return true;
122 }
123
124 // However if we do have it and it does not work,
125 // then the module is bad.
126 bool res = ((mDispatch(ModuleInitialize))!=0);
127 if (res) {
128 return true;
129 }
130
131 mDispatch = NULL;
132
134 XO("The module \"%s\" failed to initialize.\n\nIt will not be loaded.")
135 .Format(ShortName));
136 wxLogMessage(wxT("The module \"%s\" failed to initialize.\nIt will not be loaded."), mName);
137 mLib->Unload();
138
139 return false;
140}
141
142// This isn't yet used?
144{
145 if (mLib->IsLoaded()) {
146 if (mDispatch)
148 }
149
150 mLib->Unload();
151}
152
154{
155 if (mLib->IsLoaded())
156 if( mDispatch != NULL )
157 return mDispatch(type);
158
159 return 0;
160}
161
162void * Module::GetSymbol(const wxString &name)
163{
164 return mLib->GetSymbol(name);
165}
166
167// ============================================================================
168//
169// ModuleManager
170//
171// ============================================================================
172
173// The one and only ModuleManager
174std::unique_ptr<ModuleManager> ModuleManager::mInstance{};
175
176// Give builtin providers a means to identify themselves
177using BuiltinProviderList = std::vector<PluginProviderFactory>;
178namespace {
180 {
181 static BuiltinProviderList theList;
182 return theList;
183 }
184}
185
187{
188 auto &list = builtinProviderList();
189 if(pluginProviderFactory)
190 list.push_back(std::move(pluginProviderFactory));
191}
192
194{
195 auto &list = builtinProviderList();
196 auto end = list.end(), iter = std::find(list.begin(), end, pluginProviderFactory);
197 if (iter != end)
198 list.erase(iter);
199}
200
201// ----------------------------------------------------------------------------
202// Creation/Destruction
203// ----------------------------------------------------------------------------
204
206{
207}
208
210{
211 mProviders.clear();
212 builtinProviderList().clear();
213}
214
215// static
217{
218 const auto &audacityPathList = FileNames::AudacityPathList();
219 FilePaths pathList;
220 wxString pathVar;
221
222 // Code from LoadLadspa that might be useful in load modules.
223 pathVar = wxGetenv(wxT("AUDACITY_MODULES_PATH"));
224 if (!pathVar.empty())
225 FileNames::AddMultiPathsToPathList(pathVar, pathList);
226
227 for (const auto &path : audacityPathList) {
228 wxString prefix = path + wxFILE_SEP_PATH;
229 FileNames::AddUniquePathToPathList(prefix + wxT("modules"),
230 pathList);
231 if (files.size()) {
232 break;
233 }
234 }
235
236 #if defined(__WXMSW__)
237 FileNames::FindFilesInPathList(wxT("*.dll"), pathList, files);
238 #else
239 FileNames::FindFilesInPathList(wxT("*.so"), pathList, files);
240 #endif
241}
242
244 const FilePaths &files, FilePaths &decided, DelayedErrors &errors)
245{
246 FilePaths checked;
247 wxString saveOldCWD = ::wxGetCwd();
248 auto cleanup = finally([&]{ ::wxSetWorkingDirectory(saveOldCWD); });
249 for (const auto &file : files) {
250 // As a courtesy to some modules that might be bridges to
251 // open other modules, we set the current working
252 // directory to be the module's directory.
253 auto prefix = ::wxPathOnly(file);
254 ::wxSetWorkingDirectory(prefix);
255
256 // Only process the first module encountered in the
257 // defined search sequence.
258 wxString ShortName = wxFileName( file ).GetName();
259 if( checked.Index( ShortName, false ) != wxNOT_FOUND )
260 continue;
261 checked.Add( ShortName );
262
263 // Skip if a previous pass through this function decided it already
264 if( decided.Index( ShortName, false ) != wxNOT_FOUND )
265 continue;
266
267#ifdef EXPERIMENTAL_MODULE_PREFS
268 int iModuleStatus = ModuleSettings::GetModuleStatus( file );
269 if( iModuleStatus == kModuleDisabled )
270 continue;
271 if( iModuleStatus == kModuleFailed )
272 continue;
273 // New module? You have to go and explicitly enable it.
274 if( iModuleStatus == kModuleNew ){
275 // To ensure it is noted in config file and so
276 // appears on modules page.
278 continue;
279 }
280
281 if( iModuleStatus == kModuleAsk )
282#endif
283 // JKC: I don't like prompting for the plug-ins individually
284 // I think it would be better to show the module prefs page,
285 // and let the user decide for each one.
286 {
287 auto msg = XO("Module \"%s\" found.").Format( ShortName );
288 msg += XO("\n\nOnly use modules from trusted sources");
289 const TranslatableStrings buttons{
290 XO("Yes"), XO("No"),
291 }; // could add a button here for 'yes and remember that', and put it into the cfg file. Needs more thought.
292 int action = BasicUI::ShowMultiDialog(msg,
293 XO("Audacity Module Loader"),
294 buttons,
295 "",
296 XO("Try and load this module?"),
297 false);
298#ifdef EXPERIMENTAL_MODULE_PREFS
299 // If we're not prompting always, accept the answer permanently
300 if( iModuleStatus == kModuleNew ){
301 iModuleStatus = (action==1)?kModuleDisabled : kModuleEnabled;
302 ModuleSettings::SetModuleStatus( file, iModuleStatus );
303 }
304#endif
305 if(action == 1){ // "No"
306 decided.Add( ShortName );
307 continue;
308 }
309 }
310#ifdef EXPERIMENTAL_MODULE_PREFS
311 // Before attempting to load, we set the state to bad.
312 // That way, if we crash, we won't try again.
314#endif
315
316 wxString Error;
317 auto umodule = std::make_unique<Module>(file);
318 if (umodule->Load(Error)) // it will get rejected if there are version problems
319 {
320 decided.Add( ShortName );
321 auto module = umodule.get();
322
323 if (!module->HasDispatch())
324 {
325 auto ShortName = wxFileName(file).GetName();
327 XO("The module \"%s\" does not provide any of the required functions.\n\nIt will not be loaded.")
328 .Format(ShortName));
329 wxLogMessage(wxT("The module \"%s\" does not provide any of the required functions. It will not be loaded."), file);
330 module->Unload();
331 }
332 else
333 {
334 Get().mModules.push_back(std::move(umodule));
335
336#ifdef EXPERIMENTAL_MODULE_PREFS
337 // Loaded successfully, restore the status.
338 ModuleSettings::SetModuleStatus(file, iModuleStatus);
339#endif
340 }
341 }
342 else if (!Error.empty()) {
343 // Module is not yet decided in this pass.
344 // Maybe it depends on another which has not yet been loaded.
345 // But don't take the kModuleAsk path again in a later pass.
347 errors.emplace_back( std::move( umodule ), Error );
348 }
349 }
350}
351
352// static
354{
355 FilePaths files;
356 FindModules(files);
357
358 FilePaths decided;
359 DelayedErrors errors;
360 size_t numDecided = 0;
361
362 // Multiple passes give modules multiple chances to load in case they
363 // depend on some other module not yet loaded
364 do {
365 numDecided = decided.size();
366 errors.clear();
367 TryLoadModules(files, decided, errors);
368 }
369 while ( errors.size() && numDecided < decided.size() );
370
371 // Only now show accumulated errors of modules that failed to load
372 for ( const auto &pair : errors ) {
373 auto &pModule = pair.first;
374 pModule->ShowLoadFailureError(pair.second);
375 ModuleSettings::SetModuleStatus( pModule->GetName(), kModuleFailed );
376 }
377}
378
379// static
381{
382 for (const auto &module: mModules) {
383 module->Dispatch(type);
384 }
385 return 0;
386}
387
389{
390 if(mPtr)
391 {
392 mPtr->Terminate();
393 //No profit in comparison to calling/performing PluginProvider::Terminate
394 //from a destructor of the PluginProvider, since we don't offer any
395 //options to deal with errors...
396 //
397 //Example:
398 //try {
399 // provider->Terminate();
400 //}
401 //catch(e) {
402 // if(Dialog::ShowError("... Are you sure?") != Dialog::ResultOk)
403 // //other providers might have been terminated by that time,
404 // //so it might be a better option to repeatedly ask "Try again"/"Continue"
405 // return;
406 //}
407 //provider.reset();//no errors, or user confirmed deletion
408 }
409}
410
411// ============================================================================
412//
413// Return reference to singleton
414//
415// (Thread-safe...no active threading during construction or after destruction)
416// ============================================================================
418{
419 if (!mInstance)
420 mInstance = std::make_unique<ModuleManager>();
421
422 return *mInstance;
423}
424
426{
427 return L"Module";
428}
429
431{
432 return wxString::Format(wxT("%s_%s_%s_%s_%s"),
434 wxEmptyString,
435 provider->GetVendor().Internal(),
436 provider->GetSymbol().Internal(),
437 provider->GetPath());
438}
439
441{
443
444// The commented out code loads modules whether or not they are enabled.
445// none of our modules is a 'provider' of effects, so this code commented out.
446#if 0
447 FilePaths provList;
448 FilePaths pathList;
449
450 // Code from LoadLadspa that might be useful in load modules.
451 wxString pathVar = wxString::FromUTF8(getenv("AUDACITY_MODULES_PATH"));
452
453 if (!pathVar.empty())
454 {
455 FileNames::AddMultiPathsToPathList(pathVar, pathList);
456 }
457 else
458 {
460 }
461
462#if defined(__WXMSW__)
463 FileNames::FindFilesInPathList(wxT("*.dll"), pathList, provList);
464#elif defined(__WXMAC__)
465 FileNames::FindFilesInPathList(wxT("*.dylib"), pathList, provList);
466#else
467 FileNames::FindFilesInPathList(wxT("*.so"), pathList, provList);
468#endif
469
470 for ( const auto &path : provList )
471 LoadModule(path);
472#endif
473
474 return true;
475}
476
478{
479 for (const auto& pluginProviderFactory : builtinProviderList())
480 {
481 auto pluginProvider = pluginProviderFactory();
482
483 if (pluginProvider && pluginProvider->Initialize()) {
484 PluginProviderUniqueHandle handle { std::move(pluginProvider) };
485
486 auto id = GetID(handle.get());
487
488 // Need to remember it
489 mProviders[id] = std::move(handle);
490 }
491 }
492}
493
494bool ModuleManager::RegisterEffectPlugin(const PluginID & providerID, const PluginPath & path, TranslatableString &errMsg)
495{
496 errMsg = {};
497 if (mProviders.find(providerID) == mProviders.end())
498 {
499 return false;
500 }
501
502 auto nFound = mProviders[providerID]->DiscoverPluginsAtPath(path, errMsg, PluginManagerInterface::DefaultRegistrationCallback);
503
504 return nFound > 0;
505}
506
508 const PluginPath & path)
509{
510 if (path.empty() && mProviders.find(providerID) != mProviders.end())
511 {
512 return mProviders[providerID].get();
513 }
514
515 return nullptr;
516}
517
518std::unique_ptr<ComponentInterface> ModuleManager::LoadPlugin(
519 const PluginID & providerID, const PluginPath & path)
520{
521 if (auto iter = mProviders.find(providerID);
522 iter == mProviders.end())
523 return nullptr;
524 else
525 return iter->second->LoadPlugin(path);
526}
527
528bool ModuleManager::CheckPluginExist(const PluginID& providerId, const PluginPath& path)
529{
530 if(mProviders.find(providerId) == mProviders.end())
531 return false;
532
533 return mProviders[providerId]->CheckPluginExist(path);
534}
535
536bool ModuleManager::IsProviderValid(const PluginID & WXUNUSED(providerID),
537 const PluginPath & path)
538{
539 // Builtin modules do not have a path
540 if (path.empty())
541 {
542 return true;
543 }
544
545 wxFileName lib(path);
546 if (lib.FileExists() || lib.DirExists())
547 {
548 return true;
549 }
550
551 return false;
552}
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
const TranslatableString name
Definition: Distortion.cpp:82
wxString PluginID
Definition: EffectManager.h:30
wxString PluginPath
type alias for identifying a Plugin supplied by a module, each module defining its own interpretation...
Definition: Identifier.h:214
#define XO(s)
Definition: Internat.h:31
#define ModuleDispatchName
ModuleDispatchTypes
@ ModuleInitialize
@ ModuleTerminate
void RegisterProviderFactory(PluginProviderFactory pluginProviderFactory)
static BasicUI::MessageBoxResult DoMessageBox(const TranslatableString &msg)
void UnregisterProviderFactory(PluginProviderFactory pluginProviderFactory)
std::vector< PluginProviderFactory > BuiltinProviderList
#define versionFnName
wxChar *(* tVersionFn)()
std::unique_ptr< PluginProvider >(*)() PluginProviderFactory
int(* fnModuleDispatch)(ModuleDispatchTypes type)
Definition: ModuleManager.h:39
@ kModuleDisabled
@ kModuleAsk
@ kModuleFailed
@ kModuleNew
@ kModuleEnabled
Generalized interface for discovery of plug-ins for one protocol.
wxString FilePath
Definition: Project.h:20
std::vector< TranslatableString > TranslatableStrings
int id
virtual PluginPath GetPath() const =0
virtual VendorSymbol GetVendor() const =0
virtual ComponentInterfaceSymbol GetSymbol() const =0
const wxString & Internal() const
Abstract base class used in importing a file.
void ShowLoadFailureError(const wxString &Error)
fnModuleDispatch mDispatch
Definition: ModuleManager.h:58
std::unique_ptr< wxDynamicLibrary > mLib
Definition: ModuleManager.h:57
Module(const FilePath &name)
void * GetSymbol(const wxString &name)
virtual ~Module()
bool Load(wxString &deferredErrorMessage)
int Dispatch(ModuleDispatchTypes type)
const FilePath mName
Definition: ModuleManager.h:56
void Unload()
std::vector< std::pair< std::unique_ptr< Module >, wxString > > DelayedErrors
static void FindModules(FilePaths &files)
static std::unique_ptr< ModuleManager > mInstance
PluginProviderHandlesMap mProviders
static ModuleManager & Get()
static PluginID GetID(PluginProvider *provider)
static wxString GetPluginTypeString()
void InitializeBuiltins()
std::unique_ptr< ComponentInterface > LoadPlugin(const PluginID &provider, const PluginPath &path)
int Dispatch(ModuleDispatchTypes type)
PluginProvider * CreateProviderInstance(const PluginID &provider, const PluginPath &path)
bool IsProviderValid(const PluginID &provider, const PluginPath &path)
bool RegisterEffectPlugin(const PluginID &provider, const PluginPath &path, TranslatableString &errMsg)
bool CheckPluginExist(const PluginID &providerId, const PluginPath &path)
bool DiscoverProviders()
std::vector< std::unique_ptr< Module > > mModules
static void TryLoadModules(const FilePaths &files, FilePaths &decided, DelayedErrors &errors)
static const PluginID & DefaultRegistrationCallback(PluginProvider *provider, ComponentInterface *ident)
std::unique_ptr< PluginProvider > mPtr
Definition: ModuleManager.h:63
Holds a msgid for the translation catalog; may also bind format arguments.
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
int ShowMultiDialog(const TranslatableString &message, const TranslatableString &title, const TranslatableStrings &buttons, const ManualPageID &helpPage, const TranslatableString &boxMsg, bool log)
Display a dialog with radio buttons.
Definition: BasicUI.h:322
MessageBoxResult
Definition: BasicUI.h:129
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:269
FILES_API void AddUniquePathToPathList(const FilePath &path, FilePaths &pathList)
FILES_API void FindFilesInPathList(const wxString &pattern, const FilePaths &pathList, FilePaths &results, int flags=wxDIR_FILES)
FILES_API void AddMultiPathsToPathList(const wxString &multiPathString, FilePaths &pathList)
FILES_API FilePath ModulesDir()
FILES_API const FilePaths & AudacityPathList()
A list of directories that should be searched for Audacity files (plug-ins, help files,...
MODULE_MANAGER_API void SetModuleStatus(const FilePath &fname, int iStatus)
MODULE_MANAGER_API int GetModuleStatus(const FilePath &fname)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
BuiltinProviderList & builtinProviderList()
MessageBoxOptions && Caption(TranslatableString caption_) &&
Definition: BasicUI.h:98