You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ryzom-core/studio/src/extension_system/plugin_manager.cpp

452 lines
10 KiB
C++

// Object Viewer Qt - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2011 Dzmitry KAMIAHIN (dnk-88) <dnk-88@tut.by>
//
// This source file has been modified by the following contributors:
// Copyright (C) 2010 Winch Gate Property Limited
// Copyright (C) 2014 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "plugin_manager.h"
#include "plugin_spec.h"
#include <QtCore/QDir>
#include <nel/misc/debug.h>
namespace ExtensionSystem
{
PluginManager::PluginManager(QObject *parent)
:IPluginManager(parent),
m_settings(0),
m_extension("xml")
{
}
PluginManager::~PluginManager()
{
writeSettings();
stopAll();
deleteAll();
qDeleteAll(m_pluginSpecs);
}
void PluginManager::addObject(QObject *obj)
{
QWriteLocker lock(&m_lock);
if (obj == 0)
{
nlwarning("trying to add null object");
return;
}
if (m_allObjects.contains(obj))
{
nlwarning("trying to add duplicate object");
return;
}
nlinfo("addObject: %s", obj->objectName().toUtf8().constData());
m_allObjects.append(obj);
Q_EMIT objectAdded(obj);
}
void PluginManager::removeObject(QObject *obj)
{
if (obj == 0)
{
nlwarning("trying to remove null object");
return;
}
if (!m_allObjects.contains(obj))
{
nlinfo("object not in list: %s", obj->objectName().toUtf8().constData());
return;
}
nlinfo("removeObject: %s", obj->objectName().toUtf8().constData());
Q_EMIT aboutToRemoveObject(obj);
QWriteLocker lock(&m_lock);
m_allObjects.removeAll(obj);
}
QList<QObject *> PluginManager::allObjects() const
{
return m_allObjects;
}
void PluginManager::loadPlugins()
{
Q_FOREACH (PluginSpec *spec, m_pluginSpecs)
setPluginState(spec, State::Resolved);
QList<PluginSpec *> queue = loadQueue();
Q_EMIT pluginCount( queue.count() );
Q_FOREACH (PluginSpec *spec, queue)
setPluginState(spec, State::Loaded);
Q_EMIT pluginsLoaded();
Q_FOREACH (PluginSpec *spec, queue)
setPluginState(spec, State::Initialized);
Q_EMIT pluginsInitialized();
QListIterator<PluginSpec *> it(queue);
it.toBack();
while (it.hasPrevious())
setPluginState(it.previous(), State::Running);
Q_EMIT pluginsStarted();
Q_EMIT pluginsChanged();
}
bool PluginManager::loadPluginSpec( const QString &plugin )
{
nlinfo( "Loading plugin spec %s", plugin.toUtf8().data() );
PluginSpec *spec = new PluginSpec;
spec->m_pluginManager = this;
if( !spec->setSpecFileName( plugin ) )
{
nlinfo( "Error loading plugin spec %s", plugin.toUtf8().data() );
return false;
}
m_pluginSpecs.append( spec );
m_ipluginSpecs.append( spec );
return true;
}
bool PluginManager::loadPlugin( const QString &plugin )
{
if( !loadPluginSpec( plugin ) )
return false;
ExtensionSystem::PluginSpec *spec = m_pluginSpecs.last();
if( !spec->resolveDependencies( m_pluginSpecs ) )
{
nlinfo( "Error resolving dependencies for plugin spec %s", plugin.toUtf8().data() );
return false;
}
if( !spec->loadLibrary() )
{
nlinfo( "Error loading plugin %s", spec->fileName().toUtf8().data() );
return false;
}
if( !spec->initializePlugin() )
{
nlinfo( "Error initializing plugin %s", spec->fileName().toUtf8().data() );
spec->kill();
return false;
}
if( !spec->initializeExtensions() )
{
nlinfo( "Error starting plugin %s", spec->fileName().toUtf8().data() );
spec->stop();
spec->kill();
return false;
}
nlinfo( "Loaded plugin %s ( %s )", spec->name().data(), spec->fileName().toUtf8().data() );
Q_EMIT pluginsChanged();
return true;
}
bool PluginManager::unloadPlugin( ExtensionSystem::IPluginSpec *plugin )
{
ExtensionSystem::PluginSpec *spec = static_cast< ExtensionSystem::PluginSpec* >( plugin );
// Let's see if anything depends on this one
QListIterator< ExtensionSystem::PluginSpec* > itr( m_pluginSpecs );
while( itr.hasNext() )
{
ExtensionSystem::PluginSpec *sp = itr.next();
QList< ExtensionSystem::PluginSpec* > l = sp->dependencySpecs();
if( l.contains( spec ) )
return false;
}
// Stop, delete then remove plugin
spec->stop();
spec->kill();
removePlugin( plugin );
return true;
}
void PluginManager::removePlugin( ExtensionSystem::IPluginSpec *plugin )
{
QList< ExtensionSystem::IPluginSpec* >::iterator itr1;
QList< ExtensionSystem::PluginSpec* >::iterator itr2;
QList< ExtensionSystem::IPluginSpec* >::iterator i1 = m_ipluginSpecs.begin();
while( i1 != m_ipluginSpecs.end() )
{
if( *i1 == plugin )
{
itr1 = i1;
break;
}
i1++;
}
QList< ExtensionSystem::PluginSpec* >::iterator i2 = m_pluginSpecs.begin();
while( i2 != m_pluginSpecs.end() )
{
if( *i2 == static_cast< ExtensionSystem::PluginSpec* >( plugin ) )
{
itr2 = i2;
break;
}
i2++;
}
m_ipluginSpecs.erase( itr1 );
m_pluginSpecs.erase( itr2 );
delete plugin;
Q_EMIT pluginsChanged();
}
QStringList PluginManager::getPluginPaths() const
{
return m_pluginPaths;
}
void PluginManager::setPluginPaths(const QStringList &paths)
{
m_pluginPaths = paths;
readPluginPaths();
readSettings();
}
QList<IPluginSpec *> PluginManager::plugins() const
{
return m_ipluginSpecs;
}
void PluginManager::setSettings(QSettings *settings)
{
m_settings = settings;
}
QSettings *PluginManager::settings() const
{
return m_settings;
}
void PluginManager::readSettings()
{
if (m_settings)
{
QStringList blackList;
m_settings->beginGroup("PluginManager");
blackList = m_settings->value("BlackList").toStringList();
m_settings->endGroup();
Q_FOREACH (PluginSpec *spec, m_pluginSpecs)
{
QString pluginName = spec->fileName();
if (blackList.contains(pluginName))
{
spec->setEnabled(false);
spec->setEnabledStartup(false);
}
}
}
}
void PluginManager::writeSettings()
{
if (m_settings)
{
QStringList blackList;
Q_FOREACH(PluginSpec *spec, m_pluginSpecs)
{
if (!spec->isEnabled())
blackList.push_back(spec->fileName());
}
m_settings->beginGroup("PluginManager");
m_settings->setValue("BlackList", blackList);
m_settings->endGroup();
m_settings->sync();
}
}
void PluginManager::readPluginPaths()
{
qDeleteAll(m_pluginSpecs);
m_pluginSpecs.clear();
m_ipluginSpecs.clear();
QStringList pluginsList;
QStringList searchPaths = m_pluginPaths;
while (!searchPaths.isEmpty())
{
const QDir dir(searchPaths.takeFirst());
const QFileInfoList files = dir.entryInfoList(QStringList() << QString("studio_plugin_*.%1").arg(m_extension), QDir::Files);
Q_FOREACH (const QFileInfo &file, files)
pluginsList << file.absoluteFilePath();
const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
Q_FOREACH (const QFileInfo &subdir, dirs)
searchPaths << subdir.absoluteFilePath();
}
Q_FOREACH (const QString &pluginFile, pluginsList)
{
loadPluginSpec( pluginFile.toUtf8().data() );
}
Q_EMIT pluginsChanged();
}
void PluginManager::setPluginState(PluginSpec *spec, int destState)
{
if (spec->hasError() || spec->state() != destState-1)
return;
// plugin in black list
if (!spec->isEnabledStartup())
return;
switch (destState)
{
case State::Resolved:
spec->resolveDependencies(m_pluginSpecs);
return;
case State::Running:
Q_EMIT pluginStarting( spec->name().toUtf8().data() );
spec->initializeExtensions();
return;
case State::Deleted:
spec->kill();
return;
default:
break;
}
Q_FOREACH (const PluginSpec *depSpec, spec->dependencySpecs())
{
if (depSpec->state() != destState)
{
spec->m_hasError = true;
spec->m_errorString = tr("Cannot load plugin because dependency failed to load: %1")
.arg(depSpec->name());
return;
}
}
switch (destState)
{
case State::Loaded:
Q_EMIT pluginLoading( spec->name().toUtf8().data() );
spec->loadLibrary();
return;
case State::Initialized:
Q_EMIT pluginInitializing( spec->name().toUtf8().data() );
spec->initializePlugin();
break;
case State::Stopped:
spec->stop();
break;
default:
break;
}
}
QList<PluginSpec *> PluginManager::loadQueue()
{
QList<PluginSpec *> queue;
Q_FOREACH(PluginSpec *spec, m_pluginSpecs)
{
QList<PluginSpec *> circularityCheckQueue;
loadQueue(spec, queue, circularityCheckQueue);
}
return queue;
}
bool PluginManager::loadQueue(PluginSpec *spec, QList<PluginSpec *> &queue,
QList<PluginSpec *> &circularityCheckQueue)
{
if (queue.contains(spec))
return true;
// check for circular dependencies
if (circularityCheckQueue.contains(spec))
{
spec->m_hasError = true;
spec->m_errorString = tr("Circular dependency detected:\n");
int index = circularityCheckQueue.indexOf(spec);
for (int i = index; i < circularityCheckQueue.size(); ++i)
{
spec->m_errorString.append(tr("%1(%2) depends on\n")
.arg(circularityCheckQueue.at(i)->name()).arg(circularityCheckQueue.at(i)->version()));
}
spec->m_errorString.append(tr("%1(%2)").arg(spec->name()).arg(spec->version()));
return false;
}
circularityCheckQueue.append(spec);
// check if we have the dependencies
if (spec->state() == State::Invalid || spec->state() == State::Read)
{
queue.append(spec);
return false;
}
// add dependencies
Q_FOREACH (PluginSpec *depSpec, spec->dependencySpecs())
{
if (!loadQueue(depSpec, queue, circularityCheckQueue))
{
spec->m_hasError = true;
spec->m_errorString =
tr("Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3")
.arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
return false;
}
}
// add self
queue.append(spec);
return true;
}
void PluginManager::stopAll()
{
QList<PluginSpec *> queue = loadQueue();
Q_FOREACH (PluginSpec *spec, queue)
setPluginState(spec, State::Stopped);
}
void PluginManager::deleteAll()
{
QList<PluginSpec *> queue = loadQueue();
QListIterator<PluginSpec *> it(queue);
it.toBack();
while (it.hasPrevious())
{
setPluginState(it.previous(), State::Deleted);
}
}
}; // namespace ExtensionSystem