Merge with 2.0-ongoing R2885.
[ardour.git] / libs / vamp-sdk / vamp-sdk / hostext / 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-sdk/PluginHostAdapter.h"
38 #include "PluginLoader.h"
39 #include "PluginInputDomainAdapter.h"
40 #include "PluginChannelAdapter.h"
41
42 #include <fstream>
43 #include <cctype> // tolower
44
45 #ifdef _WIN32
46
47 #include <windows.h>
48 #include <tchar.h>
49 #define PLUGIN_SUFFIX "dll"
50
51 #else /* ! _WIN32 */
52
53 #include <dirent.h>
54 #include <dlfcn.h>
55
56 #ifdef __APPLE__
57 #define PLUGIN_SUFFIX "dylib"
58 #else /* ! __APPLE__ */
59 #define PLUGIN_SUFFIX "so"
60 #endif /* ! __APPLE__ */
61
62 #endif /* ! _WIN32 */
63
64 using namespace std;
65
66 namespace Vamp {
67         
68 namespace HostExt {
69
70 class PluginLoader::Impl
71 {
72 public:
73     Impl();
74     virtual ~Impl();
75
76     PluginKeyList listPlugins();
77
78     Plugin *loadPlugin(PluginKey key,
79                        float inputSampleRate,
80                        int adapterFlags);
81
82     PluginKey composePluginKey(string libraryName, string identifier);
83
84     PluginCategoryHierarchy getPluginCategory(PluginKey key);
85
86     string getLibraryPathForPlugin(PluginKey key);
87
88 protected:
89     class PluginDeletionNotifyAdapter : public PluginWrapper {
90     public:
91         PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader);
92         virtual ~PluginDeletionNotifyAdapter();
93     protected:
94         Impl *m_loader;
95     };
96
97     virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
98
99     map<PluginKey, string> m_pluginLibraryNameMap;
100     bool m_allPluginsEnumerated;
101     void enumeratePlugins(PluginKey forPlugin = "");
102
103     map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
104     void generateTaxonomy();
105
106     map<Plugin *, void *> m_pluginLibraryHandleMap;
107
108     bool decomposePluginKey(PluginKey key,
109                             string &libraryName, string &identifier);
110
111     void *loadLibrary(string path);
112     void unloadLibrary(void *handle);
113     void *lookupInLibrary(void *handle, const char *symbol);
114
115     string splicePath(string a, string b);
116     vector<string> listFiles(string dir, string ext);
117 };
118
119 PluginLoader *
120 PluginLoader::m_instance = 0;
121
122 PluginLoader::PluginLoader()
123 {
124     m_impl = new Impl();
125 }
126
127 PluginLoader::~PluginLoader()
128 {
129     delete m_impl;
130 }
131
132 PluginLoader *
133 PluginLoader::getInstance()
134 {
135     if (!m_instance) m_instance = new PluginLoader();
136     return m_instance;
137 }
138
139 vector<PluginLoader::PluginKey>
140 PluginLoader::listPlugins() 
141 {
142     return m_impl->listPlugins();
143 }
144
145 Plugin *
146 PluginLoader::loadPlugin(PluginKey key,
147                          float inputSampleRate,
148                          int adapterFlags)
149 {
150     return m_impl->loadPlugin(key, inputSampleRate, adapterFlags);
151 }
152
153 PluginLoader::PluginKey
154 PluginLoader::composePluginKey(string libraryName, string identifier) 
155 {
156     return m_impl->composePluginKey(libraryName, identifier);
157 }
158
159 PluginLoader::PluginCategoryHierarchy
160 PluginLoader::getPluginCategory(PluginKey key)
161 {
162     return m_impl->getPluginCategory(key);
163 }
164
165 string
166 PluginLoader::getLibraryPathForPlugin(PluginKey key)
167 {
168     return m_impl->getLibraryPathForPlugin(key);
169 }
170  
171 PluginLoader::Impl::Impl() :
172     m_allPluginsEnumerated(false)
173 {
174 }
175
176 PluginLoader::Impl::~Impl()
177 {
178 }
179
180 vector<PluginLoader::PluginKey>
181 PluginLoader::Impl::listPlugins() 
182 {
183     if (!m_allPluginsEnumerated) enumeratePlugins();
184
185     vector<PluginKey> plugins;
186     for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin();
187          mi != m_pluginLibraryNameMap.end(); ++mi) {
188         plugins.push_back(mi->first);
189     }
190
191     return plugins;
192 }
193
194 void
195 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin)
196 {
197     vector<string> path = PluginHostAdapter::getPluginPath();
198
199     string libraryName, identifier;
200     if (forPlugin != "") {
201         if (!decomposePluginKey(forPlugin, libraryName, identifier)) {
202             std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \""
203                       << forPlugin << "\" in enumerate" << std::endl;
204             return;
205         }
206     }
207
208     for (size_t i = 0; i < path.size(); ++i) {
209         
210         vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
211
212         for (vector<string>::iterator fi = files.begin();
213              fi != files.end(); ++fi) {
214             
215             if (libraryName != "") {
216                 // libraryName is lowercased and lacking an extension,
217                 // as it came from the plugin key
218                 string temp = *fi;
219                 for (size_t i = 0; i < temp.length(); ++i) {
220                     temp[i] = tolower(temp[i]);
221                 }
222                 string::size_type pi = temp.find('.');
223                 if (pi == string::npos) {
224                     if (libraryName != temp) continue;
225                 } else {
226                     if (libraryName != temp.substr(0, pi)) continue;
227                 }
228             }
229
230             string fullPath = path[i];
231             fullPath = splicePath(fullPath, *fi);
232             void *handle = loadLibrary(fullPath);
233             if (!handle) continue;
234             
235             VampGetPluginDescriptorFunction fn =
236                 (VampGetPluginDescriptorFunction)lookupInLibrary
237                 (handle, "vampGetPluginDescriptor");
238             
239             if (!fn) {
240                 unloadLibrary(handle);
241                 continue;
242             }
243             
244             int index = 0;
245             const VampPluginDescriptor *descriptor = 0;
246             
247             while ((descriptor = fn(VAMP_API_VERSION, index))) {
248                 ++index;
249                 if (identifier != "") {
250                     if (descriptor->identifier != identifier) continue;
251                 }
252                 PluginKey key = composePluginKey(*fi, descriptor->identifier);
253 //                std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl;
254                 if (m_pluginLibraryNameMap.find(key) ==
255                     m_pluginLibraryNameMap.end()) {
256                     m_pluginLibraryNameMap[key] = fullPath;
257                 }
258             }
259             
260             unloadLibrary(handle);
261         }
262     }
263
264     if (forPlugin == "") m_allPluginsEnumerated = true;
265 }
266
267 PluginLoader::PluginKey
268 PluginLoader::Impl::composePluginKey(string libraryName, string identifier)
269 {
270     string basename = libraryName;
271
272     string::size_type li = basename.rfind('/');
273     if (li != string::npos) basename = basename.substr(li + 1);
274
275     li = basename.find('.');
276     if (li != string::npos) basename = basename.substr(0, li);
277
278     for (size_t i = 0; i < basename.length(); ++i) {
279         basename[i] = tolower(basename[i]);
280     }
281
282     return basename + ":" + identifier;
283 }
284
285 bool
286 PluginLoader::Impl::decomposePluginKey(PluginKey key,
287                                        string &libraryName,
288                                        string &identifier)
289 {
290     string::size_type ki = key.find(':');
291     if (ki == string::npos) {
292         return false;
293     }
294
295     libraryName = key.substr(0, ki);
296     identifier = key.substr(ki + 1);
297     return true;
298 }
299
300 PluginLoader::PluginCategoryHierarchy
301 PluginLoader::Impl::getPluginCategory(PluginKey plugin)
302 {
303     if (m_taxonomy.empty()) generateTaxonomy();
304     if (m_taxonomy.find(plugin) == m_taxonomy.end()) {
305         return PluginCategoryHierarchy();
306     }
307     return m_taxonomy[plugin];
308 }
309
310 string
311 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin)
312 {
313     if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
314         if (m_allPluginsEnumerated) return "";
315         enumeratePlugins(plugin);
316     }
317     if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
318         return "";
319     }
320     return m_pluginLibraryNameMap[plugin];
321 }    
322
323 Plugin *
324 PluginLoader::Impl::loadPlugin(PluginKey key,
325                                float inputSampleRate, int adapterFlags)
326 {
327     string libname, identifier;
328     if (!decomposePluginKey(key, libname, identifier)) {
329         std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \""
330                   << key << "\" in loadPlugin" << std::endl;
331         return 0;
332     }
333         
334     string fullPath = getLibraryPathForPlugin(key);
335     if (fullPath == "") return 0;
336     
337     void *handle = loadLibrary(fullPath);
338     if (!handle) return 0;
339     
340     VampGetPluginDescriptorFunction fn =
341         (VampGetPluginDescriptorFunction)lookupInLibrary
342         (handle, "vampGetPluginDescriptor");
343
344     if (!fn) {
345         unloadLibrary(handle);
346         return 0;
347     }
348
349     int index = 0;
350     const VampPluginDescriptor *descriptor = 0;
351
352     while ((descriptor = fn(VAMP_API_VERSION, index))) {
353
354         if (string(descriptor->identifier) == identifier) {
355
356             Vamp::PluginHostAdapter *plugin =
357                 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
358
359             Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
360
361             m_pluginLibraryHandleMap[adapter] = handle;
362
363             if (adapterFlags & ADAPT_INPUT_DOMAIN) {
364                 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
365                     adapter = new PluginInputDomainAdapter(adapter);
366                 }
367             }
368
369             if (adapterFlags & ADAPT_CHANNEL_COUNT) {
370                 adapter = new PluginChannelAdapter(adapter);
371             }
372
373             return adapter;
374         }
375
376         ++index;
377     }
378
379     cerr << "Vamp::HostExt::PluginLoader: Plugin \""
380          << identifier << "\" not found in library \""
381          << fullPath << "\"" << endl;
382
383     return 0;
384 }
385
386 void
387 PluginLoader::Impl::generateTaxonomy()
388 {
389 //    cerr << "PluginLoader::Impl::generateTaxonomy" << endl;
390
391     vector<string> path = PluginHostAdapter::getPluginPath();
392     string libfragment = "/lib/";
393     vector<string> catpath;
394
395     string suffix = "cat";
396
397     for (vector<string>::iterator i = path.begin();
398          i != path.end(); ++i) {
399
400         // It doesn't matter that we're using literal forward-slash in
401         // this bit, as it's only relevant if the path contains
402         // "/lib/", which is only meaningful and only plausible on
403         // systems with forward-slash delimiters
404         
405         string dir = *i;
406         string::size_type li = dir.find(libfragment);
407
408         if (li != string::npos) {
409             catpath.push_back
410                 (dir.substr(0, li)
411                  + "/share/"
412                  + dir.substr(li + libfragment.length()));
413         }
414
415         catpath.push_back(dir);
416     }
417
418     char buffer[1024];
419
420     for (vector<string>::iterator i = catpath.begin();
421          i != catpath.end(); ++i) {
422         
423         vector<string> files = listFiles(*i, suffix);
424
425         for (vector<string>::iterator fi = files.begin();
426              fi != files.end(); ++fi) {
427
428             string filepath = splicePath(*i, *fi);
429             ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
430
431             if (is.fail()) {
432 //                cerr << "failed to open: " << filepath << endl;
433                 continue;
434             }
435
436 //            cerr << "opened: " << filepath << endl;
437
438             while (!!is.getline(buffer, 1024)) {
439
440                 string line(buffer);
441
442 //                cerr << "line = " << line << endl;
443
444                 string::size_type di = line.find("::");
445                 if (di == string::npos) continue;
446
447                 string id = line.substr(0, di);
448                 string encodedCat = line.substr(di + 2);
449
450                 if (id.substr(0, 5) != "vamp:") continue;
451                 id = id.substr(5);
452
453                 while (encodedCat.length() >= 1 &&
454                        encodedCat[encodedCat.length()-1] == '\r') {
455                     encodedCat = encodedCat.substr(0, encodedCat.length()-1);
456                 }
457
458 //                cerr << "id = " << id << ", cat = " << encodedCat << endl;
459
460                 PluginCategoryHierarchy category;
461                 string::size_type ai;
462                 while ((ai = encodedCat.find(" > ")) != string::npos) {
463                     category.push_back(encodedCat.substr(0, ai));
464                     encodedCat = encodedCat.substr(ai + 3);
465                 }
466                 if (encodedCat != "") category.push_back(encodedCat);
467
468                 m_taxonomy[id] = category;
469             }
470         }
471     }
472 }    
473
474 void *
475 PluginLoader::Impl::loadLibrary(string path)
476 {
477     void *handle = 0;
478 #ifdef _WIN32
479     handle = LoadLibrary(path.c_str());
480     if (!handle) {
481         cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
482              << path << "\"" << endl;
483     }
484 #else
485     handle = dlopen(path.c_str(), RTLD_LAZY);
486     if (!handle) {
487         cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
488              << path << "\": " << dlerror() << endl;
489     }
490 #endif
491     return handle;
492 }
493
494 void
495 PluginLoader::Impl::unloadLibrary(void *handle)
496 {
497 #ifdef _WIN32
498     FreeLibrary((HINSTANCE)handle);
499 #else
500     dlclose(handle);
501 #endif
502 }
503
504 void *
505 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol)
506 {
507 #ifdef _WIN32
508     return (void *)GetProcAddress((HINSTANCE)handle, symbol);
509 #else
510     return (void *)dlsym(handle, symbol);
511 #endif
512 }
513
514 string
515 PluginLoader::Impl::splicePath(string a, string b)
516 {
517 #ifdef _WIN32
518     return a + "\\" + b;
519 #else
520     return a + "/" + b;
521 #endif
522 }
523
524 vector<string>
525 PluginLoader::Impl::listFiles(string dir, string extension)
526 {
527     vector<string> files;
528
529 #ifdef _WIN32
530
531     string expression = dir + "\\*." + extension;
532     WIN32_FIND_DATA data;
533     HANDLE fh = FindFirstFile(expression.c_str(), &data);
534     if (fh == INVALID_HANDLE_VALUE) return files;
535
536     bool ok = true;
537     while (ok) {
538         files.push_back(data.cFileName);
539         ok = FindNextFile(fh, &data);
540     }
541
542     FindClose(fh);
543
544 #else
545
546     size_t extlen = extension.length();
547     DIR *d = opendir(dir.c_str());
548     if (!d) return files;
549             
550     struct dirent *e = 0;
551     while ((e = readdir(d))) {
552         
553         if (!(e->d_type & DT_REG) || !e->d_name) continue;
554         
555         size_t len = strlen(e->d_name);
556         if (len < extlen + 2 ||
557             e->d_name + len - extlen - 1 != "." + extension) {
558             continue;
559         }
560
561         files.push_back(e->d_name);
562     }
563
564     closedir(d);
565 #endif
566
567     return files;
568 }
569
570 void
571 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
572 {
573     void *handle = m_pluginLibraryHandleMap[adapter];
574     if (handle) unloadLibrary(handle);
575     m_pluginLibraryHandleMap.erase(adapter);
576 }
577
578 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
579                                                                              Impl *loader) :
580     PluginWrapper(plugin),
581     m_loader(loader)
582 {
583 }
584
585 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
586 {
587     // We need to delete the plugin before calling pluginDeleted, as
588     // the delete call may require calling through to the descriptor
589     // (for e.g. cleanup) but pluginDeleted may unload the required
590     // library for the call.  To prevent a double deletion when our
591     // parent's destructor runs (after this one), be sure to set
592     // m_plugin to 0 after deletion.
593     delete m_plugin;
594     m_plugin = 0;
595
596     if (m_loader) m_loader->pluginDeleted(this);
597 }
598
599 }
600
601 }