summaryrefslogtreecommitdiff
path: root/src/plugincontainer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugincontainer.cpp')
-rw-r--r--src/plugincontainer.cpp426
1 files changed, 306 insertions, 120 deletions
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index 5a485dfe..c703a36a 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -106,7 +106,7 @@ const std::set<QString> PluginRequirements::s_CorePlugins{
};
PluginRequirements::PluginRequirements(
- PluginContainer* pluginContainer, MOBase::IPlugin* plugin, IOrganizer* proxy,
+ PluginContainer* pluginContainer, MOBase::IPlugin* plugin, OrganizerProxy* proxy,
MOBase::IPluginProxy* pluginProxy)
: m_PluginContainer(pluginContainer)
, m_Plugin(plugin)
@@ -280,7 +280,7 @@ void PluginRequirements::requiredFor(std::vector<MOBase::IPlugin*> &required, st
PluginContainer::PluginContainer(OrganizerCore *organizer)
: m_Organizer(organizer)
, m_UserInterface(nullptr)
- , m_PreviewGenerator(this)
+ , m_PreviewGenerator(*this)
{
}
@@ -289,21 +289,10 @@ PluginContainer::~PluginContainer() {
unloadPlugins();
}
-void PluginContainer::setUserInterface(IUserInterface *userInterface)
+void PluginContainer::startPlugins(IUserInterface *userInterface)
{
- if (userInterface) {
- for (IPluginProxy* proxy : bf::at_key<IPluginProxy>(m_Plugins)) {
- proxy->setParentWidget(userInterface->mainWindow());
- }
- for (IPluginModPage* modPage : bf::at_key<IPluginModPage>(m_Plugins)) {
- modPage->setParentWidget(userInterface->mainWindow());
- }
- for (IPluginTool* tool : bf::at_key<IPluginTool>(m_Plugins)) {
- tool->setParentWidget(userInterface->mainWindow());
- }
- }
-
m_UserInterface = userInterface;
+ startPluginsImpl(plugins<QObject>());
}
QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const
@@ -408,6 +397,7 @@ bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy, boo
OrganizerProxy* proxy = nullptr;
if (m_Organizer) {
proxy = new OrganizerProxy(m_Organizer, this, plugin);
+ proxy->setParent(as_qobject(plugin));
}
// Check if it is a proxy plugin:
@@ -439,7 +429,12 @@ void PluginContainer::registerGame(IPluginGame *game)
m_SupportedGames.insert({ game->gameName(), game });
}
-IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileName, MOBase::IPluginProxy* pluginProxy)
+void PluginContainer::unregisterGame(MOBase::IPluginGame* game)
+{
+ m_SupportedGames.erase(game->gameName());
+}
+
+IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString& filepath, MOBase::IPluginProxy* pluginProxy)
{
// generic treatment for all plugins
@@ -459,7 +454,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
// If both plugins are from the same proxy and the same file, this is usually
// ok (in theory some one could write two different classes from the same Python file/module):
if (pluginProxy && m_Requirements.at(other).proxy() == pluginProxy
- && as_qobject(other)->property("filepath") == fileName) {
+ && this->filepath(other) == QDir::cleanPath(filepath)) {
// Plugin has already been initialized:
skipInit = true;
@@ -484,7 +479,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
// way to cast directly between IPlugin* and IPluginDiagnose*
bf::at_key<QObject>(m_Plugins).push_back(plugin);
- plugin->setProperty("filepath", fileName);
+ plugin->setProperty("filepath", QDir::cleanPath(filepath));
if (m_Organizer) {
m_Organizer->settings().plugins().registerPlugin(pluginObj);
@@ -496,8 +491,8 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
bf::at_key<IPluginDiagnose>(m_Plugins).push_back(diagnose);
bf::at_key<IPluginDiagnose>(m_AccessPlugins)[diagnose] = pluginObj;
m_DiagnosisConnections.push_back(
- diagnose->onInvalidated([&] () { emit diagnosisUpdate(); })
- );
+ diagnose->onInvalidated([&] () { emit diagnosisUpdate(); })
+ );
}
}
{ // file mapper plugin
@@ -511,6 +506,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
IPluginModPage *modPage = qobject_cast<IPluginModPage*>(plugin);
if (initPlugin(modPage, pluginProxy, skipInit)) {
bf::at_key<IPluginModPage>(m_Plugins).push_back(modPage);
+ emit pluginRegistered(modPage);
return modPage;
}
}
@@ -521,6 +517,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
if (initPlugin(game, pluginProxy, skipInit)) {
bf::at_key<IPluginGame>(m_Plugins).push_back(game);
registerGame(game);
+ emit pluginRegistered(game);
return game;
}
}
@@ -529,6 +526,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
IPluginTool *tool = qobject_cast<IPluginTool*>(plugin);
if (initPlugin(tool, pluginProxy, skipInit)) {
bf::at_key<IPluginTool>(m_Plugins).push_back(tool);
+ emit pluginRegistered(tool);
return tool;
}
}
@@ -537,8 +535,9 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
if (initPlugin(installer, pluginProxy, skipInit)) {
bf::at_key<IPluginInstaller>(m_Plugins).push_back(installer);
if (m_Organizer) {
- m_Organizer->installationManager()->registerInstaller(installer);
+ installer->setInstallationManager(m_Organizer->installationManager());
}
+ emit pluginRegistered(installer);
return installer;
}
}
@@ -546,7 +545,6 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
IPluginPreview *preview = qobject_cast<IPluginPreview*>(plugin);
if (initPlugin(preview, pluginProxy, skipInit)) {
bf::at_key<IPluginPreview>(m_Plugins).push_back(preview);
- m_PreviewGenerator.registerPlugin(preview);
return preview;
}
}
@@ -554,55 +552,12 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
IPluginProxy *proxy = qobject_cast<IPluginProxy*>(plugin);
if (initPlugin(proxy, pluginProxy, skipInit)) {
bf::at_key<IPluginProxy>(m_Plugins).push_back(proxy);
- QStringList pluginNames = proxy->pluginList(
- QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()));
- for (const QString &pluginName : pluginNames) {
- try {
- // We get a list of matching plugins as proxies can return multiple plugins
- // per file and do not have a good way of supporting multiple inheritance.
- QList<QObject*> matchingPlugins = proxy->load(pluginName);
-
- // We are going to group plugin by names and "fix" them later:
- std::map<QString, std::vector<IPlugin*>> proxiedByNames;
-
- for (QObject *proxiedPlugin : matchingPlugins) {
- if (proxiedPlugin != nullptr) {
-
- if (IPlugin* proxied = registerPlugin(proxiedPlugin, pluginName, proxy); proxied) {
- log::debug("loaded plugin '{}' from '{}' - [{}]",
- proxied->name(), QFileInfo(pluginName).fileName(), implementedInterfaces(proxied).join(", "));
+ emit pluginRegistered(proxy);
- // Store the plugin for later:
- proxiedByNames[proxied->name()].push_back(proxied);
- }
- else {
- log::warn(
- "plugin \"{}\" failed to load. If this plugin is for an older version of MO "
- "you have to update it or delete it if no update exists.",
- pluginName);
- }
- }
- }
-
- // Fake masters:
- for (auto& [name, proxiedPlugins] : proxiedByNames) {
- if (proxiedPlugins.size() > 1) {
- auto it = std::min_element(std::begin(proxiedPlugins), std::end(proxiedPlugins),
- [&](auto const& lhs, auto const& rhs) {
- return isBetterInterface(as_qobject(lhs), as_qobject(rhs));
- });
-
- for (auto& proxiedPlugin : proxiedPlugins) {
- if (proxiedPlugin != *it) {
- m_Requirements.at(proxiedPlugin).setMaster(*it);
- }
- }
- }
- }
-
- } catch (const std::exception &e) {
- reportError(QObject::tr("failed to initialize plugin %1: %2").arg(pluginName).arg(e.what()));
- }
+ QStringList filepaths = proxy->pluginList(
+ QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()));
+ for (const QString& filepath : filepaths) {
+ loadProxied(filepath, proxy);
}
return proxy;
}
@@ -613,6 +568,7 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
IPlugin *dummy = qobject_cast<IPlugin*>(plugin);
if (initPlugin(dummy, pluginProxy, skipInit)) {
bf::at_key<IPlugin>(m_Plugins).push_back(dummy);
+ emit pluginRegistered(dummy);
return dummy;
}
}
@@ -620,36 +576,6 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString &fileNam
return nullptr;
}
-void PluginContainer::unloadPlugins()
-{
- if (m_UserInterface != nullptr) {
- m_UserInterface->disconnectPlugins();
- }
-
- // disconnect all slots before unloading plugins so plugins don't have to take care of that
- if (m_Organizer) {
- m_Organizer->disconnectPlugins();
- }
-
- bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); });
- bf::for_each(m_AccessPlugins, [](auto& t) { t.second.clear(); });
- m_Requirements.clear();
-
- for (const boost::signals2::connection &connection : m_DiagnosisConnections) {
- connection.disconnect();
- }
- m_DiagnosisConnections.clear();
-
- while (!m_PluginLoaders.empty()) {
- QPluginLoader *loader = m_PluginLoaders.back();
- m_PluginLoaders.pop_back();
- if ((loader != nullptr) && !loader->unload()) {
- log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString());
- }
- delete loader;
- }
-}
-
IPlugin* PluginContainer::managedGame() const
{
// TODO: This const_cast is safe but ugly. Most methods require a IPlugin*, so
@@ -754,7 +680,22 @@ const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const
return m_Requirements.at(plugin);
}
-IPluginGame *PluginContainer::managedGame(const QString &name) const
+OrganizerProxy* PluginContainer::organizerProxy(MOBase::IPlugin* plugin) const
+{
+ return requirements(plugin).m_Organizer;
+}
+
+MOBase::IPluginProxy* PluginContainer::pluginProxy(MOBase::IPlugin* plugin) const
+{
+ return requirements(plugin).proxy();
+}
+
+QString PluginContainer::filepath(MOBase::IPlugin* plugin) const
+{
+ return as_qobject(plugin)->property("filepath").toString();
+}
+
+IPluginGame *PluginContainer::game(const QString &name) const
{
auto iter = m_SupportedGames.find(name);
if (iter != m_SupportedGames.end()) {
@@ -769,6 +710,267 @@ const PreviewGenerator &PluginContainer::previewGenerator() const
return m_PreviewGenerator;
}
+void PluginContainer::startPluginsImpl(const std::vector<QObject*>& plugins) const
+{
+ // setUserInterface()
+ if (m_UserInterface) {
+ for (auto* plugin : plugins) {
+ if (auto* proxy = qobject_cast<IPluginProxy*>(plugin)) {
+ proxy->setParentWidget(m_UserInterface->mainWindow());
+ }
+ if (auto* modPage = qobject_cast<IPluginModPage*>(plugin)) {
+ modPage->setParentWidget(m_UserInterface->mainWindow());
+ }
+ if (auto* tool = qobject_cast<IPluginTool*>(plugin)) {
+ tool->setParentWidget(m_UserInterface->mainWindow());
+ }
+ if (auto* installer = qobject_cast<IPluginInstaller*>(plugin)) {
+ installer->setParentWidget(m_UserInterface->mainWindow());
+ }
+ }
+ }
+
+ // Trigger initial callbacks, e.g. onUserInterfaceInitialized and onProfileChanged.
+ if (m_Organizer) {
+ for (auto* object : plugins) {
+ auto* plugin = qobject_cast<IPlugin*>(object);
+ auto* oproxy = organizerProxy(plugin);
+ oproxy->connectSignals();
+ oproxy->m_ProfileChanged(nullptr, m_Organizer->currentProfile());
+
+ if (m_UserInterface) {
+ oproxy->m_UserInterfaceInitialized(m_UserInterface->mainWindow());
+ }
+ }
+ }
+}
+
+std::vector<QObject*> PluginContainer::loadProxied(const QString& filepath, IPluginProxy* proxy)
+{
+ std::vector<QObject*> proxiedPlugins;
+
+ try {
+ // We get a list of matching plugins as proxies can return multiple plugins
+ // per file and do not have a good way of supporting multiple inheritance.
+ QList<QObject*> matchingPlugins = proxy->load(filepath);
+
+ // We are going to group plugin by names and "fix" them later:
+ std::map<QString, std::vector<IPlugin*>> proxiedByNames;
+
+ for (QObject* proxiedPlugin : matchingPlugins) {
+ if (proxiedPlugin != nullptr) {
+
+ if (IPlugin* proxied = registerPlugin(proxiedPlugin, filepath, proxy); proxied) {
+ log::debug("loaded plugin '{}' from '{}' - [{}]",
+ proxied->name(), QFileInfo(filepath).fileName(), implementedInterfaces(proxied).join(", "));
+
+ // Store the plugin for later:
+ proxiedPlugins.push_back(proxiedPlugin);
+ proxiedByNames[proxied->name()].push_back(proxied);
+ }
+ else {
+ log::warn(
+ "plugin \"{}\" failed to load. If this plugin is for an older version of MO "
+ "you have to update it or delete it if no update exists.",
+ filepath);
+ }
+ }
+ }
+
+ // Fake masters:
+ for (auto& [name, proxiedPlugins] : proxiedByNames) {
+ if (proxiedPlugins.size() > 1) {
+ auto it = std::min_element(std::begin(proxiedPlugins), std::end(proxiedPlugins),
+ [&](auto const& lhs, auto const& rhs) {
+ return isBetterInterface(as_qobject(lhs), as_qobject(rhs));
+ });
+
+ for (auto& proxiedPlugin : proxiedPlugins) {
+ if (proxiedPlugin != *it) {
+ m_Requirements.at(proxiedPlugin).setMaster(*it);
+ }
+ }
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ reportError(QObject::tr("failed to initialize plugin %1: %2").arg(filepath).arg(e.what()));
+ }
+
+ return proxiedPlugins;
+}
+
+QObject* PluginContainer::loadQtPlugin(const QString& filepath)
+{
+ std::unique_ptr<QPluginLoader> pluginLoader(new QPluginLoader(filepath, this));
+ if (pluginLoader->instance() == nullptr) {
+ m_FailedPlugins.push_back(filepath);
+ log::error("failed to load plugin {}: {}", filepath, pluginLoader->errorString());
+ }
+ else {
+ QObject* object = pluginLoader->instance();
+ if (IPlugin* plugin = registerPlugin(object, filepath, nullptr); plugin) {
+ log::debug("loaded plugin '{}' from '{}' - [{}]",
+ plugin->name(), QFileInfo(filepath).fileName(), implementedInterfaces(plugin).join(", "));
+ m_PluginLoaders.push_back(pluginLoader.release());
+ return object;
+ }
+ else {
+ m_FailedPlugins.push_back(filepath);
+ log::warn("plugin '{}' failed to load (may be outdated)", filepath);
+ }
+ }
+ return nullptr;
+}
+
+void PluginContainer::loadPlugin(QString const& filepath)
+{
+ std::vector<QObject*> plugins;
+ if (QFileInfo(filepath).isFile() && QLibrary::isLibrary(filepath)) {
+ QObject* plugin = loadQtPlugin(filepath);
+ if (plugin) {
+ plugins.push_back(plugin);
+ }
+ }
+ else {
+ // We need to check if this can be handled by a proxy.
+ for (auto* proxy : this->plugins<IPluginProxy>()) {
+ auto filepaths = proxy->pluginList(
+ QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()));
+ if (filepaths.contains(filepath)) {
+ plugins = loadProxied(filepath, proxy);
+ break;
+ }
+ }
+ }
+
+ for (auto* plugin : plugins) {
+ emit pluginRegistered(qobject_cast<IPlugin*>(plugin));
+ }
+
+ startPluginsImpl(plugins);
+}
+
+void PluginContainer::unloadPlugin(MOBase::IPlugin* plugin, QObject* object)
+{
+ if (auto* game = qobject_cast<IPluginGame*>(object)) {
+
+ if (game == managedGame()) {
+ throw Exception("cannot unload the plugin for the currently managed game");
+ }
+
+ unregisterGame(game);
+ }
+
+ // We need to remove from the m_Plugins maps BEFORE unloading from the proxy
+ // otherwise the qobject_cast to check the plugin type will not work.
+ bf::for_each(m_Plugins, [object](auto& t) {
+ using type = typename std::decay_t<decltype(t.second)>::value_type;
+
+ // We do not want to remove from QObject since we are iterating over them.
+ if constexpr (!std::is_same<type, QObject*>{}) {
+ auto itp = std::find(t.second.begin(), t.second.end(), qobject_cast<type>(object));
+ if (itp != t.second.end()) {
+ t.second.erase(itp);
+ }
+ }
+ });
+
+ emit pluginUnregistered(plugin);
+
+ // Remove from the members.
+ if (auto* diagnose = qobject_cast<IPluginDiagnose*>(object)) {
+ bf::at_key<IPluginDiagnose>(m_AccessPlugins).erase(diagnose);
+ }
+ if (auto* mapper = qobject_cast<IPluginFileMapper*>(object)) {
+ bf::at_key<IPluginFileMapper>(m_AccessPlugins).erase(mapper);
+ }
+
+ auto& mapNames = bf::at_key<QString>(m_AccessPlugins);
+ if (mapNames.contains(plugin->name())) {
+ mapNames.erase(plugin->name());
+ }
+
+ m_Organizer->settings().plugins().unregisterPlugin(plugin);
+
+ // Force disconnection of the signals from the proxies. This is a safety
+ // operations since those signals should be disconnected when the proxies
+ // are destroyed anyway.
+ organizerProxy(plugin)->disconnectSignals();
+
+ // Is this a proxied plugin?
+ auto* proxy = pluginProxy(plugin);
+
+ if (proxy) {
+ proxy->unload(filepath(plugin));
+ }
+ else {
+ // We need to find the loader.
+ auto it = std::find_if(m_PluginLoaders.begin(), m_PluginLoaders.end(),
+ [object](auto* loader) { return loader->instance() == object; });
+
+ if (it != m_PluginLoaders.end()) {
+ if (!(*it)->unload()) {
+ log::error("failed to unload {}: {}", (*it)->fileName(), (*it)->errorString());
+ }
+ delete* it;
+ m_PluginLoaders.erase(it);
+ }
+ else {
+ log::error("loader for plugin {} does not exist, cannot unload", plugin->name());
+ }
+
+ }
+
+ // Do this at the end.
+ m_Requirements.erase(plugin);
+}
+
+void PluginContainer::unloadPlugin(QString const& filepath)
+{
+ // We need to find all the plugins from the given path and
+ // unload them:
+ QString cleanPath = QDir::cleanPath(filepath);
+ auto &objects = bf::at_key<QObject>(m_Plugins);
+ for (auto it = objects.begin(); it != objects.end(); ) {
+ auto* plugin = qobject_cast<IPlugin*>(*it);
+ if (this->filepath(plugin) == filepath) {
+ unloadPlugin(plugin, *it);
+ it = objects.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+}
+
+void PluginContainer::reloadPlugin(QString const& filepath)
+{
+ unloadPlugin(filepath);
+ loadPlugin(filepath);
+}
+
+void PluginContainer::unloadPlugins()
+{
+ bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); });
+ bf::for_each(m_AccessPlugins, [](auto& t) { t.second.clear(); });
+ m_Requirements.clear();
+
+ for (const boost::signals2::connection& connection : m_DiagnosisConnections) {
+ connection.disconnect();
+ }
+ m_DiagnosisConnections.clear();
+
+ while (!m_PluginLoaders.empty()) {
+ QPluginLoader* loader = m_PluginLoaders.back();
+ m_PluginLoaders.pop_back();
+ if ((loader != nullptr) && !loader->unload()) {
+ log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString());
+ }
+ delete loader;
+ }
+}
+
void PluginContainer::loadPlugins()
{
TimeThis tt("PluginContainer::loadPlugins()");
@@ -863,24 +1065,9 @@ void PluginContainer::loadPlugins()
loadCheck.flush();
}
- QString pluginName = iter.filePath();
- if (QLibrary::isLibrary(pluginName)) {
- std::unique_ptr<QPluginLoader> pluginLoader(new QPluginLoader(pluginName, this));
- if (pluginLoader->instance() == nullptr) {
- m_FailedPlugins.push_back(pluginName);
- log::error(
- "failed to load plugin {}: {}",
- pluginName, pluginLoader->errorString());
- } else {
- if (IPlugin* plugin = registerPlugin(pluginLoader->instance(), pluginName, nullptr); plugin) {
- log::debug("loaded plugin '{}' from '{}' - [{}]",
- plugin->name(), QFileInfo(pluginName).fileName(), implementedInterfaces(plugin).join(", "));
- m_PluginLoaders.push_back(pluginLoader.release());
- } else {
- m_FailedPlugins.push_back(pluginName);
- log::warn("plugin \"{}\" failed to load (may be outdated)", pluginName);
- }
- }
+ QString filepath = iter.filePath();
+ if (QLibrary::isLibrary(filepath)) {
+ loadQtPlugin(filepath);
}
}
@@ -911,7 +1098,6 @@ void PluginContainer::loadPlugins()
}
}
-
std::vector<unsigned int> PluginContainer::activeProblems() const
{
std::vector<unsigned int> problems;