annotate PyPlugScanner.cpp @ 26:ba3686eb697c

examples now pass all tests
author fazekasgy
date Wed, 19 Aug 2009 15:21:17 +0000
parents 7648f3f2fa14
children e80caada79a8
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@25 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 }