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