annotate PyPlugScanner.cpp @ 24:7d28bed0864e

* Rearrange Python plugin construction. Formerly, the PyPluginAdapter has retained a single plugin instance pointer for each plugin found, and its createPlugin method has simply returned a new PyPlugin object wrapping the same instance pointer. This has a couple of negative consequences: - Because construction of the actual Python instance occurred before the wrapper was constructed, it was not possible to pass arguments (i.e. the sample rate) from the wrapper constructor to the Python plugin instance constructor -- they had to be passed later, to initialise, disadvantaging those plugins that would like to use the sample rate for parameter & step/block size calculations etc - Because there was only a single Python plugin instance, it was not possible to run more than one instance at once with any isolation This rework instead stores the Python class pointer (rather than instance pointer) in the PyPluginAdapter, and each PyPlugin wrapper instance creates its own Python plugin instance. What could possibly go wrong?
author cannam
date Mon, 17 Aug 2009 15:22:06 +0000
parents 3af6b5990ad8
children 7648f3f2fa14
rev   line source
fazekasgy@0 1 /**
cannam@7 2 * This Vamp plugin is a wrapper for Python Scripts. (VamPy)
fazekasgy@0 3 * Centre for Digital Music, Queen Mary, University of London.
fazekasgy@0 4 * Copyright 2008, George Fazekas.
fazekasgy@0 5
fazekasgy@0 6 */
fazekasgy@0 7
fazekasgy@0 8
fazekasgy@0 9 #include "PyPlugScanner.h"
fazekasgy@0 10
fazekasgy@0 11 //#include <fstream>
fazekasgy@0 12 //#include <cctype>
fazekasgy@0 13
fazekasgy@0 14 #ifdef _WIN32
fazekasgy@0 15 #include <windows.h>
fazekasgy@0 16 #include <tchar.h>
fazekasgy@0 17 #define pathsep ("\\")
fazekasgy@0 18 #else
fazekasgy@0 19 #include <dirent.h>
fazekasgy@0 20 #include <dlfcn.h>
fazekasgy@0 21 #define pathsep ("/")
fazekasgy@0 22 #endif
fazekasgy@0 23 #define joinPath(a,b) ( (a)+pathsep+(b) )
fazekasgy@0 24
fazekasgy@0 25 using std::string;
fazekasgy@0 26 using std::vector;
fazekasgy@0 27 using std::cerr;
fazekasgy@0 28 using std::endl;
fazekasgy@0 29
fazekasgy@0 30 PyPlugScanner::PyPlugScanner()
fazekasgy@0 31 {
fazekasgy@0 32
fazekasgy@0 33 }
fazekasgy@0 34
fazekasgy@0 35 PyPlugScanner *PyPlugScanner::m_instance = NULL;
fazekasgy@0 36 bool PyPlugScanner::m_hasInstance = false;
fazekasgy@0 37
fazekasgy@0 38 PyPlugScanner*
fazekasgy@0 39 PyPlugScanner::getInstance()
fazekasgy@0 40 {
fazekasgy@0 41 if (!m_hasInstance) {
fazekasgy@0 42 m_instance = new PyPlugScanner();
fazekasgy@0 43 m_hasInstance = true;
fazekasgy@0 44 }
fazekasgy@0 45 return m_instance;
fazekasgy@0 46 }
fazekasgy@0 47
fazekasgy@0 48 void
fazekasgy@0 49 PyPlugScanner::setPath(vector<string> path)
fazekasgy@0 50 {
fazekasgy@0 51 m_path=path;
fazekasgy@0 52 }
fazekasgy@0 53
fazekasgy@0 54 // TODO: This should return all scripts for all valid paths
fazekasgy@0 55 // Validate python classes here?
fazekasgy@0 56 // For now, we assume that each script on the path has one valid class
fazekasgy@0 57 vector<string>
fazekasgy@0 58 PyPlugScanner::getPyPlugs()
fazekasgy@0 59 {
fazekasgy@0 60 //foreach m_path listFiles then return vector<pyPlugs>
fazekasgy@0 61 //format: FullPathString/FileName.py:ClassName
fazekasgy@0 62
fazekasgy@0 63 vector<string> pyPlugs;
fazekasgy@0 64 string pluginKey;
cannam@24 65 PyObject *pyClass;
fazekasgy@0 66
fazekasgy@0 67 for (size_t i = 0; i < m_path.size(); ++i) {
fazekasgy@0 68
fazekasgy@0 69 vector<string> files = listFiles(m_path[i],"py");
fazekasgy@0 70
fazekasgy@0 71 for (vector<string>::iterator fi = files.begin();
fazekasgy@0 72 fi != files.end(); ++fi) {
fazekasgy@0 73 string script = *fi;
fazekasgy@0 74 if (!script.empty()) {
fazekasgy@0 75 string classname=script.substr(0,script.rfind('.'));
fazekasgy@0 76 pluginKey=joinPath(m_path[i],script)+":"+classname;
cannam@24 77 pyClass = getScriptClass(m_path[i],classname);
cannam@24 78 if (pyClass == NULL)
fazekasgy@0 79 cerr << "Warning: Syntax error in VamPy plugin: "
cannam@24 80 << classname << ". Avoiding plugin." << endl;
fazekasgy@0 81 else {
fazekasgy@0 82 pyPlugs.push_back(pluginKey);
cannam@24 83 m_pyClasses.push_back(pyClass);
fazekasgy@0 84 }
fazekasgy@0 85 //pyPlugs.push_back(pluginKey);
fazekasgy@0 86 }
fazekasgy@0 87 }
fazekasgy@0 88 }
fazekasgy@0 89
fazekasgy@0 90 return pyPlugs;
fazekasgy@0 91
fazekasgy@0 92 }
fazekasgy@0 93
fazekasgy@0 94
cannam@24 95 //For now return one class object found in each script
fazekasgy@0 96 vector<PyObject*>
cannam@24 97 PyPlugScanner::getPyClasses()
fazekasgy@0 98 {
cannam@24 99 return m_pyClasses;
fazekasgy@0 100
fazekasgy@0 101 }
fazekasgy@0 102
fazekasgy@0 103
fazekasgy@0 104 //Validate
fazekasgy@0 105 //This should not be called more than once!
fazekasgy@0 106 PyObject*
cannam@24 107 PyPlugScanner::getScriptClass(string path, string classname)
fazekasgy@0 108 {
fazekasgy@0 109
fazekasgy@0 110 //Add plugin path to active Python Path
fazekasgy@0 111 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
fazekasgy@0 112 PyRun_SimpleString(pyCmd.c_str());
fazekasgy@0 113
fazekasgy@0 114 //Assign an object to the source code
fazekasgy@0 115 PyObject *pySource = PyString_FromString(classname.c_str());
fazekasgy@0 116
fazekasgy@0 117 //Import it as a module into the py interpreter
fazekasgy@0 118 PyObject *pyModule = PyImport_Import(pySource);
fazekasgy@0 119 PyObject* pyError = PyErr_Occurred();
fazekasgy@0 120 if (! pyError == 0) {
fazekasgy@0 121 cerr << "ERROR: error importing source: " << classname << endl;
fazekasgy@0 122 PyErr_Print();
fazekasgy@0 123 Py_DECREF(pySource);
fazekasgy@0 124 Py_CLEAR(pyModule); // safer if pyModule==NULL
fazekasgy@0 125 return NULL;
fazekasgy@0 126 }
fazekasgy@0 127 Py_DECREF(pySource);
fazekasgy@0 128
fazekasgy@0 129 //Read the namespace of the module into a dictionary object (borrowed reference)
fazekasgy@0 130 PyObject *pyDict = PyModule_GetDict(pyModule);
fazekasgy@0 131 Py_DECREF(pyModule);
fazekasgy@0 132
fazekasgy@0 133 //Get the PluginClass from the module (borrowed reference)
fazekasgy@0 134 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
fazekasgy@0 135
fazekasgy@0 136 //Check if class is present and a callable method is implemented
fazekasgy@0 137 if (pyClass && PyCallable_Check(pyClass)) {
fazekasgy@0 138
cannam@24 139 return pyClass;
fazekasgy@8 140 }
fazekasgy@8 141 else {
fazekasgy@8 142 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
fazekasgy@8 143 << "Hint: plugin source filename and plugin class name must be the same." << endl;
fazekasgy@8 144 PyErr_Print();
fazekasgy@8 145 return NULL;
fazekasgy@8 146 }
fazekasgy@0 147 }
fazekasgy@0 148
fazekasgy@0 149
fazekasgy@0 150
fazekasgy@0 151 // Return a list of files in dir with given extension
fazekasgy@0 152 // Code taken from hostext/PluginLoader.cpp
fazekasgy@0 153 vector<string>
fazekasgy@0 154 PyPlugScanner::listFiles(string dir, string extension)
fazekasgy@0 155 {
fazekasgy@0 156 vector<string> files;
fazekasgy@0 157
fazekasgy@0 158 #ifdef _WIN32
fazekasgy@0 159
fazekasgy@0 160 string expression = dir + "\\*." + extension;
fazekasgy@0 161 WIN32_FIND_DATA data;
fazekasgy@0 162 HANDLE fh = FindFirstFile(expression.c_str(), &data);
fazekasgy@0 163 if (fh == INVALID_HANDLE_VALUE) return files;
fazekasgy@0 164
fazekasgy@0 165 bool ok = true;
fazekasgy@0 166 while (ok) {
fazekasgy@0 167 files.push_back(data.cFileName);
fazekasgy@0 168 ok = FindNextFile(fh, &data);
fazekasgy@0 169 }
fazekasgy@0 170
fazekasgy@0 171 FindClose(fh);
fazekasgy@0 172
fazekasgy@0 173 #else
fazekasgy@0 174
fazekasgy@0 175 size_t extlen = extension.length();
fazekasgy@0 176 DIR *d = opendir(dir.c_str());
fazekasgy@0 177 if (!d) return files;
fazekasgy@0 178
fazekasgy@0 179 struct dirent *e = 0;
fazekasgy@0 180 while ((e = readdir(d))) {
fazekasgy@0 181
fazekasgy@0 182 if (!(e->d_type & DT_REG) && (e->d_type != DT_UNKNOWN)) continue;
fazekasgy@0 183
fazekasgy@0 184 if (!e->d_name) continue;
fazekasgy@0 185
fazekasgy@0 186 size_t len = strlen(e->d_name);
fazekasgy@0 187 if (len < extlen + 2 ||
fazekasgy@0 188 e->d_name + len - extlen - 1 != "." + extension) {
fazekasgy@0 189 continue;
fazekasgy@0 190 }
fazekasgy@0 191 //cerr << "pyscripts: " << e->d_name << endl;
fazekasgy@0 192 files.push_back(e->d_name);
fazekasgy@0 193 }
fazekasgy@0 194
fazekasgy@0 195 closedir(d);
fazekasgy@0 196 #endif
fazekasgy@0 197
fazekasgy@0 198 return files;
fazekasgy@0 199 }
fazekasgy@0 200
fazekasgy@0 201 //Return correct plugin directories as per platform
fazekasgy@0 202 //Code taken from vamp-sdk/PluginHostAdapter.cpp
cannam@3 203
cannam@3 204 //!!! It would probably be better to actually call
cannam@3 205 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
cannam@3 206 // needs to link against vamp-hostsdk, but that's probably acceptable
cannam@3 207 // as it is sort of a host as well.
cannam@3 208
fazekasgy@0 209 std::vector<std::string>
fazekasgy@0 210 PyPlugScanner::getAllValidPath()
fazekasgy@0 211 {
fazekasgy@0 212 std::vector<std::string> path;
fazekasgy@0 213 std::string envPath;
fazekasgy@0 214
fazekasgy@0 215 char *cpath = getenv("VAMP_PATH");
fazekasgy@0 216 if (cpath) envPath = cpath;
fazekasgy@0 217
fazekasgy@0 218 #ifdef _WIN32
fazekasgy@0 219 #define PATH_SEPARATOR ';'
fazekasgy@0 220 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
fazekasgy@0 221 #else
fazekasgy@0 222 #define PATH_SEPARATOR ':'
fazekasgy@0 223 #ifdef __APPLE__
fazekasgy@0 224 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
fazekasgy@0 225 #else
fazekasgy@0 226 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
fazekasgy@0 227 #endif
fazekasgy@0 228 #endif
fazekasgy@0 229
fazekasgy@0 230 if (envPath == "") {
fazekasgy@0 231 envPath = DEFAULT_VAMP_PATH;
fazekasgy@0 232 char *chome = getenv("HOME");
fazekasgy@0 233 if (chome) {
fazekasgy@0 234 std::string home(chome);
fazekasgy@0 235 std::string::size_type f;
fazekasgy@0 236 while ((f = envPath.find("$HOME")) != std::string::npos &&
fazekasgy@0 237 f < envPath.length()) {
fazekasgy@0 238 envPath.replace(f, 5, home);
fazekasgy@0 239 }
fazekasgy@0 240 }
fazekasgy@0 241 #ifdef _WIN32
fazekasgy@0 242 char *cpfiles = getenv("ProgramFiles");
fazekasgy@0 243 if (!cpfiles) cpfiles = "C:\\Program Files";
fazekasgy@0 244 std::string pfiles(cpfiles);
fazekasgy@0 245 std::string::size_type f;
fazekasgy@0 246 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
fazekasgy@0 247 f < envPath.length()) {
fazekasgy@0 248 envPath.replace(f, 14, pfiles);
fazekasgy@0 249 }
fazekasgy@0 250 #endif
fazekasgy@0 251 }
fazekasgy@0 252
fazekasgy@0 253 std::string::size_type index = 0, newindex = 0;
fazekasgy@0 254
fazekasgy@0 255 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
fazekasgy@0 256 path.push_back(envPath.substr(index, newindex - index));
fazekasgy@0 257 index = newindex + 1;
fazekasgy@0 258 }
fazekasgy@0 259
fazekasgy@0 260 path.push_back(envPath.substr(index));
fazekasgy@0 261
fazekasgy@0 262 return path;
fazekasgy@0 263 }