3bf23bb16567157c2f8a5a1a70aad74db8a0aa92
[ardour.git] / libs / vamp-sdk / src / vamp-hostsdk / PluginLoader.cpp
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
3 /*
4     Vamp
5
6     An API for audio analysis and feature extraction plugins.
7
8     Centre for Digital Music, Queen Mary, University of London.
9     Copyright 2006-2007 Chris Cannam and QMUL.
10   
11     Permission is hereby granted, free of charge, to any person
12     obtaining a copy of this software and associated documentation
13     files (the "Software"), to deal in the Software without
14     restriction, including without limitation the rights to use, copy,
15     modify, merge, publish, distribute, sublicense, and/or sell copies
16     of the Software, and to permit persons to whom the Software is
17     furnished to do so, subject to the following conditions:
18
19     The above copyright notice and this permission notice shall be
20     included in all copies or substantial portions of the Software.
21
22     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
26     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30     Except as contained in this notice, the names of the Centre for
31     Digital Music; Queen Mary, University of London; and Chris Cannam
32     shall not be used in advertising or otherwise to promote the sale,
33     use or other dealings in this Software without prior written
34     authorization.
35 */
36
37 #include <vamp-hostsdk/PluginHostAdapter.h>
38 #include <vamp-hostsdk/PluginLoader.h>
39 #include <vamp-hostsdk/PluginInputDomainAdapter.h>
40 #include <vamp-hostsdk/PluginChannelAdapter.h>
41 #include <vamp-hostsdk/PluginBufferingAdapter.h>
42
43 #include <fstream>
44 #include <cctype> // tolower
45
46 #include <cstring>
47
48 #ifdef _WIN32
49
50 #include <windows.h>
51 #include <tchar.h>
52 #define PLUGIN_SUFFIX "dll"
53
54 #else /* ! _WIN32 */
55
56 #include <dirent.h>
57 #include <dlfcn.h>
58
59 #ifdef __APPLE__
60 #define PLUGIN_SUFFIX "dylib"
61 #else /* ! __APPLE__ */
62 #define PLUGIN_SUFFIX "so"
63 #endif /* ! __APPLE__ */
64
65 #endif /* ! _WIN32 */
66
67 using namespace std;
68
69 _VAMP_SDK_HOSTSPACE_BEGIN(PluginLoader.cpp)
70
71 namespace Vamp {
72         
73 namespace HostExt {
74
75 class PluginLoader::Impl
76 {
77 public:
78     Impl();
79     virtual ~Impl();
80
81     PluginKeyList listPlugins();
82
83     Plugin *loadPlugin(PluginKey key,
84                        float inputSampleRate,
85                        int adapterFlags);
86
87     PluginKey composePluginKey(string libraryName, string identifier);
88
89     PluginCategoryHierarchy getPluginCategory(PluginKey key);
90
91     string getLibraryPathForPlugin(PluginKey key);
92
93     static void setInstanceToClean(PluginLoader *instance);
94
95 protected:
96     class PluginDeletionNotifyAdapter : public PluginWrapper {
97     public:
98         PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader);
99         virtual ~PluginDeletionNotifyAdapter();
100     protected:
101         Impl *m_loader;
102     };
103
104     class InstanceCleaner {
105     public:
106         InstanceCleaner() : m_instance(0) { }
107         ~InstanceCleaner() { delete m_instance; }
108         void setInstance(PluginLoader *instance) { m_instance = instance; }
109     protected:
110         PluginLoader *m_instance;
111     };
112
113     virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
114
115     map<PluginKey, string> m_pluginLibraryNameMap;
116     bool m_allPluginsEnumerated;
117     void enumeratePlugins(PluginKey forPlugin = "");
118
119     map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
120     void generateTaxonomy();
121
122     map<Plugin *, void *> m_pluginLibraryHandleMap;
123
124     bool decomposePluginKey(PluginKey key,
125                             string &libraryName, string &identifier);
126
127     void *loadLibrary(string path);
128     void unloadLibrary(void *handle);
129     void *lookupInLibrary(void *handle, const char *symbol);
130
131     string splicePath(string a, string b);
132     vector<string> listFiles(string dir, string ext);
133     
134     static InstanceCleaner m_cleaner;
135 };
136
137 PluginLoader *
138 PluginLoader::m_instance = 0;
139
140 PluginLoader::Impl::InstanceCleaner
141 PluginLoader::Impl::m_cleaner;
142
143 PluginLoader::PluginLoader()
144 {
145     m_impl = new Impl();
146 }
147
148 PluginLoader::~PluginLoader()
149 {
150     delete m_impl;
151 }
152
153 PluginLoader *
154 PluginLoader::getInstance()
155 {
156     if (!m_instance) {
157         // The cleaner doesn't own the instance, because we leave the
158         // instance pointer in the base class for binary backwards
159         // compatibility reasons and to avoid waste
160         m_instance = new PluginLoader();
161         Impl::setInstanceToClean(m_instance);
162     }
163     return m_instance;
164 }
165
166 vector<PluginLoader::PluginKey>
167 PluginLoader::listPlugins() 
168 {
169     return m_impl->listPlugins();
170 }
171
172 Plugin *
173 PluginLoader::loadPlugin(PluginKey key,
174                          float inputSampleRate,
175                          int adapterFlags)
176 {
177     return m_impl->loadPlugin(key, inputSampleRate, adapterFlags);
178 }
179
180 PluginLoader::PluginKey
181 PluginLoader::composePluginKey(string libraryName, string identifier) 
182 {
183     return m_impl->composePluginKey(libraryName, identifier);
184 }
185
186 PluginLoader::PluginCategoryHierarchy
187 PluginLoader::getPluginCategory(PluginKey key)
188 {
189     return m_impl->getPluginCategory(key);
190 }
191
192 string
193 PluginLoader::getLibraryPathForPlugin(PluginKey key)
194 {
195     return m_impl->getLibraryPathForPlugin(key);
196 }
197  
198 PluginLoader::Impl::Impl() :
199     m_allPluginsEnumerated(false)
200 {
201 }
202
203 PluginLoader::Impl::~Impl()
204 {
205 }
206
207 void
208 PluginLoader::Impl::setInstanceToClean(PluginLoader *instance)
209 {
210     m_cleaner.setInstance(instance);
211 }
212
213 vector<PluginLoader::PluginKey>
214 PluginLoader::Impl::listPlugins() 
215 {
216     if (!m_allPluginsEnumerated) enumeratePlugins();
217
218     vector<PluginKey> plugins;
219     for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin();
220          mi != m_pluginLibraryNameMap.end(); ++mi) {
221         plugins.push_back(mi->first);
222     }
223
224     return plugins;
225 }
226
227 void
228 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin)
229 {
230     vector<string> path = PluginHostAdapter::getPluginPath();
231
232     string libraryName, identifier;
233     if (forPlugin != "") {
234         if (!decomposePluginKey(forPlugin, libraryName, identifier)) {
235             std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \""
236                       << forPlugin << "\" in enumerate" << std::endl;
237             return;
238         }
239     }
240
241     for (size_t i = 0; i < path.size(); ++i) {
242         
243         vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
244
245         for (vector<string>::iterator fi = files.begin();
246              fi != files.end(); ++fi) {
247             
248             if (libraryName != "") {
249                 // libraryName is lowercased and lacking an extension,
250                 // as it came from the plugin key
251                 string temp = *fi;
252                 for (size_t i = 0; i < temp.length(); ++i) {
253                     temp[i] = tolower(temp[i]);
254                 }
255                 string::size_type pi = temp.find('.');
256                 if (pi == string::npos) {
257                     if (libraryName != temp) continue;
258                 } else {
259                     if (libraryName != temp.substr(0, pi)) continue;
260                 }
261             }
262
263             string fullPath = path[i];
264             fullPath = splicePath(fullPath, *fi);
265             void *handle = loadLibrary(fullPath);
266             if (!handle) continue;
267             
268             VampGetPluginDescriptorFunction fn =
269                 (VampGetPluginDescriptorFunction)lookupInLibrary
270                 (handle, "vampGetPluginDescriptor");
271             
272             if (!fn) {
273                 unloadLibrary(handle);
274                 continue;
275             }
276             
277             int index = 0;
278             const VampPluginDescriptor *descriptor = 0;
279             
280             while ((descriptor = fn(VAMP_API_VERSION, index))) {
281                 ++index;
282                 if (identifier != "") {
283                     if (descriptor->identifier != identifier) continue;
284                 }
285                 PluginKey key = composePluginKey(*fi, descriptor->identifier);
286 //                std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl;
287                 if (m_pluginLibraryNameMap.find(key) ==
288                     m_pluginLibraryNameMap.end()) {
289                     m_pluginLibraryNameMap[key] = fullPath;
290                 }
291             }
292             
293             unloadLibrary(handle);
294         }
295     }
296
297     if (forPlugin == "") m_allPluginsEnumerated = true;
298 }
299
300 PluginLoader::PluginKey
301 PluginLoader::Impl::composePluginKey(string libraryName, string identifier)
302 {
303     string basename = libraryName;
304
305     string::size_type li = basename.rfind('/');
306     if (li != string::npos) basename = basename.substr(li + 1);
307
308     li = basename.find('.');
309     if (li != string::npos) basename = basename.substr(0, li);
310
311     for (size_t i = 0; i < basename.length(); ++i) {
312         basename[i] = tolower(basename[i]);
313     }
314
315     return basename + ":" + identifier;
316 }
317
318 bool
319 PluginLoader::Impl::decomposePluginKey(PluginKey key,
320                                        string &libraryName,
321                                        string &identifier)
322 {
323     string::size_type ki = key.find(':');
324     if (ki == string::npos) {
325         return false;
326     }
327
328     libraryName = key.substr(0, ki);
329     identifier = key.substr(ki + 1);
330     return true;
331 }
332
333 PluginLoader::PluginCategoryHierarchy
334 PluginLoader::Impl::getPluginCategory(PluginKey plugin)
335 {
336     if (m_taxonomy.empty()) generateTaxonomy();
337     if (m_taxonomy.find(plugin) == m_taxonomy.end()) {
338         return PluginCategoryHierarchy();
339     }
340     return m_taxonomy[plugin];
341 }
342
343 string
344 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin)
345 {
346     if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
347         if (m_allPluginsEnumerated) return "";
348         enumeratePlugins(plugin);
349     }
350     if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
351         return "";
352     }
353     return m_pluginLibraryNameMap[plugin];
354 }    
355
356 Plugin *
357 PluginLoader::Impl::loadPlugin(PluginKey key,
358                                float inputSampleRate, int adapterFlags)
359 {
360     string libname, identifier;
361     if (!decomposePluginKey(key, libname, identifier)) {
362         std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \""
363                   << key << "\" in loadPlugin" << std::endl;
364         return 0;
365     }
366         
367     string fullPath = getLibraryPathForPlugin(key);
368     if (fullPath == "") return 0;
369     
370     void *handle = loadLibrary(fullPath);
371     if (!handle) return 0;
372     
373     VampGetPluginDescriptorFunction fn =
374         (VampGetPluginDescriptorFunction)lookupInLibrary
375         (handle, "vampGetPluginDescriptor");
376
377     if (!fn) {
378         unloadLibrary(handle);
379         return 0;
380     }
381
382     int index = 0;
383     const VampPluginDescriptor *descriptor = 0;
384
385     while ((descriptor = fn(VAMP_API_VERSION, index))) {
386
387         if (string(descriptor->identifier) == identifier) {
388
389             Vamp::PluginHostAdapter *plugin =
390                 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
391
392             Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
393
394             m_pluginLibraryHandleMap[adapter] = handle;
395
396             if (adapterFlags & ADAPT_INPUT_DOMAIN) {
397                 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
398                     adapter = new PluginInputDomainAdapter(adapter);
399                 }
400             }
401
402             if (adapterFlags & ADAPT_BUFFER_SIZE) {
403                 adapter = new PluginBufferingAdapter(adapter);
404             }
405
406             if (adapterFlags & ADAPT_CHANNEL_COUNT) {
407                 adapter = new PluginChannelAdapter(adapter);
408             }
409
410             return adapter;
411         }
412
413         ++index;
414     }
415
416     cerr << "Vamp::HostExt::PluginLoader: Plugin \""
417          << identifier << "\" not found in library \""
418          << fullPath << "\"" << endl;
419
420     return 0;
421 }
422
423 void
424 PluginLoader::Impl::generateTaxonomy()
425 {
426 //    cerr << "PluginLoader::Impl::generateTaxonomy" << endl;
427
428     vector<string> path = PluginHostAdapter::getPluginPath();
429     string libfragment = "/lib/";
430     vector<string> catpath;
431
432     string suffix = "cat";
433
434     for (vector<string>::iterator i = path.begin();
435          i != path.end(); ++i) {
436
437         // It doesn't matter that we're using literal forward-slash in
438         // this bit, as it's only relevant if the path contains
439         // "/lib/", which is only meaningful and only plausible on
440         // systems with forward-slash delimiters
441         
442         string dir = *i;
443         string::size_type li = dir.find(libfragment);
444
445         if (li != string::npos) {
446             catpath.push_back
447                 (dir.substr(0, li)
448                  + "/share/"
449                  + dir.substr(li + libfragment.length()));
450         }
451
452         catpath.push_back(dir);
453     }
454
455     char buffer[1024];
456
457     for (vector<string>::iterator i = catpath.begin();
458          i != catpath.end(); ++i) {
459         
460         vector<string> files = listFiles(*i, suffix);
461
462         for (vector<string>::iterator fi = files.begin();
463              fi != files.end(); ++fi) {
464
465             string filepath = splicePath(*i, *fi);
466             ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
467
468             if (is.fail()) {
469 //                cerr << "failed to open: " << filepath << endl;
470                 continue;
471             }
472
473 //            cerr << "opened: " << filepath << endl;
474
475             while (!!is.getline(buffer, 1024)) {
476
477                 string line(buffer);
478
479 //                cerr << "line = " << line << endl;
480
481                 string::size_type di = line.find("::");
482                 if (di == string::npos) continue;
483
484                 string id = line.substr(0, di);
485                 string encodedCat = line.substr(di + 2);
486
487                 if (id.substr(0, 5) != "vamp:") continue;
488                 id = id.substr(5);
489
490                 while (encodedCat.length() >= 1 &&
491                        encodedCat[encodedCat.length()-1] == '\r') {
492                     encodedCat = encodedCat.substr(0, encodedCat.length()-1);
493                 }
494
495 //                cerr << "id = " << id << ", cat = " << encodedCat << endl;
496
497                 PluginCategoryHierarchy category;
498                 string::size_type ai;
499                 while ((ai = encodedCat.find(" > ")) != string::npos) {
500                     category.push_back(encodedCat.substr(0, ai));
501                     encodedCat = encodedCat.substr(ai + 3);
502                 }
503                 if (encodedCat != "") category.push_back(encodedCat);
504
505                 m_taxonomy[id] = category;
506             }
507         }
508     }
509 }    
510
511 void *
512 PluginLoader::Impl::loadLibrary(string path)
513 {
514     void *handle = 0;
515 #ifdef _WIN32
516     handle = LoadLibrary(path.c_str());
517     if (!handle) {
518         cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
519              << path << "\"" << endl;
520     }
521 #else
522     handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
523     if (!handle) {
524         cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
525              << path << "\": " << dlerror() << endl;
526     }
527 #endif
528     return handle;
529 }
530
531 void
532 PluginLoader::Impl::unloadLibrary(void *handle)
533 {
534 #ifdef _WIN32
535     FreeLibrary((HINSTANCE)handle);
536 #else
537     dlclose(handle);
538 #endif
539 }
540
541 void *
542 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol)
543 {
544 #ifdef _WIN32
545     return (void *)GetProcAddress((HINSTANCE)handle, symbol);
546 #else
547     return (void *)dlsym(handle, symbol);
548 #endif
549 }
550
551 string
552 PluginLoader::Impl::splicePath(string a, string b)
553 {
554 #ifdef _WIN32
555     return a + "\\" + b;
556 #else
557     return a + "/" + b;
558 #endif
559 }
560
561 vector<string>
562 PluginLoader::Impl::listFiles(string dir, string extension)
563 {
564     vector<string> files;
565
566 #ifdef _WIN32
567
568     string expression = dir + "\\*." + extension;
569     WIN32_FIND_DATA data;
570     HANDLE fh = FindFirstFile(expression.c_str(), &data);
571     if (fh == INVALID_HANDLE_VALUE) return files;
572
573     bool ok = true;
574     while (ok) {
575         files.push_back(data.cFileName);
576         ok = FindNextFile(fh, &data);
577     }
578
579     FindClose(fh);
580
581 #else
582
583     size_t extlen = extension.length();
584     DIR *d = opendir(dir.c_str());
585     if (!d) return files;
586             
587     struct dirent *e = 0;
588     while ((e = readdir(d))) {
589  
590         if (!e->d_name) continue;
591        
592         size_t len = strlen(e->d_name);
593         if (len < extlen + 2 ||
594             e->d_name + len - extlen - 1 != "." + extension) {
595             continue;
596         }
597
598         files.push_back(e->d_name);
599     }
600
601     closedir(d);
602 #endif
603
604     return files;
605 }
606
607 void
608 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
609 {
610     void *handle = m_pluginLibraryHandleMap[adapter];
611     if (handle) unloadLibrary(handle);
612     m_pluginLibraryHandleMap.erase(adapter);
613 }
614
615 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
616                                                                              Impl *loader) :
617     PluginWrapper(plugin),
618     m_loader(loader)
619 {
620 }
621
622 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
623 {
624     // We need to delete the plugin before calling pluginDeleted, as
625     // the delete call may require calling through to the descriptor
626     // (for e.g. cleanup) but pluginDeleted may unload the required
627     // library for the call.  To prevent a double deletion when our
628     // parent's destructor runs (after this one), be sure to set
629     // m_plugin to 0 after deletion.
630     delete m_plugin;
631     m_plugin = 0;
632
633     if (m_loader) m_loader->pluginDeleted(this);
634 }
635
636 }
637
638 }
639
640 _VAMP_SDK_HOSTSPACE_END(PluginLoader.cpp)
641