mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Merge pull request #84 from Holt59/ipluginproxy-unload
Add IPluginProxy::unload() and rename instantiate() to load().
This commit is contained in:
@@ -227,7 +227,7 @@ QList<PluginSetting> ProxyPython::settings() const
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList ProxyPython::pluginList(const QString &pluginPath) const
|
||||
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
|
||||
{
|
||||
QDir dir(pluginPath);
|
||||
dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
|
||||
@@ -240,29 +240,32 @@ QStringList ProxyPython::pluginList(const QString &pluginPath) const
|
||||
QString name = iter.next();
|
||||
QFileInfo info = iter.fileInfo();
|
||||
|
||||
|
||||
if (info.isFile() && name.endsWith(".py")) {
|
||||
result.append(name);
|
||||
}
|
||||
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
|
||||
result.append(name);
|
||||
// result.append(info.baseName());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QList<QObject*> ProxyPython::instantiate(const QString &pluginName)
|
||||
QList<QObject*> ProxyPython::load(const QString& identifier)
|
||||
{
|
||||
if (m_Runner != nullptr) {
|
||||
QList<QObject*> result = m_Runner->instantiate(pluginName);
|
||||
return result;
|
||||
} else {
|
||||
return QList<QObject*>();
|
||||
if (!m_Runner) {
|
||||
return {};
|
||||
}
|
||||
return m_Runner->load(identifier);
|
||||
}
|
||||
|
||||
void ProxyPython::unload(const QString& identifier)
|
||||
{
|
||||
if (m_Runner) {
|
||||
return m_Runner->unload(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned int> ProxyPython::activeProblems() const
|
||||
{
|
||||
|
||||
@@ -48,8 +48,9 @@ public:
|
||||
virtual MOBase::VersionInfo version() const;
|
||||
virtual QList<MOBase::PluginSetting> settings() const;
|
||||
|
||||
QStringList pluginList(const QString &pluginPath) const;
|
||||
QList<QObject*> instantiate(const QString &pluginName);
|
||||
QStringList pluginList(const QDir& pluginPath) const;
|
||||
QList<QObject*> load(const QString& identifier);
|
||||
void unload(const QString& identifier);
|
||||
|
||||
/**
|
||||
* @return the parent widget for newly created dialogs
|
||||
|
||||
+72
-14
@@ -1106,7 +1106,10 @@ public:
|
||||
~PythonRunner();
|
||||
|
||||
bool initPython(const QString& pythonDir);
|
||||
QList<QObject*> instantiate(const QString& pluginName);
|
||||
|
||||
QList<QObject*> load(const QString& identifier);
|
||||
void unload(const QString& identifier);
|
||||
|
||||
bool isPythonInstalled() const;
|
||||
bool isPythonVersionSupported() const;
|
||||
|
||||
@@ -1132,9 +1135,10 @@ private:
|
||||
|
||||
private:
|
||||
|
||||
// List of python objects representing plugins to keep all the bpy::object "alive"
|
||||
// during the execution.
|
||||
std::vector<bpy::object> m_PythonObjects;
|
||||
// For each "identifier" (python file or python module folder), contains the list
|
||||
// of python objects to keep "alive" during the execution.
|
||||
std::unordered_map<QString, std::vector<bpy::object>> m_PythonObjects;
|
||||
|
||||
wchar_t* m_PythonHome;
|
||||
};
|
||||
|
||||
@@ -1158,6 +1162,15 @@ PythonRunner::PythonRunner()
|
||||
PythonRunner::~PythonRunner() {
|
||||
// We need the GIL lock when destroying Python objects.
|
||||
GILock lock;
|
||||
|
||||
// Boost.Python does not handle cyclic garbace collection, so we need to release
|
||||
// everything hold by the objects before deleting the objects themselves:
|
||||
for (auto& [name, objects] : m_PythonObjects) {
|
||||
for (auto& obj : objects) {
|
||||
obj.attr("__dict__").attr("clear")();
|
||||
}
|
||||
}
|
||||
|
||||
m_PythonObjects.clear();
|
||||
}
|
||||
|
||||
@@ -1336,7 +1349,7 @@ void PythonRunner::appendIfInstance(bpy::object const& obj, QList<QObject*> &int
|
||||
}
|
||||
}
|
||||
|
||||
QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
QList<QObject*> PythonRunner::load(const QString& identifier)
|
||||
{
|
||||
GILock lock;
|
||||
|
||||
@@ -1354,25 +1367,25 @@ QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
// Dictionary that will contain createPlugin() or createPlugins().
|
||||
bpy::dict moduleDict;
|
||||
|
||||
if (pluginName.endsWith(".py")) {
|
||||
if (identifier.endsWith(".py")) {
|
||||
bpy::object mainModule = bpy::import("__main__");
|
||||
bpy::dict moduleNamespace = bpy::extract<bpy::dict>(mainModule.attr("__dict__"))();
|
||||
|
||||
std::string temp = ToString(pluginName);
|
||||
std::string temp = ToString(identifier);
|
||||
if (!handled_exec_file(temp.c_str(), moduleNamespace)) {
|
||||
moduleDict = moduleNamespace;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Retrieve the module name:
|
||||
QStringList parts = pluginName.split("/");
|
||||
QStringList parts = identifier.split("/");
|
||||
std::string moduleName = ToString(parts.takeLast());
|
||||
ensureFolderInPath(parts.join("/"));
|
||||
moduleDict = bpy::dict(bpy::import(moduleName.c_str()).attr("__dict__"));
|
||||
}
|
||||
|
||||
if (bpy::len(moduleDict) == 0) {
|
||||
MOBase::log::error("Failed to import plugin from {}.", pluginName);
|
||||
MOBase::log::error("Failed to import plugin from {}.", identifier);
|
||||
throw pyexcept::PythonError();
|
||||
}
|
||||
|
||||
@@ -1388,7 +1401,7 @@ QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
else if (moduleDict.has_key("createPlugins")) {
|
||||
bpy::object pyPlugins = moduleDict["createPlugins"]();
|
||||
if (!PySequence_Check(pyPlugins.ptr())) {
|
||||
MOBase::log::error("Plugin {}: createPlugins must return a list.", pluginName);
|
||||
MOBase::log::error("Plugin {}: createPlugins must return a list.", identifier);
|
||||
}
|
||||
else {
|
||||
bpy::list pyList(pyPlugins);
|
||||
@@ -1402,7 +1415,7 @@ QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
bpy::delitem(moduleDict, bpy::str("createPlugins"));
|
||||
}
|
||||
else {
|
||||
MOBase::log::error("Plugin {}: missing a createPlugin(s) function.", pluginName);
|
||||
MOBase::log::error("Plugin {}: missing a createPlugin(s) function.", identifier);
|
||||
}
|
||||
|
||||
// If we have no plugins, there was an issue, and we already logged the problem:
|
||||
@@ -1415,7 +1428,7 @@ QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
for (bpy::object pluginObj : plugins) {
|
||||
|
||||
// Add the plugin to keep it alive:
|
||||
m_PythonObjects.push_back(pluginObj);
|
||||
m_PythonObjects[identifier].push_back(pluginObj);
|
||||
|
||||
QList<QObject*> interfaceList;
|
||||
|
||||
@@ -1435,7 +1448,7 @@ QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
}
|
||||
|
||||
if (interfaceList.isEmpty()) {
|
||||
MOBase::log::error("Plugin {}: no plugin interface implemented.", pluginName);
|
||||
MOBase::log::error("Plugin {}: no plugin interface implemented.", identifier);
|
||||
}
|
||||
|
||||
// Append the plugins to the main list:
|
||||
@@ -1445,11 +1458,56 @@ QList<QObject*> PythonRunner::instantiate(const QString &pluginName)
|
||||
return allInterfaceList;
|
||||
}
|
||||
catch (const bpy::error_already_set&) {
|
||||
MOBase::log::error("Failed to import plugin from {}.", pluginName);
|
||||
MOBase::log::error("Failed to import plugin from {}.", identifier);
|
||||
throw pyexcept::PythonError();
|
||||
}
|
||||
}
|
||||
|
||||
void PythonRunner::unload(const QString& identifier)
|
||||
{
|
||||
auto it = m_PythonObjects.find(identifier);
|
||||
if (it != m_PythonObjects.end()) {
|
||||
|
||||
GILock lock;
|
||||
|
||||
if (!identifier.endsWith(".py")) {
|
||||
|
||||
// At this point, the identifier is the full path to the module.
|
||||
QDir folder(identifier);
|
||||
|
||||
// We want to "unload" (remove from sys.modules) modules that come
|
||||
// from this plugin (whose __path__ points under this module, including
|
||||
// the module of the plugin itself).
|
||||
bpy::object sys = bpy::import("sys");
|
||||
bpy::dict modules = bpy::extract<bpy::dict>(sys.attr("modules"));
|
||||
bpy::list keys = modules.keys();
|
||||
for (std::size_t i = 0; i < bpy::len(keys); ++i) {
|
||||
bpy::object mod = modules[keys[i]];
|
||||
if (PyObject_HasAttrString(mod.ptr(), "__path__")) {
|
||||
QString mpath = bpy::extract<QString>(mod.attr("__path__")[0]);
|
||||
|
||||
if (!folder.relativeFilePath(mpath).startsWith("..")) {
|
||||
// If the path is under identifier, we need to unload it.
|
||||
log::debug("Unloading module {} from {} for {}.", bpy::extract<QString>(keys[i])(), mpath, identifier);
|
||||
bpy::delitem(modules, keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Boost.Python does not handle cyclic garbace collection, so we need to release
|
||||
// everything hold by the objects before deleting the objects themselves (done when
|
||||
// erasing from m_PythonObjects).
|
||||
for (auto& obj : it->second) {
|
||||
obj.attr("__dict__").attr("clear")();
|
||||
}
|
||||
|
||||
log::debug("Deleting {} python objects for {}.", it->second.size(), identifier);
|
||||
m_PythonObjects.erase(it);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonRunner::isPythonInstalled() const
|
||||
{
|
||||
return Py_IsInitialized() != 0;
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
|
||||
class IPythonRunner {
|
||||
public:
|
||||
virtual QList<QObject*> instantiate(const QString &pluginName) = 0;
|
||||
|
||||
virtual QList<QObject*> load(const QString& identifier) = 0;
|
||||
virtual void unload(const QString& identifier) = 0;
|
||||
|
||||
virtual bool isPythonInstalled() const = 0;
|
||||
virtual bool isPythonVersionSupported() const = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user