Back to index

scribus-ng  1.3.4.dfsg+svn20071115
pluginmanager.cpp
Go to the documentation of this file.
00001 /*
00002 For general Scribus (>=1.3.2) copyright and licensing information please refer
00003 to the COPYING file provided with the program. Following this notice may exist
00004 a copyright and/or license notice that predates the release of Scribus 1.3.2
00005 for which a new license (GPL+exception) is in place.
00006 */
00007 #include "pluginmanager.h"
00008 #include "pluginmanager.moc"
00009 #include "scplugin.h"
00010 #include "loadsaveplugin.h"
00011 
00012 #include <qdir.h>
00013 
00014 #include "scconfig.h"
00015 
00016 #include "scribus.h"
00017 #include "scribusdoc.h"
00018 #include "scribuscore.h"
00019 #include "menumanager.h"
00020 #include "scraction.h"
00021 #include "splash.h"
00022 //#include "mpalette.h"
00023 //#include "tree.h"
00024 #include "prefsmanager.h"
00025 #include "prefsfile.h"
00026 #include "scpaths.h"
00027 #include "commonstrings.h"
00028 
00029 #ifdef HAVE_DLFCN_H
00030 #include <dlfcn.h>
00031 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00032 #include <windows.h>
00033 #else
00034 #include <qlibrary.h>
00035 #endif
00036 
00037 
00038 extern ScribusQApp *ScQApp;
00039 
00040 PluginManager::PluginManager() :
00041        QObject(0),
00042        prefs(PrefsManager::instance()->prefsFile->getPluginContext("pluginmanager"))
00043 {
00044 }
00045 
00046 PluginManager::~PluginManager()
00047 {
00048 }
00049 
00050 void* PluginManager::loadDLL( QString plugin )
00051 {
00052        void* lib = NULL;
00053 #ifdef HAVE_DLFCN_H
00054        QString libpath = QDir::convertSeparators( plugin );
00055        lib = dlopen(libpath, RTLD_LAZY | RTLD_GLOBAL);
00056        if (!lib)
00057        {
00058               const char* error = dlerror();
00059               qDebug("%s: %s",
00060                             tr("Cannot find plugin", "plugin manager").local8Bit().data(),
00061                             error ? error : tr("unknown error","plugin manager").local8Bit().data());
00062        }
00063        dlerror();
00064 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00065        QString libpath = QDir::convertSeparators( plugin );
00066        HINSTANCE hdll = LoadLibrary( (const char*) libpath );
00067        lib = (void*) hdll;
00068 #else
00069        if( QFile::exists(plugin) )
00070               lib = (void*) new QLibrary( plugin );
00071        else
00072               qDebug("%s \"%s\"", tr("Cannot find plugin", "plugin manager").local8Bit().data(), plugin.local8Bit().data());
00073 #endif
00074        return lib;
00075 }
00076 
00077 void* PluginManager::resolveSym( void* plugin, const char* sym )
00078 {
00079        void* symAddr = NULL;
00080 #ifdef HAVE_DLFCN_H
00081        const char* error;
00082        dlerror();
00083        symAddr = dlsym( plugin, sym );
00084        if ((error = dlerror()) != NULL)
00085        {
00086               qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(error).local8Bit().data());
00087               symAddr = NULL;
00088        }
00089 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00090        symAddr = (void* ) GetProcAddress( (HMODULE) plugin, sym);
00091        if ( symAddr == NULL)
00092               qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(sym).local8Bit().data());
00093 #else
00094        QLibrary* qlib = (QLibrary*) plugin;
00095        if( plugin ) symAddr = qlib->resolve(sym);
00096        if ( symAddr == NULL)
00097               qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(sym).local8Bit().data());
00098 #endif
00099        return symAddr;
00100 }
00101 
00102 void  PluginManager::unloadDLL( void* plugin )
00103 {
00104 #ifdef HAVE_DLFCN_H
00105        dlclose( plugin );
00106        dlerror();
00107 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00108        FreeLibrary( (HMODULE) plugin );
00109 #else
00110        delete ( (QLibrary*) plugin );
00111 #endif
00112 }
00113 
00114 void PluginManager::savePreferences()
00115 {
00116        // write configuration
00117        for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00118               prefs->set(it.data().pluginName, it.data().enableOnStartup);
00119 }
00120 
00121 QCString PluginManager::getPluginName(QString fileName)
00122 {
00123        // Must return plug-in name. Note that this may be platform dependent;
00124        // it's likely to need some adjustment for platform naming schemes.
00125        // It currently handles:
00126        //    (lib)?pluginname(\.pluginext)?
00127        QFileInfo fi(fileName);
00128        QString baseName(fi.baseName());
00129        if (baseName.startsWith("lib"))
00130               baseName = baseName.remove(0,3);
00131        if (baseName.endsWith(platformDllExtension()))
00132               baseName = baseName.left(baseName.length() - 1 - platformDllExtension().length());
00133        // check name
00134        for (int i = 0; i < (int)baseName.length(); i++)
00135               if (! baseName[i].isLetterOrNumber() && baseName[i] != '_' )
00136               {
00137                      qDebug("Invalid character in plugin name for %s; skipping",
00138                                    fileName.local8Bit().data());
00139                      return QCString();
00140               }
00141        return baseName.latin1();
00142 }
00143 
00144 int PluginManager::initPlugin(const QString fileName)
00145 {
00146        PluginData pda;
00147        pda.pluginFile = QString("%1/%2").arg(ScPaths::instance().pluginDir()).arg(fileName);
00148        pda.pluginName = getPluginName(pda.pluginFile);
00149        if (pda.pluginName.isNull())
00150               // Couldn't determine plugname from filename. We've already complained, so
00151               // move on to the next one.
00152               return 0;
00153        pda.plugin = 0;
00154        pda.pluginDLL = 0;
00155        pda.enabled = false;
00156        pda.enableOnStartup = prefs->getBool(pda.pluginName, true);
00157        ScCore->setSplashStatus( tr("Plugin: loading %1", "plugin manager").arg(pda.pluginName));
00158        if (loadPlugin(pda))
00159        {
00160               if (pda.enableOnStartup)
00161                      enablePlugin(pda);
00162               pluginMap.insert(pda.pluginName, pda);
00163               return 1;
00164        }
00165        return 0;
00166 }
00167 
00168 void PluginManager::initPlugs()
00169 {
00170        Q_ASSERT(!pluginMap.count());
00171        QString libPattern = QString("*.%1*").arg(platformDllExtension());
00172        QMap<QString,int> allPlugs;
00173        uint loaded = 0;
00174        uint changes = 1;
00175        QStringList failedPlugs; // output string for warn dialog
00176 
00180        QDir dirList(ScPaths::instance().pluginDir(),
00181                              libPattern, QDir::Name | QDir::Reversed,
00182                              QDir::Files | QDir::Executable | QDir::NoSymLinks);
00183 
00184        if ((!dirList.exists()) || (dirList.count() == 0))
00185               return;
00186        for (uint dc = 0; dc < dirList.count(); ++dc)
00187        {
00188               int res = initPlugin(dirList[dc]);
00189               allPlugs[dirList[dc]] = res;
00190               if (res != 0)
00191                      ++loaded;
00192               else
00193                      failedPlugs.append(dirList[dc]);
00194        }
00195        /* Re-try the failed plugins again and again until it promote
00196        any progress (changes variable is changing ;)) */
00197        QMap<QString,int>::Iterator it;
00198        while (loaded < allPlugs.count() && changes!=0)
00199        {
00200               changes = 0;
00201               for (it = allPlugs.begin(); it != allPlugs.end(); ++it)
00202               {
00203                      if (it.data() != 0)
00204                             continue;
00205                      int res = initPlugin(it.key());
00206                      allPlugs[it.key()] = res;
00207                      if (res == 1)
00208                      {
00209                             ++loaded;
00210                             ++changes;
00211                             failedPlugs.remove(it.key());
00212                      }
00213               }
00214        }
00215        if (loaded != allPlugs.count())
00216        {
00217               if (ScCore->usingGUI())
00218               {
00219                      bool splashShown = ScCore->splashShowing();
00220                      QString failedStr("<ul>");
00221                      for (QStringList::Iterator it = failedPlugs.begin(); it != failedPlugs.end(); ++it)
00222                             failedStr += "<li>" + *it + "</li>";
00223                      failedStr += "</ul>";
00224                      if (splashShown)
00225                             ScCore->showSplash(false);
00226                      QMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning,
00227                                                          "<qt>" + tr("There is a problem loading %1 of %2 plugins. %3 This is probably caused by some kind of dependency issue or old plugins existing in your install directory. If you clean out your install directory and reinstall and this still occurs, please report it on bugs.scribus.net."
00228                                                                       ).arg(allPlugs.count()-loaded).arg(allPlugs.count()).arg(failedStr)
00229                                                                 + "</qt>",
00230                                                   CommonStrings::tr_OK);
00231                      if (splashShown)
00232                             ScCore->showSplash(true);
00233               }
00234        }
00235 }
00236 
00237 // After a plug-in has been initialized, this method calls its setup
00238 // routines and connects it to the application.
00239 void PluginManager::enablePlugin(PluginData & pda)
00240 {
00241        Q_ASSERT(pda.enabled == false);
00242        QString failReason;
00243        bool isActionPlugin=false;
00244        if (pda.plugin->inherits("ScActionPlugin"))
00245        {
00246               ScActionPlugin* plugin = dynamic_cast<ScActionPlugin*>(pda.plugin);
00247               Q_ASSERT(plugin);
00248               isActionPlugin=true;
00249               /*
00250               pda.enabled = setupPluginActions(plugin);
00251               if (!pda.enabled)
00252                      failReason = tr("init failed", "plugin load error");
00253               */
00254        }
00255        else if (pda.plugin->inherits("ScPersistentPlugin"))
00256        {
00257               ScPersistentPlugin* plugin = dynamic_cast<ScPersistentPlugin*>(pda.plugin);
00258               Q_ASSERT(plugin);
00259               pda.enabled = plugin->initPlugin();
00260               if (!pda.enabled)
00261                      failReason = tr("init failed", "plugin load error");
00262        }
00263 /* temporary hack to enable the import plugins */
00264        else if (pda.plugin->inherits("LoadSavePlugin"))
00265               pda.enabled = true;
00266        else
00267               failReason = tr("unknown plugin type", "plugin load error");
00268        if (pda.enabled || isActionPlugin)
00269               ScCore->setSplashStatus(
00270                             tr("Plugin: %1 loaded", "plugin manager")
00271                             .arg(pda.plugin->fullTrName()));
00272        else
00273               ScCore->setSplashStatus(
00274                             tr("Plugin: %1 failed to load: %2", "plugin manager")
00275                             .arg(pda.plugin->fullTrName()).arg(failReason));
00276 }
00277 
00278 bool PluginManager::setupPluginActions(ScribusMainWindow *mw)
00279 {
00280        Q_CHECK_PTR(mw);
00281        ScActionPlugin* plugin = 0;
00282 
00283        mw->scrMenuMgr->addMenuSeparator("Extras");
00284        for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00285        {
00286               if (it.data().plugin->inherits("ScActionPlugin"))
00287               {
00288                      //Add in ScrAction based plugin linkage
00289                      //Insert DLL Action into Dictionary with values from plugin interface
00290                      plugin = dynamic_cast<ScActionPlugin*>(it.data().plugin);
00291                      Q_ASSERT(plugin);
00292                      ScActionPlugin::ActionInfo ai(plugin->actionInfo());
00293                      ScrAction* action = new ScrAction(ScrAction::ActionDLL, ai.iconSet, ai.text, ai.keySequence, mw, ai.name);
00294                      Q_CHECK_PTR(action);
00295                      mw->scrActions.insert(ai.name, action);
00296 
00297                      // then enable and connect up the action
00298                      mw->scrActions[ai.name]->setEnabled(ai.enabledOnStartup);
00299                      // Connect action's activated signal with the plugin's run method
00300                      it.data().enabled = connect( mw->scrActions[ai.name], SIGNAL(activatedData(ScribusDoc*)),
00301                                                  plugin, SLOT(run(ScribusDoc*)) );
00302                      //Get the menu manager to add the DLL's menu item to the right menu, after the chosen existing item
00303                      if ( ai.menuAfterName.isEmpty() )
00304                             mw->scrMenuMgr->addMenuItem(mw->scrActions[ai.name], ai.menu);
00305                      else
00306                      {
00307                             QString actionName(ai.menu.lower()+ai.menuAfterName);
00308                             ScrAction* afterAction=0;
00309                             if (mw->scrActions.contains(actionName))
00310                                    afterAction=mw->scrActions[actionName];
00311                             mw->scrMenuMgr->addMenuItemAfter(mw->scrActions[ai.name], ai.menu, afterAction);
00312                      }
00313                      if (it.data().enabled)
00314                             ScCore->setSplashStatus( tr("Plugin: %1 initialized ok ", "plugin manager")
00315                                           .arg(plugin->fullTrName()));
00316                      else
00317                             ScCore->setSplashStatus( tr("Plugin: %1 failed post initialization", "plugin manager")
00318                                           .arg(plugin->fullTrName()));
00319               }
00320               else
00321               {
00322                      it.data().plugin->addToMainWindowMenu(mw);
00323               }
00324 
00325        }
00326        return true;
00327 }
00328 
00329 bool PluginManager::DLLexists(QCString name, bool includeDisabled) const
00330 {
00331        // the plugin name must be known
00332        if (pluginMap.contains(name))
00333        {
00334               // the plugin must be loaded
00335               if (pluginMap[name].plugin)
00336               {
00337                      // and the plugin must be enabled unless we were told otherwise
00338                      if (pluginMap[name].enabled)
00339                             return true;
00340                      else
00341                             return includeDisabled;
00342               }
00343        }
00344        return false;
00345 }
00346 
00347 bool PluginManager::loadPlugin(PluginData & pda)
00348 {
00349        typedef int (*getPluginAPIVersionPtr)();
00350        typedef ScPlugin* (*getPluginPtr)();
00351        getPluginAPIVersionPtr getPluginAPIVersion;
00352        getPluginPtr getPlugin;
00353 
00354        Q_ASSERT(pda.plugin == 0);
00355        Q_ASSERT(pda.pluginDLL == 0);
00356        Q_ASSERT(!pda.enabled);
00357        pda.plugin = 0;
00358 
00359        pda.pluginDLL = loadDLL(pda.pluginFile);
00360        if (!pda.pluginDLL)
00361               return false;
00362 
00363        getPluginAPIVersion = (getPluginAPIVersionPtr)
00364               resolveSym(pda.pluginDLL, pda.pluginName + "_getPluginAPIVersion");
00365        if (getPluginAPIVersion)
00366        {
00367               int gotVersion = (*getPluginAPIVersion)();
00368               if ( gotVersion != PLUGIN_API_VERSION )
00369               {
00370                      qDebug("API version mismatch when loading %s: Got %i, expected %i",
00371                                    pda.pluginFile.local8Bit().data(), gotVersion, PLUGIN_API_VERSION);
00372               }
00373               else
00374               {
00375                      getPlugin = (getPluginPtr)
00376                             resolveSym(pda.pluginDLL, pda.pluginName + "_getPlugin");
00377                      if (getPlugin)
00378                      {
00379                             pda.plugin = (*getPlugin)();
00380                             if (!pda.plugin)
00381                             {
00382                                    qDebug("Unable to get ScPlugin when loading %s",
00383                                                  pda.pluginFile.local8Bit().data());
00384                             }
00385                             else
00386                                    return true;
00387                      }
00388               }
00389        }
00390        unloadDLL(pda.pluginDLL);
00391        pda.pluginDLL = 0;
00392        Q_ASSERT(!pda.plugin);
00393        return false;
00394 }
00395 
00396 void PluginManager::cleanupPlugins()
00397 {
00398        for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00399               if (it.data().enabled == true)
00400                      finalizePlug(it.data());
00401 }
00402 
00403 void PluginManager::finalizePlug(PluginData & pda)
00404 {
00405        typedef void (*freePluginPtr)(ScPlugin* plugin);
00406        if (pda.plugin)
00407        {
00408               if (pda.enabled)
00409                      disablePlugin(pda);
00410               Q_ASSERT(!pda.enabled);
00411               freePluginPtr freePlugin =
00412                      (freePluginPtr) resolveSym(pda.pluginDLL, pda.pluginName + "_freePlugin");
00413               if ( freePlugin )
00414                      (*freePlugin)( pda.plugin );
00415               pda.plugin = 0;
00416        }
00417        Q_ASSERT(!pda.enabled);
00418        if (pda.pluginDLL)
00419        {
00420               unloadDLL(pda.pluginDLL);
00421               pda.pluginDLL = 0;
00422        }
00423 }
00424 
00425 void PluginManager::disablePlugin(PluginData & pda)
00426 {
00427        Q_ASSERT(pda.enabled);
00428        Q_ASSERT(pda.plugin);
00429        if (pda.plugin->inherits("ScActionPlugin"))
00430        {
00431               ScActionPlugin* plugin = dynamic_cast<ScActionPlugin*>(pda.plugin);
00432               Q_ASSERT(plugin);
00433               // FIXME: Correct way to delete action?
00434               delete ScCore->primaryMainWindow()->scrActions[plugin->actionInfo().name];
00435        }
00436        else if (pda.plugin->inherits("ScPersistentPlugin"))
00437        {
00438               ScPersistentPlugin* plugin = dynamic_cast<ScPersistentPlugin*>(pda.plugin);
00439               Q_ASSERT(plugin);
00440               plugin->cleanupPlugin();
00441        }
00442 /* temporary hack to enable the import plugins */
00443        else if (pda.plugin->inherits("LoadSavePlugin"))
00444               pda.enabled = false;
00445        else
00446               Q_ASSERT(false); // We shouldn't ever have enabled an unknown plugin type.
00447        pda.enabled = false;
00448 }
00449 
00450 QCString PluginManager::platformDllExtension()
00451 {
00452 #ifdef __hpux
00453        // HP/UX
00454        return "sl";
00455 #elif defined(__APPLE__) && defined(__MACH__)
00456        // MacOS/X, Darwin
00457 
00458        // MacOS/X may actually use both 'so' and 'dylib'. .so is usually used
00459        // for plugins etc, dylib for system and app libraries We need to
00460        // support this distinction in the plugin manager, but for now it's
00461        // most appropriate to return the extension used by plugins -- CR
00462 
00463        //return "dylib";
00464        return "so";
00465 #elif defined(_WIN32) || defined(_WIN64)
00466        return "dll";
00467 #else
00468        // Generic *NIX
00469        return "so";
00470 #endif
00471 }
00472 
00473 void PluginManager::languageChange()
00474 {
00475        ScPlugin* plugin = 0;
00476        ScActionPlugin* ixplug = 0;
00477        ScrAction* pluginAction = 0;
00478        for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00479        {
00480               plugin = it.data().plugin;
00481               if (plugin)
00482               {
00483                      plugin->languageChange();
00484                      ixplug = dynamic_cast<ScActionPlugin*>(plugin);
00485                      if (ixplug)
00486                      {
00487                             ScActionPlugin::ActionInfo ai(ixplug->actionInfo());
00488                             pluginAction = ScCore->primaryMainWindow()->scrActions[ai.name];
00489                             if (pluginAction != 0)
00490                                    pluginAction->setMenuText( ai.text );
00491                      }
00492               }
00493        }
00494 }
00495 
00496 ScPlugin* PluginManager::getPlugin(const QCString & pluginName, bool includeDisabled) const
00497 {
00498        if (DLLexists(pluginName, includeDisabled))
00499               return pluginMap[pluginName].plugin;
00500        return 0;
00501 }
00502 
00503 PluginManager & PluginManager::instance()
00504 {
00505        return (*ScCore->pluginManager);
00506 }
00507 
00508 const QString & PluginManager::getPluginPath(const QCString pluginName) const
00509 {
00510        // It is not legal to call this function without a valid
00511        // plug in name.
00512        Q_ASSERT(pluginMap.contains(pluginName));
00513        return pluginMap[pluginName].pluginFile;
00514 }
00515 
00516 bool & PluginManager::enableOnStartup(const QCString pluginName)
00517 {
00518        // It is not legal to call this function without a valid
00519        // plug in name.
00520        Q_ASSERT(pluginMap.contains(pluginName));
00521        return pluginMap[pluginName].enableOnStartup;
00522 }
00523 
00524 bool PluginManager::enabled(const QCString pluginName)
00525 {
00526        // It is not legal to call this function without a valid
00527        // plug in name.
00528        Q_ASSERT(pluginMap.contains(pluginName));
00529        return pluginMap[pluginName].enabled;
00530 }
00531 
00532 QValueList<QCString> PluginManager::pluginNames(
00533               bool includeDisabled, const char* inherits) const
00534 {
00535        // Scan the plugin map for plugins...
00536        QValueList<QCString> names;
00537        for (PluginMap::ConstIterator it = pluginMap.constBegin(); it != pluginMap.constEnd(); ++it)
00538               if (includeDisabled || it.data().enabled)
00539                      // Only including plugins that inherit a named parent (if
00540                      // specified), using the QMetaObject system.
00541                      if (!inherits || it.data().plugin->inherits(inherits))
00542                             names.append(it.data().pluginName);
00543        return names;
00544 }