Krita Source Code Documentation
Loading...
Searching...
No Matches
PythonPluginManager Class Reference

#include <PythonPluginManager.h>

+ Inheritance diagram for PythonPluginManager:

Public Slots

void unloadAllModules ()
 

Public Member Functions

PythonPluginsModel * model ()
 
PythonPlugin * plugin (int index)
 
const QList< PythonPlugin > & plugins () const
 
 PythonPluginManager ()
 
void scanPlugins ()
 
void setPluginEnabled (PythonPlugin &plugin, bool enabled)
 
void tryLoadEnabledPlugins ()
 

Private Member Functions

void loadModule (PythonPlugin &plugin)
 
void unloadModule (PythonPlugin &plugin)
 

Static Private Member Functions

static QPair< QString, PyKrita::version_checker > parseDependency (const QString &)
 
static void verifyDependenciesSetStatus (PythonPlugin &)
 
static bool verifyModuleExists (PythonPlugin &)
 

Private Attributes

PythonPluginsModel m_model
 
QList< PythonPlugin > m_plugins
 

Detailed Description

The Python plugin manager handles discovery, loading and unloading of Python plugins. To get a reference to the manager, use PyKrita::pluginManager().

Definition at line 108 of file PythonPluginManager.h.

Constructor & Destructor Documentation

◆ PythonPluginManager()

PythonPluginManager::PythonPluginManager ( )

Definition at line 93 of file PythonPluginManager.cpp.

94 : QObject(0)
95 , m_model(0, this)
96{}
PythonPluginsModel m_model

Member Function Documentation

◆ loadModule()

void PythonPluginManager::loadModule ( PythonPlugin & plugin)
private

Definition at line 345 of file PythonPluginManager.cpp.

346{
348
349 // We can't know if a plugin is still broken until we try to load it
350 plugin.m_broken = false;
351 plugin.m_errorReason.clear();
352
353 QString module_name = plugin.moduleName();
354 KisUsageLogger::writeSysInfo("\t" + module_name);
355 dbgScript << "Loading module: " << module_name;
356
358
359 // Get 'plugins' key from 'pykrita' module dictionary.
360 // Every entry has a module name as a key and 2 elements tuple as a value
361 PyObject* plugins = py.itemString("plugins");
363
364 PyObject* module = py.moduleImport(PQ(module_name));
365 if (module) {
366 // Move just loaded module to the dict
367 const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
368 KIS_SAFE_ASSERT_RECOVER_NOOP(ins_result == 0);
369 Py_DECREF(module);
370 // Handle failure in release mode.
371 if (ins_result == 0) {
372 // Initialize the module from Python's side
373 PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
374 PyObject* result = py.functionCall("_pluginLoaded", PyKrita::Python::PYKRITA_ENGINE, args);
375 Py_DECREF(args);
376 if (result) {
377 dbgScript << "\t" << "success!";
378 plugin.m_loaded = true;
379 return;
380 }
381 }
382 plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
383 } else {
384 plugin.m_errorReason = i18nc(
385 "@info:tooltip"
386 , "Module not loaded:<br/><code>%1</code>"
387 , py.lastTraceback().replace("\n", "<br/>")
388 );
389 }
390 plugin.m_broken = true;
391 warnScript << "Error loading plugin" << module_name;
392}
static void writeSysInfo(const QString &message)
Writes to the system information file and Krita log.
bool functionCall(const char *functionName, const char *moduleName=PYKRITA_ENGINE)
static const char * PYKRITA_ENGINE
Definition utilities.h:219
PyObject * itemString(const char *item, const char *moduleName=PYKRITA_ENGINE)
QString lastTraceback(void) const
PythonPlugin * plugin(int index)
const QList< PythonPlugin > & plugins() const
bool isEnabled() const
QString moduleName() const
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define dbgScript
Definition kis_debug.h:60
#define warnScript
Definition kis_debug.h:105
#define PQ(x)
Save us some ruddy time when printing out QStrings with UTF-8.
Definition utilities.h:21

References dbgScript, PyKrita::Python::functionCall(), PythonPlugin::isEnabled(), PyKrita::Python::itemString(), KIS_SAFE_ASSERT_RECOVER_NOOP, KIS_SAFE_ASSERT_RECOVER_RETURN, PyKrita::Python::lastTraceback(), PythonPlugin::m_broken, PythonPlugin::m_errorReason, PythonPlugin::m_loaded, PythonPlugin::moduleName(), plugin(), plugins(), PQ, PyKrita::Python::PYKRITA_ENGINE, warnScript, and KisUsageLogger::writeSysInfo().

◆ model()

PythonPluginsModel * PythonPluginManager::model ( )

Definition at line 111 of file PythonPluginManager.cpp.

112{
113 return &m_model;
114}

References m_model.

◆ parseDependency()

QPair< QString, PyKrita::version_checker > PythonPluginManager::parseDependency ( const QString & d)
staticprivate

Definition at line 160 of file PythonPluginManager.cpp.

161{
162 // Check if dependency has package info attached
163 const int pnfo = d.indexOf('(');
164 if (pnfo != -1) {
165 QString dependency = d.mid(0, pnfo);
166 QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
167 dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
169 if (!(checker.isValid() && d.endsWith(')'))) {
170 dbgScript << "Invalid version spec " << d;
171 QString reason = i18nc(
172 "@info:tooltip"
173 , "<p>Specified version has invalid format for dependency <application>%1</application>: "
174 "<icode>%2</icode>. Skipped</p>"
175 , dependency
176 , version_str
177 );
178 return qMakePair(reason, PyKrita::version_checker());
179 }
180 return qMakePair(dependency, checker);
181 }
183}
Class version_checker.
static version_checker fromString(const QString &version_info)

References dbgScript, PyKrita::version_checker::fromString(), PyKrita::version_checker::isValid(), and PyKrita::version_checker::undefined.

◆ plugin()

PythonPlugin * PythonPluginManager::plugin ( int index)

Definition at line 103 of file PythonPluginManager.cpp.

103 {
104 if (index >= 0 && index < m_plugins.count()) {
105 return &m_plugins[index];
106 }
107
108 return nullptr;
109}
QList< PythonPlugin > m_plugins

References m_plugins.

◆ plugins()

const QList< PythonPlugin > & PythonPluginManager::plugins ( ) const

Definition at line 98 of file PythonPluginManager.cpp.

99{
100 return m_plugins;
101}

References m_plugins.

◆ scanPlugins()

void PythonPluginManager::scanPlugins ( )

Definition at line 285 of file PythonPluginManager.cpp.

286{
287 m_plugins.clear();
288
289 KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
290
291 QStringList desktopFiles = KoResourcePaths::findAllAssets("data", "pykrita/*desktop");
292
293 Q_FOREACH(const QString &desktopFile, desktopFiles) {
294
295 KDesktopFile df(desktopFile);
296 df.setLocale(currentLocale());
297 const KConfigGroup dg = df.desktopGroup();
298 if (dg.readEntry("ServiceTypes") == "Krita/PythonPlugin") {
300 plugin.m_comment = df.readComment();
301 plugin.m_name = df.readName();
302 plugin.m_moduleName = dg.readEntry("X-KDE-Library");
303 plugin.m_desktopFilePath = QFileInfo(desktopFile).filePath();
304
305 QString manual = dg.readEntry("X-Krita-Manual");
306 if (!manual.isEmpty()) {
307 plugin.m_manualPath = QFileInfo(desktopFile).path() + "/" + plugin.m_moduleName + "/" + manual;
308 }
309 if (!plugin.isValid()) {
310 dbgScript << plugin.name() << "is not usable";
311 continue;
312 }
313
315 dbgScript << "Cannot load" << plugin.name() << ": broken"
316 << plugin.isBroken()
317 << "because:" << plugin.errorReason();
318 continue;
319 }
320
322
323 plugin.m_enabled = pluginSettings.readEntry(QString("enable_") + plugin.moduleName(), false);
324
325 m_plugins.append(plugin);
326 }
327 }
328}
static QString currentLocale()
static QStringList findAllAssets(const QString &type, const QString &filter=QString(), SearchOptions options=NoSearchOptions)
static bool verifyModuleExists(PythonPlugin &)
static void verifyDependenciesSetStatus(PythonPlugin &)
QString name() const
bool isBroken() const
const QString & errorReason() const

References currentLocale(), dbgScript, PythonPlugin::errorReason(), KoResourcePaths::findAllAssets(), PythonPlugin::isBroken(), PythonPlugin::isValid(), PythonPlugin::m_comment, PythonPlugin::m_desktopFilePath, PythonPlugin::m_enabled, PythonPlugin::m_manualPath, PythonPlugin::m_moduleName, PythonPlugin::m_name, m_plugins, PythonPlugin::moduleName(), PythonPlugin::name(), plugin(), verifyDependenciesSetStatus(), and verifyModuleExists().

◆ setPluginEnabled()

void PythonPluginManager::setPluginEnabled ( PythonPlugin & plugin,
bool enabled )

Definition at line 423 of file PythonPluginManager.cpp.

424{
425 bool wasEnabled = plugin.isEnabled();
426
427 if (wasEnabled && !enabled && plugin.m_loaded) {
429 }
430
431 plugin.m_enabled = enabled;
432 KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
433 pluginSettings.writeEntry(QString("enable_") + plugin.moduleName(), enabled);
434
435 if (!wasEnabled && enabled) {
437 }
438}
void loadModule(PythonPlugin &plugin)
void unloadModule(PythonPlugin &plugin)

References PythonPlugin::isEnabled(), loadModule(), PythonPlugin::m_enabled, PythonPlugin::m_loaded, PythonPlugin::moduleName(), plugin(), and unloadModule().

◆ tryLoadEnabledPlugins()

void PythonPluginManager::tryLoadEnabledPlugins ( )

Definition at line 330 of file PythonPluginManager.cpp.

331{
332 KisUsageLogger::writeSysInfo("Loaded Python Plugins");
333 for (PythonPlugin &plugin : m_plugins) {
334 dbgScript << "Trying to load plugin" << plugin.moduleName()
335 << ". Enabled:" << plugin.isEnabled()
336 << ". Broken: " << plugin.isBroken();
337
338 if (plugin.m_enabled && !plugin.isBroken()) {
340 }
341 }
343}

References dbgScript, PythonPlugin::isBroken(), PythonPlugin::isEnabled(), loadModule(), PythonPlugin::m_enabled, m_plugins, PythonPlugin::moduleName(), plugin(), and KisUsageLogger::writeSysInfo().

◆ unloadAllModules

void PythonPluginManager::unloadAllModules ( )
slot

Definition at line 116 of file PythonPluginManager.cpp.

117{
118 Q_FOREACH(PythonPlugin plugin, m_plugins) {
119 if (plugin.m_loaded) {
121 }
122 }
123}

References PythonPlugin::m_loaded, m_plugins, plugin(), and unloadModule().

◆ unloadModule()

void PythonPluginManager::unloadModule ( PythonPlugin & plugin)
private

Definition at line 394 of file PythonPluginManager.cpp.

395{
398
399 dbgScript << "Unloading module: " << plugin.moduleName();
400
402
403 // Get 'plugins' key from 'pykrita' module dictionary
404 PyObject* plugins = py.itemString("plugins");
406
407 PyObject* const args = Py_BuildValue("(s)", PQ(plugin.moduleName()));
408 py.functionCall("_pluginUnloading", PyKrita::Python::PYKRITA_ENGINE, args);
409 Py_DECREF(args);
410
411 // This will just decrement a reference count for module instance
412 PyDict_DelItemString(plugins, PQ(plugin.moduleName()));
413
414 // Remove the module also from 'sys.modules' dict to really unload it,
415 // so if reloaded all @init actions will work again!
416 PyObject* sys_modules = py.itemString("modules", "sys");
418 PyDict_DelItemString(sys_modules, PQ(plugin.moduleName()));
419
420 plugin.m_loaded = false;
421}

References dbgScript, PyKrita::Python::functionCall(), PythonPlugin::isBroken(), PyKrita::Python::itemString(), KIS_SAFE_ASSERT_RECOVER_RETURN, PythonPlugin::m_loaded, PythonPlugin::moduleName(), plugin(), plugins(), PQ, and PyKrita::Python::PYKRITA_ENGINE.

◆ verifyDependenciesSetStatus()

void PythonPluginManager::verifyDependenciesSetStatus ( PythonPlugin & plugin)
staticprivate

Collect dependencies and check them. To do it just try to import a module... when unload it ;)

X-Python-Dependencies property of .desktop file has the following format: python-module(version-info), where python-module a python module name to be imported, version-spec is a version triplet delimited by dots, possible w/ leading compare operator: =, <, >, <=, >=

Definition at line 195 of file PythonPluginManager.cpp.

196{
197 QStringList dependencies = plugin.property("X-Python-Dependencies").toStringList();
198
200 QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
201 Q_FOREACH(const QString & d, dependencies) {
202 QPair<QString, PyKrita::version_checker> info_pair = parseDependency(d);
203 PyKrita::version_checker& checker = info_pair.second;
204 if (!checker.isValid()) {
205 plugin.m_broken = true;
206 reason += info_pair.first;
207 continue;
208 }
209
210 dbgScript << "Try to import dependency module/package:" << d;
211
212 // Try to import a module
213 const QString& dependency = info_pair.first;
214 PyObject* module = py.moduleImport(PQ(dependency));
215 if (module) {
216 if (checker.isEmpty()) { // Need to check smth?
217 dbgScript << "No version to check, just make sure it's loaded:" << dependency;
218 Py_DECREF(module);
219 continue;
220 }
221 // Try to get __version__ from module
222 // See PEP396: https://www.python.org/dev/peps/pep-0396/
223 PyObject* version_obj = py.itemString("__version__", PQ(dependency));
224 if (!version_obj) {
225 dbgScript << "No __version__ for " << dependency
226 << "[" << plugin.name() << "]:\n" << py.lastTraceback()
227 ;
228 plugin.m_unstable = true;
229 reason += i18nc(
230 "@info:tooltip"
231 , "<p>Failed to check version of dependency <application>%1</application>: "
232 "Module do not have PEP396 <code>__version__</code> attribute. "
233 "It is not disabled, but behaviour is unpredictable...</p>"
234 , dependency
235 );
236 }
237 PyKrita::version dep_version = PyKrita::version::fromPythonObject(version_obj);
238
239 if (!dep_version.isValid()) {
240 // Dunno what is this... Giving up!
241 dbgScript << "***: Can't parse module version for" << dependency;
242 plugin.m_unstable = true;
243 reason += i18nc(
244 "@info:tooltip"
245 , "<p><application>%1</application>: Unexpected module's version format"
246 , dependency
247 );
248 } else if (!checker(dep_version)) {
249 dbgScript << "Version requirement check failed ["
250 << plugin.name() << "] for "
251 << dependency << ": wanted " << checker.operationToString()
252 << QString(checker.required())
253 << ", but found" << QString(dep_version)
254 ;
255 plugin.m_broken = true;
256 reason += i18nc(
257 "@info:tooltip"
258 , "<p><application>%1</application>: No suitable version found. "
259 "Required version %2 %3, but found %4</p>"
260 , dependency
261 , checker.operationToString()
262 , QString(checker.required())
263 , QString(dep_version)
264 );
265 }
266 // Do not need this module anymore...
267 Py_DECREF(module);
268 } else {
269 dbgScript << "Load failure [" << plugin.name() << "]:\n" << py.lastTraceback();
270 plugin.m_broken = true;
271 reason += i18nc(
272 "@info:tooltip"
273 , "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
274 , dependency
275 , py.lastTraceback()
276 );
277 }
278 }
279
280 if (plugin.isBroken() || plugin.isUnstable()) {
281 plugin.m_errorReason = reason;
282 }
283}
QString operationToString() const
Class version.
bool isValid() const
static version fromPythonObject(PyObject *version_obj)
static QPair< QString, PyKrita::version_checker > parseDependency(const QString &)
bool isUnstable() const
QVariant property(const QString &name) const

References dbgScript, PyKrita::version::fromPythonObject(), PythonPlugin::isBroken(), PyKrita::version_checker::isEmpty(), PythonPlugin::isUnstable(), PyKrita::version::isValid(), PyKrita::version_checker::isValid(), PyKrita::Python::itemString(), PyKrita::Python::lastTraceback(), PythonPlugin::m_broken, PythonPlugin::m_errorReason, PythonPlugin::m_unstable, PythonPlugin::name(), PyKrita::version_checker::operationToString(), parseDependency(), plugin(), PQ, PythonPlugin::property(), and PyKrita::version_checker::required().

◆ verifyModuleExists()

bool PythonPluginManager::verifyModuleExists ( PythonPlugin & plugin)
staticprivate

Definition at line 125 of file PythonPluginManager.cpp.

126{
127 // Find the module:
128 // 0) try to locate directory based plugin first
129 QString rel_path = plugin.moduleFilePathPart();
130 rel_path = rel_path + "/" + "__init__.py";
131 dbgScript << "Finding Python module with rel_path:" << rel_path;
132
133 QString module_path = KoResourcePaths::findAsset("pythonscripts", rel_path);
134
135 dbgScript << "module_path:" << module_path;
136
137 if (module_path.isEmpty()) {
138 // 1) Nothing found, then try file based plugin
139 rel_path = plugin.moduleFilePathPart() + ".py";
140 dbgScript << "Finding Python module with rel_path:" << rel_path;
141 module_path = KoResourcePaths::findAsset("pythonscripts", rel_path);
142 dbgScript << "module_path:" << module_path;
143 }
144
145 // Is anything found at all?
146 if (module_path.isEmpty()) {
147 plugin.m_broken = true;
148 plugin.m_errorReason = i18nc(
149 "@info:tooltip"
150 , "Unable to find the module specified <application>%1</application>"
152 );
153 dbgScript << "Cannot load module:" << plugin.m_errorReason;
154 return false;
155 }
156 dbgScript << "Found module path:" << module_path;
157 return true;
158}
static QString findAsset(const QString &type, const QString &fileName)
QString moduleFilePathPart() const

References dbgScript, KoResourcePaths::findAsset(), PythonPlugin::m_broken, PythonPlugin::m_errorReason, PythonPlugin::moduleFilePathPart(), PythonPlugin::moduleName(), and plugin().

Member Data Documentation

◆ m_model

PythonPluginsModel PythonPluginManager::m_model
private

Definition at line 137 of file PythonPluginManager.h.

◆ m_plugins

QList<PythonPlugin> PythonPluginManager::m_plugins
private

Definition at line 135 of file PythonPluginManager.h.


The documentation for this class was generated from the following files: