-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathPyPlugScanner.cpp
421 lines (349 loc) · 10.9 KB
/
PyPlugScanner.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
/* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */
/*
* Vampy : This plugin is a wrapper around the Vamp plugin API.
* It allows for writing Vamp plugins in Python.
* Centre for Digital Music, Queen Mary University of London.
* Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
* for licence information.)
*/
#include "PyPlugScanner.h"
#include "PyExtensionManager.h"
#include "Debug.h"
#include <algorithm>
#include <cstdlib>
//#include "vamp-hostsdk/PluginHostAdapter.h"
#ifdef _WIN32
// We should be using the unicode apis, but we're not (yet)
#undef UNICODE
#undef _UNICODE
#define _MBCS 1
#include <windows.h>
#include <tchar.h>
#define pathsep ("\\")
#else
#include <dirent.h>
#include <dlfcn.h>
#define pathsep ("/")
#endif
#define joinPath(a,b) ( (a)+pathsep+(b) )
using std::string;
using std::vector;
using std::cerr;
using std::endl;
using std::find;
PyPlugScanner::PyPlugScanner()
{
}
PyPlugScanner *PyPlugScanner::m_instance = NULL;
bool PyPlugScanner::m_hasInstance = false;
PyPlugScanner*
PyPlugScanner::getInstance()
{
if (!m_hasInstance) {
m_instance = new PyPlugScanner();
m_hasInstance = true;
}
return m_instance;
}
void
PyPlugScanner::setPath(vector<string> path)
{
m_path=path;
}
// We assume that each script on the path has one valid class
vector<string>
PyPlugScanner::getPyPlugs()
{
//for_each m_path listFiles then return vector<pyPlugs>
//key format: FullPathString/FileName.py:ClassName
bool getCompiled = true;
char* getPyc = getenv("VAMPY_COMPILED");
if (getPyc) {
string value(getPyc);
cerr << "VAMPY_COMPILED=" << value << endl;
getCompiled = value.compare("1")?false:true;
}
vector<string> pyPlugs;
string pluginKey;
PyObject *pyClass;
for (size_t i = 0; i < m_path.size(); ++i) {
vector<string> files = listFiles(m_path[i],"py");
/// recognise byte compiled plugins
if (getCompiled) {
vector<string> pyc_files = listFiles(m_path[i],"pyc");
vector<string> pyo_files = listFiles(m_path[i],"pyo");
mergeFileLists(pyc_files,pyo_files,".pyo");
mergeFileLists(pyo_files,files,".py");
}
for (vector<string>::iterator fi = files.begin();
fi != files.end(); ++fi) {
string script = *fi;
if (!script.empty()) {
string classname=script.substr(0,script.rfind('.'));
pluginKey=joinPath(m_path[i],script)+":"+classname;
pyClass = getScriptClass(m_path[i],classname);
if (pyClass == NULL)
cerr << "Warning: Syntax error or other problem in scanning VamPy plugin: "
<< classname << ". Avoiding plugin." << endl;
else {
pyPlugs.push_back(pluginKey);
m_pyClasses.push_back(pyClass);
}
}
}
}
return pyPlugs;
}
/// insert python byte code names (.pyc) if a .py file can not be found
/// The interpreter automatically generates byte code files and executes
/// them if they exist. Therefore, we prefer .py files, but we allow
/// (relatively) closed source distributions by recognising .pyc files.
void
PyPlugScanner::mergeFileLists(vector<string> &src, vector<string> &tg, string target_ext)
{
for (vector<string>::iterator srcit = src.begin();
srcit != src.end(); ++srcit) {
// cerr << *srcit;
string src_name = *srcit;
string tg_name = src_name.substr(0,src_name.rfind('.')) + target_ext;
vector<string>::iterator tgit = find (tg.begin(), tg.end(), tg_name);
if (tgit == tg.end()) tg.push_back(src_name);
}
}
//For now return one class object found in each script
vector<PyObject*>
PyPlugScanner::getPyClasses()
{
return m_pyClasses;
}
//Validate
//This should not be called more than once!
PyObject*
PyPlugScanner::getScriptClass(string path, string classname)
{
//Add plugin path to active Python Path
string pyCmd =
"import sys\n"
"sys.path.append('" + path + "')\n"
"sys.argv = ['" + classname + "']\n";
PyRun_SimpleString(pyCmd.c_str());
//Assign an object to the source code
PyObject *pySource = PyString_FromString(classname.c_str());
//Import it as a module into the py interpreter
PyObject *pyModule = PyImport_Import(pySource);
PyObject* pyError = PyErr_Occurred();
if (pyError) {
cerr << "ERROR: error importing source: " << classname << endl;
PyErr_Print();
Py_DECREF(pySource);
Py_CLEAR(pyModule); // safer if pyModule==NULL
return NULL;
}
Py_DECREF(pySource);
//Read the dictionary object holding the namespace of the module (borrowed reference)
PyObject *pyDict = PyModule_GetDict(pyModule);
Py_DECREF(pyModule);
//Get the PluginClass from the module (borrowed reference)
PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
if (pyClass == Py_None) {
DSTREAM << "Vampy: class name " << classname
<< " is None in module; assuming it was scrubbed "
<< "following an earlier load failure" << endl;
return NULL;
}
// Check if class is present and a callable method is implemented
if (!pyClass || !PyCallable_Check(pyClass)) {
cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
<< "Hint: plugin source filename and plugin class name must be the same." << endl;
PyErr_Print();
return NULL;
}
bool acceptable = true;
// Check that the module doesn't have any name collisions with
// our own symbols
int i = 0;
while (PyExtensionManager::m_exposedNames[i]) {
const char* name = PyExtensionManager::m_exposedNames[i];
i++;
PyObject *item = PyDict_GetItemString(pyDict, name);
if (!item) continue;
if (item == Py_None) {
DSTREAM << "Vampy: name " << name << " is None "
<< "in module " << classname
<< "; assuming it was cleared on unload"
<< endl;
continue;
}
PyTypeObject *metatype = Py_TYPE(item);
if (!metatype) {
cerr << "ERROR: plugin " << classname
<< " reports null metatype for Vampy name \""
<< name << "\" (incomplete due to load error?)"
<< endl;
acceptable = false;
break;
}
if (!strcmp(name, "frame2RealTime")) {
if (metatype != &PyCFunction_Type) {
cerr << "ERROR: plugin " << classname
<< " redefines Vampy function name \""
<< name << "\" (metatype is \""
<< metatype->tp_name << "\")" << endl;
acceptable = false;
break;
} else {
continue;
}
}
if (metatype != &PyType_Type) {
cerr << "ERROR: plugin " << classname
<< " uses Vampy reserved type name \"" << name
<< "\" for non-type (metatype is \""
<< metatype->tp_name << "\")" << endl;
acceptable = false;
break;
}
PyTypeObject *type = (PyTypeObject *)item;
if (type->tp_name == std::string("vampy.") + name) {
DSTREAM << "Vampy: acceptable Vampy type name "
<< type->tp_name << " found in module" << endl;
} else {
cerr << "ERROR: plugin " << classname
<< " redefines Vampy type \"" << name << "\"";
if (strcmp(type->tp_name, name)) {
cerr << " (as \"" << type->tp_name << "\")";
}
cerr << endl;
acceptable = false;
break;
}
}
if (acceptable) {
return pyClass;
} else {
PyObject *key = PyString_FromString(classname.c_str());
PyDict_SetItem(pyDict, key, Py_None);
Py_DECREF(key);
return NULL;
}
}
// Return a list of files in dir with given extension
// Code taken from hostext/PluginLoader.cpp
vector<string>
PyPlugScanner::listFiles(string dir, string extension)
{
vector<string> files;
#ifdef _WIN32
string expression = dir + "\\*." + extension;
WIN32_FIND_DATA data;
HANDLE fh = FindFirstFile(expression.c_str(), &data);
if (fh == INVALID_HANDLE_VALUE) return files;
bool ok = true;
while (ok) {
files.push_back(data.cFileName);
ok = FindNextFile(fh, &data);
}
FindClose(fh);
#else
size_t extlen = extension.length();
DIR *d = opendir(dir.c_str());
if (!d) return files;
struct dirent *e = 0;
while ((e = readdir(d))) {
size_t len = strlen(e->d_name);
if (len < extlen + 2 ||
e->d_name + len - extlen - 1 != "." + extension) {
continue;
}
files.push_back(e->d_name);
}
closedir(d);
#endif
return files;
}
//!!! It would probably be better to actually call
// PluginHostAdapter::getPluginPath. That would mean this "plugin"
// needs to link against vamp-hostsdk, but that's probably acceptable
// as it is sort of a host as well.
// std::vector<std::string>
// PyPlugScanner::getAllValidPath()
// {
// Vamp::PluginHostAdapter host_adapter( ??? );
// return host_adapter.getPluginPath();
// }
// tried to implement it, but found a bit confusing how to
// instantiate the host adapter here...
//Return correct plugin directories as per platform
//Code taken from vamp-sdk/PluginHostAdapter.cpp
std::vector<std::string>
PyPlugScanner::getAllValidPath()
{
std::vector<std::string> path;
std::string envPath;
bool nonNative32 = false;
#ifdef _WIN32
BOOL (WINAPI *fnIsWow64Process)(HANDLE, PBOOL) =
(BOOL (WINAPI *)(HANDLE, PBOOL)) GetProcAddress
(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if (fnIsWow64Process) {
BOOL wow64 = FALSE;
if (fnIsWow64Process(GetCurrentProcess(), &wow64) && wow64) {
nonNative32 = true;
}
}
#endif
char *cpath;
if (nonNative32) {
cpath = getenv("VAMP_PATH_32");
} else {
cpath = getenv("VAMP_PATH");
}
if (cpath) envPath = cpath;
#ifdef _WIN32
#define PATH_SEPARATOR ';'
#define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
#else
#define PATH_SEPARATOR ':'
#ifdef __APPLE__
#define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
#else
#define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
#endif
#endif
if (envPath == "") {
envPath = DEFAULT_VAMP_PATH;
char *chome = getenv("HOME");
if (chome) {
std::string home(chome);
std::string::size_type f;
while ((f = envPath.find("$HOME")) != std::string::npos &&
f < envPath.length()) {
envPath.replace(f, 5, home);
}
}
#ifdef _WIN32
const char *cpfiles = getenv("ProgramFiles");
if (!cpfiles) cpfiles = "C:\\Program Files";
std::string pfiles(cpfiles);
std::string::size_type f;
while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
f < envPath.length()) {
envPath.replace(f, 14, pfiles);
}
#endif
}
std::string::size_type index = 0, newindex = 0;
while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
path.push_back(envPath.substr(index, newindex - index));
index = newindex + 1;
}
path.push_back(envPath.substr(index));
//can add an extra path for vampy plugins
char* extraPath = getenv("VAMPY_EXTPATH");
if (extraPath) {
string vampyPath(extraPath);
cerr << "VAMPY_EXTPATH=" << vampyPath << endl;
path.push_back(vampyPath);
}
return path;
}