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