Audacity  2.2.2
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 <wx/dynarray.h>
22 #include <wx/dynlib.h>
23 #include <wx/list.h>
24 #include <wx/log.h>
25 #include <wx/string.h>
26 #include <wx/filename.h>
27 
28 #include "Audacity.h"
29 #include "AudacityApp.h"
30 #include "FileNames.h"
31 #include "Internat.h"
32 #include "PluginManager.h"
33 
35 #include <NonGuiThread.h> // header from libwidgetextra
36 
37 #include "audacity/PluginInterface.h"
38 
39 #ifdef EXPERIMENTAL_MODULE_PREFS
40 #include "Prefs.h"
41 #include "./prefs/ModulePrefs.h"
42 #endif
43 
44 #include "ModuleManager.h"
45 #include "widgets/MultiDialog.h"
46 
47 #include <wx/arrimpl.cpp>
48 
49 #include "Experimental.h"
50 #include "widgets/ErrorDialog.h"
51 
52 #define initFnName "ExtensionModuleInit"
53 #define versionFnName "GetVersionString"
54 #define scriptFnName "RegScriptServerFunc"
55 #define mainPanelFnName "MainPanelFunc"
56 
57 typedef wxWindow * pwxWindow;
58 typedef int (*tModuleInit)(int);
59 //typedef wxString (*tVersionFn)();
60 typedef wxChar * (*tVersionFn)();
61 typedef pwxWindow (*tPanelFn)(int);
62 
63 // This variable will hold the address of a subroutine in
64 // a DLL that can hijack the normal panel.
65 static tPanelFn pPanelHijack=NULL;
66 
67 // Next two commented out lines are handy when investigating
68 // strange DLL behaviour. Instead of dynamic linking,
69 // link the library which has the replacement panel statically.
70 // Give the address of the routine here.
71 // This is a great help in identifying missing
72 // symbols which otherwise cause a dll to unload after loading
73 // without an explanation as to why!
74 //extern wxWindow * MainPanelFunc( int i );
75 //tPanelFn pPanelHijack=&MainPanelFunc;
76 
80 wxWindow * MakeHijackPanel()
81 {
82  if( pPanelHijack == NULL )
83  return NULL;
84  return pPanelHijack(0);
85 }
86 
87 // This variable will hold the address of a subroutine in a DLL that
88 // starts a thread and reads script commands.
90 
91 Module::Module(const wxString & name)
92 {
93  mName = name;
94  mLib = std::make_unique<wxDynamicLibrary>();
95  mDispatch = NULL;
96 }
97 
99 {
100 }
101 
103 {
104  if (mLib->IsLoaded()) {
105  if (mDispatch) {
106  return true;
107  }
108  return false;
109  }
110 
111  if (!mLib->Load(mName, wxDL_LAZY)) {
112  return false;
113  }
114 
115  // Check version string matches. (For now, they must match exactly)
116  tVersionFn versionFn = (tVersionFn)(mLib->GetSymbol(wxT(versionFnName)));
117  if (versionFn == NULL){
118  wxString ShortName = wxFileName( mName ).GetName();
119  AudacityMessageBox(wxString::Format(_("The module %s does not provide a version string.\nIt will not be loaded."), ShortName), _("Module Unsuitable"));
120  wxLogMessage(wxString::Format(_("The module %s does not provide a version string. It will not be loaded."), mName));
121  mLib->Unload();
122  return false;
123  }
124 
125  wxString moduleVersion = versionFn();
126  if( !moduleVersion.IsSameAs(AUDACITY_VERSION_STRING)) {
127  wxString ShortName = wxFileName( mName ).GetName();
128  AudacityMessageBox(wxString::Format(_("The module %s is matched with Audacity version %s.\n\nIt will not be loaded."), ShortName, moduleVersion), _("Module Unsuitable"));
129  wxLogMessage(wxString::Format(_("The module %s is matched with Audacity version %s. It will not be loaded."), mName, moduleVersion));
130  mLib->Unload();
131  return false;
132  }
133 
134  mDispatch = (fnModuleDispatch) mLib->GetSymbol(wxT(ModuleDispatchName));
135  if (!mDispatch) {
136  // Module does not provide a dispatch function...
137  // That can be OK, as long as we never try to call it.
138  return true;
139  }
140 
141  // However if we do have it and it does not work,
142  // then the module is bad.
143  bool res = ((mDispatch(ModuleInitialize))!=0);
144  if (res) {
145  return true;
146  }
147 
148  mDispatch = NULL;
149  return false;
150 }
151 
153 {
154  if (mLib->IsLoaded()) {
156  }
157 
158  mLib->Unload();
159 }
160 
162 {
163  if (mLib->IsLoaded())
164  if( mDispatch != NULL )
165  return mDispatch(type);
166 
167  return 0;
168 }
169 
170 void * Module::GetSymbol(const wxString &name)
171 {
172  return mLib->GetSymbol(name);
173 }
174 
175 // ============================================================================
176 //
177 // ModuleManager
178 //
179 // ============================================================================
180 
181 // The one and only ModuleManager
182 std::unique_ptr<ModuleManager> ModuleManager::mInstance{};
183 
184 // Provide builtin modules a means to identify themselves
185 using BuiltinModuleList = std::vector<ModuleMain>;
186 namespace {
187  BuiltinModuleList &builtinModuleList()
188  {
189  static BuiltinModuleList theList;
190  return theList;
191  }
192 }
193 
194 void RegisterBuiltinModule(ModuleMain moduleMain)
195 {
196  builtinModuleList().push_back(moduleMain);
197 
198  return;
199 }
200 
201 // ----------------------------------------------------------------------------
202 // Creation/Destruction
203 // ----------------------------------------------------------------------------
204 
206 {
207 }
208 
210 {
211  mDynModules.clear();
212  builtinModuleList().clear();
213 }
214 
215 // static
217 {
218  wxArrayString audacityPathList = wxGetApp().audacityPathList;
219  wxArrayString pathList;
220  wxArrayString files;
221  wxString pathVar;
222  size_t i;
223 
224  // Code from LoadLadspa that might be useful in load modules.
225  pathVar = wxGetenv(wxT("AUDACITY_MODULES_PATH"));
226  if (pathVar != wxT(""))
227  wxGetApp().AddMultiPathsToPathList(pathVar, pathList);
228 
229  for (i = 0; i < audacityPathList.GetCount(); i++) {
230  wxString prefix = audacityPathList[i] + wxFILE_SEP_PATH;
231  wxGetApp().AddUniquePathToPathList(prefix + wxT("modules"),
232  pathList);
233  }
234 
235  #if defined(__WXMSW__)
236  wxGetApp().FindFilesInPathList(wxT("*.dll"), pathList, files);
237  #else
238  wxGetApp().FindFilesInPathList(wxT("*.so"), pathList, files);
239  #endif
240 
241  wxString saveOldCWD = ::wxGetCwd();
242  for (i = 0; i < files.GetCount(); i++) {
243  // As a courtesy to some modules that might be bridges to
244  // open other modules, we set the current working
245  // directory to be the module's directory.
246  wxString prefix = ::wxPathOnly(files[i]);
247  ::wxSetWorkingDirectory(prefix);
248 
249 #ifdef EXPERIMENTAL_MODULE_PREFS
250  int iModuleStatus = ModulePrefs::GetModuleStatus( files[i] );
251  if( iModuleStatus == kModuleDisabled )
252  continue;
253  if( iModuleStatus == kModuleFailed )
254  continue;
255  // New module? You have to go and explicitly enable it.
256  if( iModuleStatus == kModuleNew ){
257  // To ensure it is noted in config file and so
258  // appears on modules page.
260  continue;
261  }
262 
263  if( iModuleStatus == kModuleAsk )
264 #endif
265  // JKC: I don't like prompting for the plug-ins individually
266  // I think it would be better to show the module prefs page,
267  // and let the user decide for each one.
268  {
269  wxString ShortName = wxFileName( files[i] ).GetName();
270  wxString msg;
271  msg.Printf(_("Module \"%s\" found."), ShortName);
272  msg += _("\n\nOnly use modules from trusted sources");
273  const wxChar *buttons[] = {_("Yes"), _("No"), NULL}; // could add a button here for 'yes and remember that', and put it into the cfg file. Needs more thought.
274  int action;
275  action = ShowMultiDialog(msg, _("Audacity Module Loader"), buttons, _("Try and load this module?"), false);
276 #ifdef EXPERIMENTAL_MODULE_PREFS
277  // If we're not prompting always, accept the answer permanantly
278  if( iModuleStatus == kModuleNew ){
279  iModuleStatus = (action==1)?kModuleDisabled : kModuleEnabled;
280  ModulePrefs::SetModuleStatus( files[i], iModuleStatus );
281  }
282 #endif
283  if(action == 1){ // "No"
284  continue;
285  }
286  }
287 #ifdef EXPERIMENTAL_MODULE_PREFS
288  // Before attempting to load, we set the state to bad.
289  // That way, if we crash, we won't try again.
291 #endif
292 
293  auto umodule = make_movable<Module>(files[i]);
294  if (umodule->Load()) // it will get rejected if there are version problems
295  {
296  auto module = umodule.get();
297  Get().mModules.push_back(std::move(umodule));
298  // We've loaded and initialised OK.
299  // So look for special case functions:
300  wxLogNull logNo; // Don't show wxWidgets errors if we can't do these. (Was: Fix bug 544.)
301  // (a) for scripting.
302  if( scriptFn == NULL )
303  scriptFn = (tpRegScriptServerFunc)(module->GetSymbol(wxT(scriptFnName)));
304  // (b) for hijacking the entire Audacity panel.
305  if( pPanelHijack==NULL )
306  {
307  pPanelHijack = (tPanelFn)(module->GetSymbol(wxT(mainPanelFnName)));
308  }
309 #ifdef EXPERIMENTAL_MODULE_PREFS
310  // Loaded successfully, restore the status.
311  ModulePrefs::SetModuleStatus( files[i], iModuleStatus);
312 #endif
313  }
314  }
315  ::wxSetWorkingDirectory(saveOldCWD);
316 
317  // After loading all the modules, we may have a registered scripting function.
318  if(scriptFn)
319  {
322  NonGuiThread::StartChild(&ScriptCommandRelay::Run);
323  }
324 }
325 
326 // static
328 {
329  for (const auto &module: mModules) {
330  module->Dispatch(type);
331  }
332  return 0;
333 }
334 
335 // ============================================================================
336 //
337 // Return reference to singleton
338 //
339 // (Thread-safe...no active threading during construction or after destruction)
340 // ============================================================================
342 {
343  if (!mInstance)
344  {
346  }
347 
348  return *mInstance;
349 }
350 
352 {
354 
355  wxArrayString provList;
356  wxArrayString pathList;
357 
358  // Code from LoadLadspa that might be useful in load modules.
359  wxString pathVar = wxString::FromUTF8(getenv("AUDACITY_MODULES_PATH"));
360 
361  if (pathVar != wxT(""))
362  {
363  wxGetApp().AddMultiPathsToPathList(pathVar, pathList);
364  }
365  else
366  {
368  }
369 
370 #if defined(__WXMSW__)
371  wxGetApp().FindFilesInPathList(wxT("*.dll"), pathList, provList);
372 #elif defined(__WXMAC__)
373  wxGetApp().FindFilesInPathList(wxT("*.dylib"), pathList, provList);
374 #else
375  wxGetApp().FindFilesInPathList(wxT("*.so"), pathList, provList);
376 #endif
377 
379 
380  for (int i = 0, cnt = provList.GetCount(); i < cnt; i++)
381  {
382  ModuleInterface *module = LoadModule(provList[i]);
383  if (module)
384  {
385  // Register the provider
386  pm.RegisterPlugin(module);
387 
388  // Now, allow the module to auto-register children
389  module->AutoRegisterPlugins(pm);
390  }
391  }
392 
393  return true;
394 }
395 
397 {
399 
400  for (auto moduleMain : builtinModuleList())
401  {
402  ModuleInterfaceHandle module {
403  moduleMain(this, NULL), ModuleInterfaceDeleter{}
404  };
405 
406  if (module->Initialize())
407  {
408  // Register the provider
409  ModuleInterface *pInterface = module.get();
410  const PluginID & id = pm.RegisterPlugin(pInterface);
411 
412  // Need to remember it
413  mDynModules[id] = std::move(module);
414 
415  // Allow the module to auto-register children
416  pInterface->AutoRegisterPlugins(pm);
417  }
418  else
419  {
420  // Don't leak! Destructor of module does that.
421  }
422  }
423 }
424 
425 ModuleInterface *ModuleManager::LoadModule(const wxString & path)
426 {
427  auto lib = make_movable<wxDynamicLibrary>();
428 
429  if (lib->Load(path, wxDL_NOW))
430  {
431  bool success = false;
432  ModuleMain audacityMain = (ModuleMain) lib->GetSymbol(wxSTRINGIZE_T(MODULE_ENTRY),
433  &success);
434  if (success && audacityMain)
435  {
436  ModuleInterfaceHandle handle {
437  audacityMain(this, &path), ModuleInterfaceDeleter{}
438  };
439  if (handle)
440  {
441  if (handle->Initialize())
442  {
443 
444  auto module = handle.get();
445  mDynModules[PluginManager::GetID(module)] = std::move(handle);
446  mLibs[module] = std::move(lib);
447 
448  return module;
449  }
450  }
451  }
452 
453  lib->Unload();
454  }
455 
456  return NULL;
457 }
458 
459 void ModuleInterfaceDeleter::operator() (ModuleInterface *pInterface) const
460 {
461  if (pInterface)
462  {
463  pInterface->Terminate();
464 
465  auto &libs = ModuleManager::Get().mLibs;
466 
467  auto iter = libs.find(pInterface);
468  if (iter != libs.end())
469  libs.erase(iter); // This causes unloading in ~wxDynamicLibrary
470 
471  std::unique_ptr < ModuleInterface > { pInterface }; // DELETE it
472  }
473 }
474 
475 void ModuleManager::RegisterModule(ModuleInterface *inModule)
476 {
477  std::unique_ptr<ModuleInterface> module{ inModule };
478 
479  PluginID id = PluginManager::GetID(module.get());
480 
481  if (mDynModules.find(id) != mDynModules.end())
482  {
483  // TODO: Should we complain about a duplicate registeration????
484  // PRL: Don't leak resources!
485  module->Terminate();
486  return;
487  }
488 
490  module.release(), ModuleInterfaceDeleter{}
491  };
492 
494 }
495 
496 void ModuleManager::FindAllPlugins(PluginIDList & providers, wxArrayString & paths)
497 {
499 
500  wxArrayString modIDs;
501  wxArrayString modPaths;
503  while (plug)
504  {
505  modIDs.push_back(plug->GetID());
506  modPaths.push_back(plug->GetPath());
507  plug = pm.GetNextPlugin(PluginTypeModule);
508  }
509 
510  for (size_t i = 0, cnt = modIDs.size(); i < cnt; i++)
511  {
512  PluginID providerID = modIDs[i];
513 
514  ModuleInterface *module =
515  static_cast<ModuleInterface *>(CreateProviderInstance(providerID, modPaths[i]));
516 
517  if (!module)
518  continue;
519 
520  wxArrayString newpaths = module->FindPluginPaths(pm);
521  for (size_t i = 0, cnt = newpaths.size(); i < cnt; i++)
522  {
523  providers.push_back(providerID);
524  paths.push_back(newpaths[i]);
525  }
526  }
527 }
528 
529 wxArrayString ModuleManager::FindPluginsForProvider(const PluginID & providerID,
530  const wxString & path)
531 {
532  // Instantiate if it hasn't already been done
533  if (mDynModules.find(providerID) == mDynModules.end())
534  {
535  // If it couldn't be created, just give up and return an empty list
536  if (!CreateProviderInstance(providerID, path))
537  {
538  return wxArrayString();
539  }
540  }
541 
542  return mDynModules[providerID]->FindPluginPaths(PluginManager::Get());
543 }
544 
545 bool ModuleManager::RegisterPlugin(const PluginID & providerID, const wxString & path, wxString &errMsg)
546 {
547  errMsg.clear();
548  if (mDynModules.find(providerID) == mDynModules.end())
549  {
550  return false;
551  }
552 
553  auto nFound = mDynModules[providerID]->DiscoverPluginsAtPath(path, errMsg);
554 
555  return nFound > 0;
556 }
557 
558 IdentInterface *ModuleManager::CreateProviderInstance(const PluginID & providerID,
559  const wxString & path)
560 {
561  if (path.IsEmpty() && mDynModules.find(providerID) != mDynModules.end())
562  {
563  return mDynModules[providerID].get();
564  }
565 
566  return LoadModule(path);
567 }
568 
569 IdentInterface *ModuleManager::CreateInstance(const PluginID & providerID,
570  const wxString & path)
571 {
572  if (mDynModules.find(providerID) == mDynModules.end())
573  {
574  return NULL;
575  }
576 
577  return mDynModules[providerID]->CreateInstance(path);
578 }
579 
580 void ModuleManager::DeleteInstance(const PluginID & providerID,
581  IdentInterface *instance)
582 {
583  if (mDynModules.find(providerID) == mDynModules.end())
584  {
585  return;
586  }
587 
588  mDynModules[providerID]->DeleteInstance(instance);
589 }
590 
591 bool ModuleManager::IsProviderValid(const PluginID & WXUNUSED(providerID),
592  const wxString & path)
593 {
594  // Builtin modules do not have a path
595  if (path.IsEmpty())
596  {
597  return true;
598  }
599 
600  wxFileName lib(path);
601  if (lib.FileExists() || lib.DirExists())
602  {
603  return true;
604  }
605 
606  return false;
607 }
608 
609 bool ModuleManager::IsPluginValid(const PluginID & providerID,
610  const wxString & path,
611  bool bFast)
612 {
613  if (mDynModules.find(providerID) == mDynModules.end())
614  {
615  return false;
616  }
617 
618  return mDynModules[providerID]->IsPluginValid(path, bFast);
619 }
620 
void * GetSymbol(const wxString &name)
#define AUDACITY_VERSION_STRING
Definition: Audacity.h:81
wxArrayString FindPluginsForProvider(const PluginID &provider, const wxString &path)
pwxWindow(* tPanelFn)(int)
static PluginID GetID(ModuleInterface *module)
int(* fnModuleDispatch)(ModuleDispatchTypes type)
Definition: ModuleManager.h:46
static ModuleManager & Get()
static tpRegScriptServerFunc scriptFn
bool DiscoverProviders()
std::vector< movable_ptr< Module > > mModules
static void SetRegScriptServerFunc(tpRegScriptServerFunc scriptFn)
IdentInterface * CreateInstance(const PluginID &provider, const wxString &path)
static std::unique_ptr< ModuleManager > mInstance
void FindAllPlugins(PluginIDList &providers, wxArrayString &paths)
IdentInterface * CreateProviderInstance(const PluginID &provider, const wxString &path)
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
Contains declarations for ScriptCommandRelay.
int Dispatch(ModuleDispatchTypes type)
static tPanelFn pPanelHijack
static void AddMultiPathsToPathList(const wxString &multiPathString, wxArrayString &pathList)
bool IsProviderValid(const PluginID &provider, const wxString &path)
void operator()(ModuleInterface *pInterface) const
fnModuleDispatch mDispatch
Definition: ModuleManager.h:62
static void FindFilesInPathList(const wxString &pattern, const wxArrayString &pathList, wxArrayString &results, int flags=wxDIR_FILES)
wxArrayString PluginIDList
#define versionFnName
bool IsPluginValid(const PluginID &provider, const wxString &path, bool bFast)
wxString mName
Definition: ModuleManager.h:60
#define safenew
Definition: Audacity.h:223
int Dispatch(ModuleDispatchTypes type)
const wxString & GetPath() const
static void Run()
Calls the script function, passing it the function for obeying commands.
void DeleteInstance(const PluginID &provider, IdentInterface *instance)
static void SetModuleStatus(const wxString &fname, int iStatus)
ModuleInterface * LoadModule(const wxString &path)
wxChar *(* tVersionFn)()
const PluginDescriptor * GetNextPlugin(PluginType type)
void InitializeBuiltins()
const PluginDescriptor * GetFirstPlugin(PluginType type)
virtual ~Module()
Module(const wxString &name)
Contains methods for applying commands that are passed to it.
ModuleDispatchTypes
Definition: ModuleManager.h:35
static int GetModuleStatus(const wxString &fname)
ModuleMap mDynModules
bool Load()
int ShowMultiDialog(const wxString &message, const wxString &title, const wxChar **buttons, const wxString &boxMsg, bool log)
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
static void AddUniquePathToPathList(const wxString &path, wxArrayString &pathList)
#define mainPanelFnName
const wxChar * name
Definition: Distortion.cpp:94
const wxString & GetID() const
const PluginID & RegisterPlugin(ModuleInterface *module) override
int(* tpRegScriptServerFunc)(tpExecScriptServerFunc pFn)
void RegisterModule(ModuleInterface *module) override
void Initialize(CommandHandler &cmdHandler)
wxArrayString audacityPathList
A list of directories that should be searched for Audacity files (plug-ins, help files, etc.).
Definition: AudacityApp.h:138
static wxString ModulesDir()
Definition: FileNames.cpp:247
void Unload()
int(* tModuleInit)(int)
wxWindow * MakeHijackPanel()
bool RegisterPlugin(const PluginID &provider, const wxString &path, wxString &errMsg)
AudacityApp & wxGetApp()
static PluginManager & Get()
LibraryMap mLibs
void RegisterBuiltinModule(ModuleMain moduleMain)
#define scriptFnName
static void SetCommandHandler(CommandHandler &ch)
std::vector< ModuleMain > BuiltinModuleList
#define ModuleDispatchName
Definition: ModuleManager.h:33
wxWindow * pwxWindow
std::unique_ptr< wxDynamicLibrary > mLib
Definition: ModuleManager.h:61