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