comparison PyPlugScanner.cpp @ 37:27bab3a16c9a vampy2final

new branch Vampy2final
author fazekasgy
date Mon, 05 Oct 2009 11:28:00 +0000
parents
children 62dcaa5fe6f8
comparison
equal deleted inserted replaced
-1:000000000000 37:27bab3a16c9a
1 /*
2
3 * Vampy : This plugin is a wrapper around the Vamp plugin API.
4 * It allows for writing Vamp plugins in Python.
5
6 * Centre for Digital Music, Queen Mary University of London.
7 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
8 * for licence information.)
9
10 */
11
12
13 #include "PyPlugScanner.h"
14 #include <algorithm>
15 #include <cstdlib>
16 //#include "vamp-hostsdk/PluginHostAdapter.h"
17
18 #ifdef _WIN32
19 #include <windows.h>
20 #include <tchar.h>
21 #define pathsep ("\\")
22 #else
23 #include <dirent.h>
24 #include <dlfcn.h>
25 #define pathsep ("/")
26 #endif
27 #define joinPath(a,b) ( (a)+pathsep+(b) )
28
29 using std::string;
30 using std::vector;
31 using std::cerr;
32 using std::endl;
33 using std::find;
34
35 PyPlugScanner::PyPlugScanner()
36 {
37
38 }
39
40 PyPlugScanner *PyPlugScanner::m_instance = NULL;
41 bool PyPlugScanner::m_hasInstance = false;
42
43 PyPlugScanner*
44 PyPlugScanner::getInstance()
45 {
46 if (!m_hasInstance) {
47 m_instance = new PyPlugScanner();
48 m_hasInstance = true;
49 }
50 return m_instance;
51 }
52
53 void
54 PyPlugScanner::setPath(vector<string> path)
55 {
56 m_path=path;
57 }
58
59 // We assume that each script on the path has one valid class
60 vector<string>
61 PyPlugScanner::getPyPlugs()
62 {
63 //for_each m_path listFiles then return vector<pyPlugs>
64 //key format: FullPathString/FileName.py:ClassName
65
66 bool getCompiled = true;
67 char* getPyc = getenv("VAMPY_COMPILED");
68 if (getPyc) {
69 string value(getPyc);
70 cerr << "VAMPY_COMPILED=" << value << endl;
71 getCompiled = value.compare("1")?false:true;
72 }
73
74 vector<string> pyPlugs;
75 string pluginKey;
76 PyObject *pyClass;
77
78 for (size_t i = 0; i < m_path.size(); ++i) {
79
80 vector<string> files = listFiles(m_path[i],"py");
81
82 /// recognise byte compiled plugins
83 if (getCompiled) {
84 vector<string> compiled_files = listFiles(m_path[i],"pyc");
85 mergeFileLists(compiled_files,files);
86 }
87
88 for (vector<string>::iterator fi = files.begin();
89 fi != files.end(); ++fi) {
90 string script = *fi;
91 if (!script.empty()) {
92 string classname=script.substr(0,script.rfind('.'));
93 pluginKey=joinPath(m_path[i],script)+":"+classname;
94 pyClass = getScriptClass(m_path[i],classname);
95 if (pyClass == NULL)
96 cerr << "Warning: Syntax error in VamPy plugin: "
97 << classname << ". Avoiding plugin." << endl;
98 else {
99 pyPlugs.push_back(pluginKey);
100 m_pyClasses.push_back(pyClass);
101 }
102 //pyPlugs.push_back(pluginKey);
103 }
104 }
105 }
106
107 return pyPlugs;
108
109 }
110
111 /// insert python byte code names (.pyc) if a .py file can not be found
112 /// The interpreter automatically generates byte code files and executes
113 /// them if they exist. Therefore, we prefer .py files, but we allow
114 /// (relatively) closed source distributions by recognising .pyc files.
115 void
116 PyPlugScanner::mergeFileLists(vector<string> &pyc, vector<string> &py)
117 {
118 for (vector<string>::iterator pycit = pyc.begin();
119 pycit != pyc.end(); ++pycit) {
120 // cerr << *pycit;
121 string pyc_name = *pycit;
122 string py_name = pyc_name.substr(0,pyc_name.rfind('.')) + ".py";
123 vector<string>::iterator pyit = find (py.begin(), py.end(), py_name);
124 if (pyit == py.end()) py.push_back(pyc_name);
125 }
126
127 }
128
129
130 //For now return one class object found in each script
131 vector<PyObject*>
132 PyPlugScanner::getPyClasses()
133 {
134 return m_pyClasses;
135
136 }
137
138 //Validate
139 //This should not be called more than once!
140 PyObject*
141 PyPlugScanner::getScriptClass(string path, string classname)
142 {
143
144 //Add plugin path to active Python Path
145 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
146 PyRun_SimpleString(pyCmd.c_str());
147
148 //Assign an object to the source code
149 PyObject *pySource = PyString_FromString(classname.c_str());
150
151 //Import it as a module into the py interpreter
152 PyObject *pyModule = PyImport_Import(pySource);
153 PyObject* pyError = PyErr_Occurred();
154 if (! pyError == 0) {
155 cerr << "ERROR: error importing source: " << classname << endl;
156 PyErr_Print();
157 Py_DECREF(pySource);
158 Py_CLEAR(pyModule); // safer if pyModule==NULL
159 return NULL;
160 }
161 Py_DECREF(pySource);
162
163 //Read the dictionary object holding the namespace of the module (borrowed reference)
164 PyObject *pyDict = PyModule_GetDict(pyModule);
165 Py_DECREF(pyModule);
166
167 //Get the PluginClass from the module (borrowed reference)
168 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
169
170 //Check if class is present and a callable method is implemented
171 if (pyClass && PyCallable_Check(pyClass)) {
172
173 return pyClass;
174 }
175 else {
176 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
177 << "Hint: plugin source filename and plugin class name must be the same." << endl;
178 PyErr_Print();
179 return NULL;
180 }
181 }
182
183
184
185 // Return a list of files in dir with given extension
186 // Code taken from hostext/PluginLoader.cpp
187 vector<string>
188 PyPlugScanner::listFiles(string dir, string extension)
189 {
190 vector<string> files;
191
192 #ifdef _WIN32
193
194 string expression = dir + "\\*." + extension;
195 WIN32_FIND_DATA data;
196 HANDLE fh = FindFirstFile(expression.c_str(), &data);
197 if (fh == INVALID_HANDLE_VALUE) return files;
198
199 bool ok = true;
200 while (ok) {
201 files.push_back(data.cFileName);
202 ok = FindNextFile(fh, &data);
203 }
204
205 FindClose(fh);
206
207 #else
208
209 size_t extlen = extension.length();
210 DIR *d = opendir(dir.c_str());
211 if (!d) return files;
212
213 struct dirent *e = 0;
214 while ((e = readdir(d))) {
215
216 if (!e->d_name) continue;
217
218 size_t len = strlen(e->d_name);
219 if (len < extlen + 2 ||
220 e->d_name + len - extlen - 1 != "." + extension) {
221 continue;
222 }
223 //cerr << "pyscripts: " << e->d_name << endl;
224 files.push_back(e->d_name);
225 }
226
227 closedir(d);
228 #endif
229
230 return files;
231 }
232
233
234 //!!! It would probably be better to actually call
235 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
236 // needs to link against vamp-hostsdk, but that's probably acceptable
237 // as it is sort of a host as well.
238
239 // std::vector<std::string>
240 // PyPlugScanner::getAllValidPath()
241 // {
242 // Vamp::PluginHostAdapter host_adapter( ??? );
243 // return host_adapter.getPluginPath();
244 // }
245
246 // tried to implement it, but found a bit confusing how to
247 // instantiate the host adapter here...
248
249
250 //Return correct plugin directories as per platform
251 //Code taken from vamp-sdk/PluginHostAdapter.cpp
252 std::vector<std::string>
253 PyPlugScanner::getAllValidPath()
254 {
255
256 std::vector<std::string> path;
257 std::string envPath;
258
259 char *cpath = getenv("VAMP_PATH");
260 if (cpath) envPath = cpath;
261
262 #ifdef _WIN32
263 #define PATH_SEPARATOR ';'
264 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
265 #else
266 #define PATH_SEPARATOR ':'
267 #ifdef __APPLE__
268 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
269 #else
270 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
271 #endif
272 #endif
273
274 if (envPath == "") {
275 envPath = DEFAULT_VAMP_PATH;
276 char *chome = getenv("HOME");
277 if (chome) {
278 std::string home(chome);
279 std::string::size_type f;
280 while ((f = envPath.find("$HOME")) != std::string::npos &&
281 f < envPath.length()) {
282 envPath.replace(f, 5, home);
283 }
284 }
285 #ifdef _WIN32
286 char *cpfiles = getenv("ProgramFiles");
287 if (!cpfiles) cpfiles = "C:\\Program Files";
288 std::string pfiles(cpfiles);
289 std::string::size_type f;
290 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
291 f < envPath.length()) {
292 envPath.replace(f, 14, pfiles);
293 }
294 #endif
295 }
296
297 std::string::size_type index = 0, newindex = 0;
298
299 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
300 path.push_back(envPath.substr(index, newindex - index));
301 index = newindex + 1;
302 }
303
304 path.push_back(envPath.substr(index));
305
306 //can add an extra path for vampy plugins
307 char* extraPath = getenv("VAMPY_EXTPATH");
308 if (extraPath) {
309 string vampyPath(extraPath);
310 cerr << "VAMPY_EXTPATH=" << vampyPath << endl;
311 path.push_back(vampyPath);
312 }
313
314 return path;
315 }