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