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

PythonPluginsModelmodel ()
 
PythonPluginplugin (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_checkerparseDependency (const QString &)
 
static void verifyDependenciesSetStatus (PythonPlugin &)
 
static bool verifyModuleExists (PythonPlugin &)
 

Private Attributes

PythonPluginsModel m_model
 
QList< PythonPluginm_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 102 of file PythonPluginManager.h.

Constructor & Destructor Documentation

◆ PythonPluginManager()

PythonPluginManager::PythonPluginManager ( )

Definition at line 67 of file PythonPluginManager.cpp.

68 : QObject(0)
69 , m_model(0, this)
70{}
PythonPluginsModel m_model

Member Function Documentation

◆ loadModule()

void PythonPluginManager::loadModule ( PythonPlugin & plugin)
private

Definition at line 324 of file PythonPluginManager.cpp.

325{
327
328 QString module_name = plugin.moduleName();
329 KisUsageLogger::writeSysInfo("\t" + module_name);
330 dbgScript << "Loading module: " << module_name;
331
333
334 // Get 'plugins' key from 'pykrita' module dictionary.
335 // Every entry has a module name as a key and 2 elements tuple as a value
336 PyObject* plugins = py.itemString("plugins");
338
339 PyObject* module = py.moduleImport(PQ(module_name));
340 if (module) {
341 // Move just loaded module to the dict
342 const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
343 KIS_SAFE_ASSERT_RECOVER_NOOP(ins_result == 0);
344 Py_DECREF(module);
345 // Handle failure in release mode.
346 if (ins_result == 0) {
347 // Initialize the module from Python's side
348 PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
349 PyObject* result = py.functionCall("_pluginLoaded", PyKrita::Python::PYKRITA_ENGINE, args);
350 Py_DECREF(args);
351 if (result) {
352 dbgScript << "\t" << "success!";
353 plugin.m_loaded = true;
354 return;
355 }
356 }
357 plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
358 } else {
359 plugin.m_errorReason = i18nc(
360 "@info:tooltip"
361 , "Module not loaded:<br/>%1"
362 , py.lastTraceback().replace("\n", "<br/>")
363 );
364 }
365 plugin.m_broken = true;
366 warnScript << "Error loading plugin" << module_name;
367}
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 isBroken() 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:56
#define warnScript
Definition kis_debug.h:98
#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::isBroken(), 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 85 of file PythonPluginManager.cpp.

86{
87 return &m_model;
88}

References m_model.

◆ parseDependency()

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

Definition at line 134 of file PythonPluginManager.cpp.

135{
136 // Check if dependency has package info attached
137 const int pnfo = d.indexOf('(');
138 if (pnfo != -1) {
139 QString dependency = d.mid(0, pnfo);
140 QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
141 dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
143 if (!(checker.isValid() && d.endsWith(')'))) {
144 dbgScript << "Invalid version spec " << d;
145 QString reason = i18nc(
146 "@info:tooltip"
147 , "<p>Specified version has invalid format for dependency <application>%1</application>: "
148 "<icode>%2</icode>. Skipped</p>"
149 , dependency
150 , version_str
151 );
152 return qMakePair(reason, PyKrita::version_checker());
153 }
154 return qMakePair(dependency, checker);
155 }
157}
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 77 of file PythonPluginManager.cpp.

77 {
78 if (index >= 0 && index < m_plugins.count()) {
79 return &m_plugins[index];
80 }
81
82 return nullptr;
83}
QList< PythonPlugin > m_plugins

References m_plugins.

◆ plugins()

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

Definition at line 72 of file PythonPluginManager.cpp.

73{
74 return m_plugins;
75}

References m_plugins.

◆ scanPlugins()

void PythonPluginManager::scanPlugins ( )

Definition at line 259 of file PythonPluginManager.cpp.

260{
261 m_plugins.clear();
262
263 KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
264
265 QStringList desktopFiles = KoResourcePaths::findAllAssets("data", "pykrita/*desktop");
266
267 Q_FOREACH(const QString &desktopFile, desktopFiles) {
268
269 KDesktopFile df(desktopFile);
270 df.setLocale(currentLocale());
271 const KConfigGroup dg = df.desktopGroup();
272 if (dg.readEntry("ServiceTypes") == "Krita/PythonPlugin") {
274 plugin.m_comment = df.readComment();
275 plugin.m_name = df.readName();
276 plugin.m_moduleName = dg.readEntry("X-KDE-Library");
277
278 QString manual = dg.readEntry("X-Krita-Manual");
279 if (!manual.isEmpty()) {
280 QFile f(QFileInfo(desktopFile).path() + "/" + plugin.m_moduleName + "/" + manual);
281 if (f.exists()) {
282 f.open(QFile::ReadOnly);
283 QByteArray ba = f.readAll();
284 f.close();
285 plugin.m_manual = QString::fromUtf8(ba);
286 }
287 }
288 if (!plugin.isValid()) {
289 dbgScript << plugin.name() << "is not usable";
290 continue;
291 }
292
294 dbgScript << "Cannot load" << plugin.name() << ": broken"
295 << plugin.isBroken()
296 << "because:" << plugin.errorReason();
297 continue;
298 }
299
301
302 plugin.m_enabled = pluginSettings.readEntry(QString("enable_") + plugin.moduleName(), false);
303
304 m_plugins.append(plugin);
305 }
306 }
307}
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
const QString & errorReason() const

References currentLocale(), dbgScript, PythonPlugin::errorReason(), KoResourcePaths::findAllAssets(), PythonPlugin::isBroken(), PythonPlugin::isValid(), PythonPlugin::m_comment, PythonPlugin::m_enabled, PythonPlugin::m_manual, 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 398 of file PythonPluginManager.cpp.

399{
400 bool wasEnabled = plugin.isEnabled();
401
402 if (wasEnabled && !enabled) {
404 }
405
406 plugin.m_enabled = enabled;
407 KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
408 pluginSettings.writeEntry(QString("enable_") + plugin.moduleName(), enabled);
409
410 if (!wasEnabled && enabled) {
412 }
413}
void loadModule(PythonPlugin &plugin)
void unloadModule(PythonPlugin &plugin)

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

◆ tryLoadEnabledPlugins()

void PythonPluginManager::tryLoadEnabledPlugins ( )

Definition at line 309 of file PythonPluginManager.cpp.

310{
311 KisUsageLogger::writeSysInfo("Loaded Python Plugins");
312 for (PythonPlugin &plugin : m_plugins) {
313 dbgScript << "Trying to load plugin" << plugin.moduleName()
314 << ". Enabled:" << plugin.isEnabled()
315 << ". Broken: " << plugin.isBroken();
316
317 if (plugin.m_enabled && !plugin.isBroken()) {
319 }
320 }
322}

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 90 of file PythonPluginManager.cpp.

91{
92 Q_FOREACH(PythonPlugin plugin, m_plugins) {
93 if (plugin.m_loaded) {
95 }
96 }
97}

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

◆ unloadModule()

void PythonPluginManager::unloadModule ( PythonPlugin & plugin)
private

Definition at line 369 of file PythonPluginManager.cpp.

370{
373
374 dbgScript << "Unloading module: " << plugin.moduleName();
375
377
378 // Get 'plugins' key from 'pykrita' module dictionary
379 PyObject* plugins = py.itemString("plugins");
381
382 PyObject* const args = Py_BuildValue("(s)", PQ(plugin.moduleName()));
383 py.functionCall("_pluginUnloading", PyKrita::Python::PYKRITA_ENGINE, args);
384 Py_DECREF(args);
385
386 // This will just decrement a reference count for module instance
387 PyDict_DelItemString(plugins, PQ(plugin.moduleName()));
388
389 // Remove the module also from 'sys.modules' dict to really unload it,
390 // so if reloaded all @init actions will work again!
391 PyObject* sys_modules = py.itemString("modules", "sys");
393 PyDict_DelItemString(sys_modules, PQ(plugin.moduleName()));
394
395 plugin.m_loaded = false;
396}

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 169 of file PythonPluginManager.cpp.

170{
171 QStringList dependencies = plugin.property("X-Python-Dependencies").toStringList();
172
174 QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
175 Q_FOREACH(const QString & d, dependencies) {
176 QPair<QString, PyKrita::version_checker> info_pair = parseDependency(d);
177 PyKrita::version_checker& checker = info_pair.second;
178 if (!checker.isValid()) {
179 plugin.m_broken = true;
180 reason += info_pair.first;
181 continue;
182 }
183
184 dbgScript << "Try to import dependency module/package:" << d;
185
186 // Try to import a module
187 const QString& dependency = info_pair.first;
188 PyObject* module = py.moduleImport(PQ(dependency));
189 if (module) {
190 if (checker.isEmpty()) { // Need to check smth?
191 dbgScript << "No version to check, just make sure it's loaded:" << dependency;
192 Py_DECREF(module);
193 continue;
194 }
195 // Try to get __version__ from module
196 // See PEP396: https://www.python.org/dev/peps/pep-0396/
197 PyObject* version_obj = py.itemString("__version__", PQ(dependency));
198 if (!version_obj) {
199 dbgScript << "No __version__ for " << dependency
200 << "[" << plugin.name() << "]:\n" << py.lastTraceback()
201 ;
202 plugin.m_unstable = true;
203 reason += i18nc(
204 "@info:tooltip"
205 , "<p>Failed to check version of dependency <application>%1</application>: "
206 "Module do not have PEP396 <code>__version__</code> attribute. "
207 "It is not disabled, but behaviour is unpredictable...</p>"
208 , dependency
209 );
210 }
211 PyKrita::version dep_version = PyKrita::version::fromPythonObject(version_obj);
212
213 if (!dep_version.isValid()) {
214 // Dunno what is this... Giving up!
215 dbgScript << "***: Can't parse module version for" << dependency;
216 plugin.m_unstable = true;
217 reason += i18nc(
218 "@info:tooltip"
219 , "<p><application>%1</application>: Unexpected module's version format"
220 , dependency
221 );
222 } else if (!checker(dep_version)) {
223 dbgScript << "Version requirement check failed ["
224 << plugin.name() << "] for "
225 << dependency << ": wanted " << checker.operationToString()
226 << QString(checker.required())
227 << ", but found" << QString(dep_version)
228 ;
229 plugin.m_broken = true;
230 reason += i18nc(
231 "@info:tooltip"
232 , "<p><application>%1</application>: No suitable version found. "
233 "Required version %2 %3, but found %4</p>"
234 , dependency
235 , checker.operationToString()
236 , QString(checker.required())
237 , QString(dep_version)
238 );
239 }
240 // Do not need this module anymore...
241 Py_DECREF(module);
242 } else {
243 dbgScript << "Load failure [" << plugin.name() << "]:\n" << py.lastTraceback();
244 plugin.m_broken = true;
245 reason += i18nc(
246 "@info:tooltip"
247 , "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
248 , dependency
249 , py.lastTraceback()
250 );
251 }
252 }
253
254 if (plugin.isBroken() || plugin.isUnstable()) {
255 plugin.m_errorReason = reason;
256 }
257}
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 99 of file PythonPluginManager.cpp.

100{
101 // Find the module:
102 // 0) try to locate directory based plugin first
103 QString rel_path = plugin.moduleFilePathPart();
104 rel_path = rel_path + "/" + "__init__.py";
105 dbgScript << "Finding Python module with rel_path:" << rel_path;
106
107 QString module_path = KoResourcePaths::findAsset("pythonscripts", rel_path);
108
109 dbgScript << "module_path:" << module_path;
110
111 if (module_path.isEmpty()) {
112 // 1) Nothing found, then try file based plugin
113 rel_path = plugin.moduleFilePathPart() + ".py";
114 dbgScript << "Finding Python module with rel_path:" << rel_path;
115 module_path = KoResourcePaths::findAsset("pythonscripts", rel_path);
116 dbgScript << "module_path:" << module_path;
117 }
118
119 // Is anything found at all?
120 if (module_path.isEmpty()) {
121 plugin.m_broken = true;
122 plugin.m_errorReason = i18nc(
123 "@info:tooltip"
124 , "Unable to find the module specified <application>%1</application>"
126 );
127 dbgScript << "Cannot load module:" << plugin.m_errorReason;
128 return false;
129 }
130 dbgScript << "Found module path:" << module_path;
131 return true;
132}
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 131 of file PythonPluginManager.h.

◆ m_plugins

QList<PythonPlugin> PythonPluginManager::m_plugins
private

Definition at line 129 of file PythonPluginManager.h.


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