annotate plugin/NativeVampPluginFactory.cpp @ 1249:d45a16c232bd piper

Align Sonic Annotator with the new Piper-ified subrepos (bearing in mind we want neither Piper nor the plugin load checker in Sonic Annotator itself)
author Chris Cannam
date Fri, 04 Nov 2016 14:16:01 +0000
parents 8f076d02569a
children 48e9f538e6e9
rev   line source
Chris@49 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@52 4 Sonic Visualiser
Chris@52 5 An audio file viewer and annotation editor.
Chris@52 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1225 7 This file copyright 2006-2016 Chris Cannam and QMUL.
Chris@0 8
Chris@52 9 This program is free software; you can redistribute it and/or
Chris@52 10 modify it under the terms of the GNU General Public License as
Chris@52 11 published by the Free Software Foundation; either version 2 of the
Chris@52 12 License, or (at your option) any later version. See the file
Chris@52 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@1225 16 #include "NativeVampPluginFactory.h"
Chris@0 17 #include "PluginIdentifier.h"
Chris@0 18
Chris@1225 19 #include <vamp-hostsdk/PluginHostAdapter.h>
Chris@1225 20 #include <vamp-hostsdk/PluginWrapper.h>
Chris@1225 21
Chris@150 22 #include "system/System.h"
Chris@66 23
Chris@1179 24 #include "PluginScan.h"
Chris@1179 25
Chris@66 26 #include <QDir>
Chris@66 27 #include <QFile>
Chris@66 28 #include <QFileInfo>
Chris@165 29 #include <QTextStream>
Chris@66 30
Chris@0 31 #include <iostream>
Chris@0 32
Chris@408 33 #include "base/Profiler.h"
Chris@408 34
Chris@1225 35 #include <QMutex>
Chris@1225 36 #include <QMutexLocker>
Chris@1223 37
Chris@1164 38 using namespace std;
Chris@1164 39
Chris@249 40 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
Chris@249 41
Chris@1225 42 class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
Chris@1225 43 public:
Chris@1225 44 PluginDeletionNotifyAdapter(Vamp::Plugin *plugin,
Chris@1225 45 NativeVampPluginFactory *factory) :
Chris@1225 46 PluginWrapper(plugin), m_factory(factory) { }
Chris@1225 47 virtual ~PluginDeletionNotifyAdapter();
Chris@1225 48 protected:
Chris@1225 49 NativeVampPluginFactory *m_factory;
Chris@1225 50 };
Chris@0 51
Chris@1225 52 PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
Chris@0 53 {
Chris@1225 54 // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn
Chris@1225 55 Vamp::Plugin *p = m_plugin;
Chris@1225 56 delete m_plugin;
Chris@1225 57 m_plugin = 0;
Chris@1225 58 // acceptable use after free here, as pluginDeleted uses p only as
Chris@1225 59 // pointer key and does not deref it
Chris@1225 60 if (m_factory) m_factory->pluginDeleted(p);
Chris@66 61 }
Chris@66 62
Chris@1164 63 vector<QString>
Chris@1225 64 NativeVampPluginFactory::getPluginPath()
Chris@0 65 {
Chris@1225 66 if (!m_pluginPath.empty()) return m_pluginPath;
Chris@1225 67
Chris@1225 68 vector<string> p = Vamp::PluginHostAdapter::getPluginPath();
Chris@1225 69 for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str());
Chris@1225 70 return m_pluginPath;
Chris@1225 71 }
Chris@1225 72
Chris@1249 73 static
Chris@1249 74 QList<PluginScan::Candidate>
Chris@1249 75 getCandidateLibraries()
Chris@1249 76 {
Chris@1249 77 #ifdef HAVE_PLUGIN_CHECKER_HELPER
Chris@1249 78 return PluginScan::getInstance()->getCandidateLibrariesFor
Chris@1249 79 (PluginScan::VampPlugin);
Chris@1249 80 #else
Chris@1249 81 auto path = Vamp::PluginHostAdapter::getPluginPath();
Chris@1249 82 QList<PluginScan::Candidate> candidates;
Chris@1249 83 for (string dirname: path) {
Chris@1249 84 SVDEBUG << "NativeVampPluginFactory: scanning directory myself: "
Chris@1249 85 << dirname << endl;
Chris@1249 86 #if defined(_WIN32)
Chris@1249 87 #define PLUGIN_GLOB "*.dll"
Chris@1249 88 #elif defined(__APPLE__)
Chris@1249 89 #define PLUGIN_GLOB "*.dylib *.so"
Chris@1249 90 #else
Chris@1249 91 #define PLUGIN_GLOB "*.so"
Chris@1249 92 #endif
Chris@1249 93 QDir dir(dirname.c_str(), PLUGIN_GLOB,
Chris@1249 94 QDir::Name | QDir::IgnoreCase,
Chris@1249 95 QDir::Files | QDir::Readable);
Chris@1249 96
Chris@1249 97 for (unsigned int i = 0; i < dir.count(); ++i) {
Chris@1249 98 QString soname = dir.filePath(dir[i]);
Chris@1249 99 candidates.push_back({ soname, "" });
Chris@1249 100 }
Chris@1249 101 }
Chris@1249 102
Chris@1249 103 return candidates;
Chris@1249 104 #endif
Chris@1249 105 }
Chris@1249 106
Chris@1225 107 vector<QString>
Chris@1227 108 NativeVampPluginFactory::getPluginIdentifiers(QString &)
Chris@1225 109 {
Chris@1225 110 Profiler profiler("NativeVampPluginFactory::getPluginIdentifiers");
Chris@1225 111
Chris@1225 112 QMutexLocker locker(&m_mutex);
Chris@1225 113
Chris@1225 114 if (!m_identifiers.empty()) {
Chris@1225 115 return m_identifiers;
Chris@1225 116 }
Chris@1249 117
Chris@1249 118 auto candidates = getCandidateLibraries();
Chris@0 119
Chris@1249 120 SVDEBUG << "INFO: Have " << candidates.size() << " candidate Vamp plugin libraries" << endl;
Chris@1249 121
Chris@1246 122 for (auto candidate : candidates) {
Chris@1246 123
Chris@1246 124 QString soname = candidate.libraryPath;
Chris@1225 125
Chris@1249 126 SVDEBUG << "INFO: Considering candidate Vamp plugin library " << soname << endl;
Chris@1249 127
Chris@1225 128 void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
Chris@1225 129
Chris@1225 130 if (!libraryHandle) {
Chris@1247 131 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
Chris@1225 132 continue;
Chris@1225 133 }
Chris@1225 134
Chris@1225 135 VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
Chris@1225 136 DLSYM(libraryHandle, "vampGetPluginDescriptor");
Chris@1225 137
Chris@1225 138 if (!fn) {
Chris@1247 139 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
Chris@1225 140 if (DLCLOSE(libraryHandle) != 0) {
Chris@1247 141 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
Chris@1225 142 }
Chris@1225 143 continue;
Chris@1225 144 }
Chris@1225 145
Chris@1225 146 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 147 cerr << "NativeVampPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
Chris@1225 148 #endif
Chris@1225 149
Chris@1225 150 const VampPluginDescriptor *descriptor = 0;
Chris@1225 151 int index = 0;
Chris@1225 152
Chris@1225 153 map<string, int> known;
Chris@1225 154 bool ok = true;
Chris@1225 155
Chris@1225 156 while ((descriptor = fn(VAMP_API_VERSION, index))) {
Chris@1225 157
Chris@1225 158 if (known.find(descriptor->identifier) != known.end()) {
Chris@1247 159 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
Chris@1225 160 << soname
Chris@1225 161 << " returns the same plugin identifier \""
Chris@1225 162 << descriptor->identifier << "\" at indices "
Chris@1225 163 << known[descriptor->identifier] << " and "
Chris@1225 164 << index << endl;
Chris@1247 165 SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
Chris@1225 166 ok = false;
Chris@1225 167 break;
Chris@1225 168 } else {
Chris@1225 169 known[descriptor->identifier] = index;
Chris@1225 170 }
Chris@1225 171
Chris@1225 172 ++index;
Chris@1225 173 }
Chris@1225 174
Chris@1225 175 if (ok) {
Chris@1225 176
Chris@1225 177 index = 0;
Chris@1225 178
Chris@1225 179 while ((descriptor = fn(VAMP_API_VERSION, index))) {
Chris@1225 180
Chris@1225 181 QString id = PluginIdentifier::createIdentifier
Chris@1225 182 ("vamp", soname, descriptor->identifier);
Chris@1225 183 m_identifiers.push_back(id);
Chris@1225 184 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 185 cerr << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
Chris@1225 186 #endif
Chris@1225 187 ++index;
Chris@1225 188 }
Chris@1225 189 }
Chris@1225 190
Chris@1225 191 if (DLCLOSE(libraryHandle) != 0) {
Chris@1247 192 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
Chris@1225 193 }
Chris@0 194 }
Chris@0 195
Chris@1225 196 generateTaxonomy();
Chris@1225 197
Chris@0 198 // Plugins can change the locale, revert it to default.
Chris@608 199 RestoreStartupLocale();
Chris@608 200
Chris@1225 201 return m_identifiers;
Chris@1225 202 }
Chris@1225 203
Chris@1225 204 QString
Chris@1225 205 NativeVampPluginFactory::findPluginFile(QString soname, QString inDir)
Chris@1225 206 {
Chris@1225 207 QString file = "";
Chris@1225 208
Chris@1225 209 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 210 cerr << "NativeVampPluginFactory::findPluginFile(\""
Chris@1225 211 << soname << "\", \"" << inDir << "\")"
Chris@1225 212 << endl;
Chris@1225 213 #endif
Chris@1225 214
Chris@1225 215 if (inDir != "") {
Chris@1225 216
Chris@1225 217 QDir dir(inDir, PLUGIN_GLOB,
Chris@1225 218 QDir::Name | QDir::IgnoreCase,
Chris@1225 219 QDir::Files | QDir::Readable);
Chris@1225 220 if (!dir.exists()) return "";
Chris@1225 221
Chris@1225 222 file = dir.filePath(QFileInfo(soname).fileName());
Chris@1225 223
Chris@1225 224 if (QFileInfo(file).exists() && QFileInfo(file).isFile()) {
Chris@1225 225
Chris@1225 226 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 227 cerr << "NativeVampPluginFactory::findPluginFile: "
Chris@1225 228 << "found trivially at " << file << endl;
Chris@1225 229 #endif
Chris@1225 230
Chris@1225 231 return file;
Chris@1225 232 }
Chris@1225 233
Chris@1225 234 for (unsigned int j = 0; j < dir.count(); ++j) {
Chris@1225 235 file = dir.filePath(dir[j]);
Chris@1225 236 if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) {
Chris@1225 237
Chris@1225 238 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 239 cerr << "NativeVampPluginFactory::findPluginFile: "
Chris@1225 240 << "found \"" << soname << "\" at " << file << endl;
Chris@1225 241 #endif
Chris@1225 242
Chris@1225 243 return file;
Chris@1225 244 }
Chris@1225 245 }
Chris@1225 246
Chris@1225 247 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 248 cerr << "NativeVampPluginFactory::findPluginFile (with dir): "
Chris@1225 249 << "not found" << endl;
Chris@1225 250 #endif
Chris@1225 251
Chris@1225 252 return "";
Chris@1225 253
Chris@1225 254 } else {
Chris@1225 255
Chris@1225 256 QFileInfo fi(soname);
Chris@1225 257
Chris@1225 258 if (fi.isAbsolute() && fi.exists() && fi.isFile()) {
Chris@1225 259 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 260 cerr << "NativeVampPluginFactory::findPluginFile: "
Chris@1225 261 << "found trivially at " << soname << endl;
Chris@1225 262 #endif
Chris@1225 263 return soname;
Chris@1225 264 }
Chris@1225 265
Chris@1225 266 if (fi.isAbsolute() && fi.absolutePath() != "") {
Chris@1225 267 file = findPluginFile(soname, fi.absolutePath());
Chris@1225 268 if (file != "") return file;
Chris@1225 269 }
Chris@1225 270
Chris@1225 271 vector<QString> path = getPluginPath();
Chris@1225 272 for (vector<QString>::iterator i = path.begin();
Chris@1225 273 i != path.end(); ++i) {
Chris@1225 274 if (*i != "") {
Chris@1225 275 file = findPluginFile(soname, *i);
Chris@1225 276 if (file != "") return file;
Chris@1225 277 }
Chris@1225 278 }
Chris@1225 279
Chris@1225 280 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 281 cerr << "NativeVampPluginFactory::findPluginFile: "
Chris@1225 282 << "not found" << endl;
Chris@1225 283 #endif
Chris@1225 284
Chris@1225 285 return "";
Chris@1225 286 }
Chris@1225 287 }
Chris@1225 288
Chris@1225 289 Vamp::Plugin *
Chris@1225 290 NativeVampPluginFactory::instantiatePlugin(QString identifier,
Chris@1225 291 sv_samplerate_t inputSampleRate)
Chris@1225 292 {
Chris@1225 293 Profiler profiler("NativeVampPluginFactory::instantiatePlugin");
Chris@1225 294
Chris@1225 295 Vamp::Plugin *rv = 0;
Chris@1225 296 Vamp::PluginHostAdapter *plugin = 0;
Chris@1225 297
Chris@1225 298 const VampPluginDescriptor *descriptor = 0;
Chris@1225 299 int index = 0;
Chris@1225 300
Chris@1225 301 QString type, soname, label;
Chris@1225 302 PluginIdentifier::parseIdentifier(identifier, type, soname, label);
Chris@1225 303 if (type != "vamp") {
Chris@1225 304 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 305 cerr << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
Chris@1225 306 #endif
Chris@1225 307 return 0;
Chris@1225 308 }
Chris@1225 309
Chris@1225 310 QString found = findPluginFile(soname);
Chris@1225 311
Chris@1225 312 if (found == "") {
Chris@1247 313 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
Chris@1225 314 return 0;
Chris@1225 315 } else if (found != soname) {
Chris@1225 316
Chris@1225 317 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 318 cerr << "NativeVampPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
Chris@1225 319 cerr << soname << " -> " << found << endl;
Chris@1225 320 #endif
Chris@1225 321
Chris@1225 322 }
Chris@1225 323
Chris@1225 324 soname = found;
Chris@1225 325
Chris@1225 326 void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
Chris@1225 327
Chris@1225 328 if (!libraryHandle) {
Chris@1247 329 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
Chris@1225 330 return 0;
Chris@1225 331 }
Chris@1225 332
Chris@1225 333 VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
Chris@1225 334 DLSYM(libraryHandle, "vampGetPluginDescriptor");
Chris@1225 335
Chris@1225 336 if (!fn) {
Chris@1247 337 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
Chris@1225 338 goto done;
Chris@1225 339 }
Chris@1225 340
Chris@1225 341 while ((descriptor = fn(VAMP_API_VERSION, index))) {
Chris@1225 342 if (label == descriptor->identifier) break;
Chris@1225 343 ++index;
Chris@1225 344 }
Chris@1225 345
Chris@1225 346 if (!descriptor) {
Chris@1247 347 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl;
Chris@1225 348 goto done;
Chris@1225 349 }
Chris@1225 350
Chris@1225 351 plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate));
Chris@1225 352
Chris@1225 353 if (plugin) {
Chris@1225 354 m_handleMap[plugin] = libraryHandle;
Chris@1225 355 rv = new PluginDeletionNotifyAdapter(plugin, this);
Chris@1225 356 }
Chris@1225 357
Chris@1225 358 // SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << endl;
Chris@1225 359
Chris@1225 360 //!!! need to dlclose() when plugins from a given library are unloaded
Chris@1225 361
Chris@1225 362 done:
Chris@1225 363 if (!rv) {
Chris@1225 364 if (DLCLOSE(libraryHandle) != 0) {
Chris@1247 365 SVDEBUG << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
Chris@1225 366 }
Chris@1225 367 }
Chris@1225 368
Chris@1225 369 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 370 cerr << "NativeVampPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
Chris@1225 371 #endif
Chris@1225 372
Chris@0 373 return rv;
Chris@0 374 }
Chris@0 375
Chris@1225 376 void
Chris@1225 377 NativeVampPluginFactory::pluginDeleted(Vamp::Plugin *plugin)
Chris@0 378 {
Chris@1225 379 void *handle = m_handleMap[plugin];
Chris@1225 380 if (handle) {
Chris@1225 381 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1225 382 cerr << "unloading library " << handle << " for plugin " << plugin << endl;
Chris@1225 383 #endif
Chris@1225 384 DLCLOSE(handle);
Chris@1225 385 }
Chris@1225 386 m_handleMap.erase(plugin);
Chris@1225 387 }
Chris@408 388
Chris@1225 389 QString
Chris@1225 390 NativeVampPluginFactory::getPluginCategory(QString identifier)
Chris@1225 391 {
Chris@1225 392 return m_taxonomy[identifier];
Chris@1225 393 }
Chris@1225 394
Chris@1225 395 void
Chris@1225 396 NativeVampPluginFactory::generateTaxonomy()
Chris@1225 397 {
Chris@1225 398 vector<QString> pluginPath = getPluginPath();
Chris@1225 399 vector<QString> path;
Chris@1225 400
Chris@1225 401 for (size_t i = 0; i < pluginPath.size(); ++i) {
Chris@1225 402 if (pluginPath[i].contains("/lib/")) {
Chris@1225 403 QString p(pluginPath[i]);
Chris@1225 404 path.push_back(p);
Chris@1225 405 p.replace("/lib/", "/share/");
Chris@1225 406 path.push_back(p);
Chris@1225 407 }
Chris@1225 408 path.push_back(pluginPath[i]);
Chris@1225 409 }
Chris@1225 410
Chris@1225 411 for (size_t i = 0; i < path.size(); ++i) {
Chris@1225 412
Chris@1225 413 QDir dir(path[i], "*.cat");
Chris@1225 414
Chris@1225 415 // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
Chris@1225 416 for (unsigned int j = 0; j < dir.count(); ++j) {
Chris@1225 417
Chris@1225 418 QFile file(path[i] + "/" + dir[j]);
Chris@1225 419
Chris@1225 420 // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
Chris@1225 421
Chris@1225 422 if (file.open(QIODevice::ReadOnly)) {
Chris@1225 423 // cerr << "...opened" << endl;
Chris@1225 424 QTextStream stream(&file);
Chris@1225 425 QString line;
Chris@1225 426
Chris@1225 427 while (!stream.atEnd()) {
Chris@1225 428 line = stream.readLine();
Chris@1225 429 // cerr << "line is: \"" << line << "\"" << endl;
Chris@1225 430 QString id = PluginIdentifier::canonicalise
Chris@1225 431 (line.section("::", 0, 0));
Chris@1225 432 QString cat = line.section("::", 1, 1);
Chris@1225 433 m_taxonomy[id] = cat;
Chris@1225 434 // cerr << "NativeVampPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl;
Chris@1225 435 }
Chris@1225 436 }
Chris@1225 437 }
Chris@1225 438 }
Chris@1225 439 }
Chris@1225 440
Chris@1225 441 piper_vamp::PluginStaticData
Chris@1225 442 NativeVampPluginFactory::getPluginStaticData(QString identifier)
Chris@1225 443 {
Chris@1209 444 QMutexLocker locker(&m_mutex);
Chris@1209 445
Chris@1225 446 if (m_pluginData.find(identifier) != m_pluginData.end()) {
Chris@1225 447 return m_pluginData[identifier];
Chris@1209 448 }
Chris@1210 449
Chris@66 450 QString type, soname, label;
Chris@66 451 PluginIdentifier::parseIdentifier(identifier, type, soname, label);
Chris@1210 452 std::string pluginKey = (soname + ":" + label).toStdString();
Chris@0 453
Chris@1225 454 std::vector<std::string> catlist;
Chris@1225 455 for (auto s: getPluginCategory(identifier).split(" > ")) {
Chris@1225 456 catlist.push_back(s.toStdString());
Chris@1225 457 }
Chris@1225 458
Chris@1225 459 Vamp::Plugin *p = instantiatePlugin(identifier, 44100);
Chris@1225 460 if (!p) return {};
Chris@66 461
Chris@1225 462 auto psd = piper_vamp::PluginStaticData::fromPlugin(pluginKey,
Chris@1225 463 catlist,
Chris@1225 464 p);
Chris@1225 465
Chris@1225 466 delete p;
Chris@1225 467
Chris@1225 468 m_pluginData[identifier] = psd;
Chris@1225 469 return psd;
Chris@298 470 }
Chris@298 471