mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
687 lines
18 KiB
C++
687 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2006 Aaron Seigo <aseigo@kde.org>
|
|
* Copyright (C) 2007, 2009 Ryan P. Bitanga <ryan.bitanga@gmail.com>
|
|
* Copyright (C) 2008 Jordi Polo <mumismo@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Library General Public License as
|
|
* published by the Free Software Foundation; either version 2, 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 General Public License for more details
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "runnermanager.h"
|
|
|
|
#include "config-plasma.h"
|
|
|
|
#include <QMutex>
|
|
#include <QTimer>
|
|
#include <QCoreApplication>
|
|
#include <QThreadPool>
|
|
|
|
#include <kdebug.h>
|
|
#include <kplugininfo.h>
|
|
#include <kservicetypetrader.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
#include "private/runnerjobs_p.h"
|
|
#include "pluginloader.h"
|
|
#include "querymatch.h"
|
|
|
|
//#define MEASURE_PREPTIME
|
|
|
|
namespace Plasma
|
|
{
|
|
|
|
/*****************************************************
|
|
* RunnerManager::Private class
|
|
*
|
|
*****************************************************/
|
|
class RunnerManagerPrivate
|
|
{
|
|
public:
|
|
|
|
RunnerManagerPrivate(RunnerManager *parent)
|
|
: q(parent),
|
|
currentSingleRunner(0),
|
|
threadPool(0),
|
|
prepped(false),
|
|
allRunnersPrepped(false),
|
|
singleRunnerPrepped(false),
|
|
teardownRequested(false),
|
|
singleMode(false),
|
|
singleRunnerWasLoaded(false)
|
|
{
|
|
threadPool = new QThreadPool();
|
|
|
|
matchChangeTimer.setSingleShot(true);
|
|
|
|
QObject::connect(&matchChangeTimer, SIGNAL(timeout()), q, SLOT(matchesChanged()));
|
|
QObject::connect(&context, SIGNAL(matchesChanged()), q, SLOT(scheduleMatchesChanged()));
|
|
}
|
|
|
|
~RunnerManagerPrivate()
|
|
{
|
|
KConfigGroup config = configGroup();
|
|
context.save(config);
|
|
|
|
kDebug() << "waiting for runner jobs";
|
|
threadPool->waitForDone();
|
|
delete threadPool;
|
|
}
|
|
|
|
void scheduleMatchesChanged()
|
|
{
|
|
matchChangeTimer.start(100);
|
|
}
|
|
|
|
void matchesChanged()
|
|
{
|
|
emit q->matchesChanged(context.matches());
|
|
}
|
|
|
|
void loadConfiguration()
|
|
{
|
|
KConfigGroup config = configGroup();
|
|
|
|
int idealThreads = QThread::idealThreadCount();
|
|
if (idealThreads < 0) {
|
|
idealThreads = 4;
|
|
}
|
|
const int maxThreads = config.readEntry("maxThreads", idealThreads);
|
|
kDebug() << "limiting runner threads to" << maxThreads;
|
|
//This entry allows to define a hard upper limit independent of the number of processors.
|
|
threadPool->setMaxThreadCount(maxThreads);
|
|
|
|
context.restore(config);
|
|
}
|
|
|
|
KConfigGroup configGroup()
|
|
{
|
|
return conf.isValid() ? conf : KConfigGroup(KGlobal::config(), "PlasmaRunnerManager");
|
|
}
|
|
|
|
void clearSingleRunner()
|
|
{
|
|
if (singleRunnerWasLoaded) {
|
|
delete currentSingleRunner;
|
|
}
|
|
|
|
currentSingleRunner = 0;
|
|
}
|
|
|
|
void loadSingleRunner()
|
|
{
|
|
if (!singleMode || singleModeRunnerId.isEmpty()) {
|
|
clearSingleRunner();
|
|
return;
|
|
}
|
|
|
|
if (currentSingleRunner) {
|
|
if (currentSingleRunner->id() == singleModeRunnerId) {
|
|
return;
|
|
}
|
|
|
|
clearSingleRunner();
|
|
}
|
|
|
|
AbstractRunner *loadedRunner = q->runner(singleModeRunnerId);
|
|
if (loadedRunner) {
|
|
singleRunnerWasLoaded = false;
|
|
currentSingleRunner = loadedRunner;
|
|
return;
|
|
}
|
|
|
|
KService::List offers = KServiceTypeTrader::self()->query("Plasma/Runner", QString("[X-KDE-PluginInfo-Name] == '%1'").arg(singleModeRunnerId));
|
|
if (!offers.isEmpty()) {
|
|
const KService::Ptr &service = offers[0];
|
|
currentSingleRunner = loadInstalledRunner(service);
|
|
|
|
if (currentSingleRunner) {
|
|
emit currentSingleRunner->prepare();
|
|
singleRunnerWasLoaded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void loadRunners()
|
|
{
|
|
KConfigGroup config = configGroup();
|
|
KPluginInfo::List offers = RunnerManager::listRunnerInfo();
|
|
|
|
const bool loadAll = config.readEntry("loadAll", false);
|
|
const QStringList whiteList = config.readEntry("pluginWhiteList", QStringList());
|
|
const bool noWhiteList = whiteList.isEmpty();
|
|
KConfigGroup pluginConf;
|
|
if (conf.isValid()) {
|
|
pluginConf = KConfigGroup(&conf, "Plugins");
|
|
} else {
|
|
pluginConf = KConfigGroup(KGlobal::config(), "Plugins");
|
|
}
|
|
|
|
advertiseSingleRunnerIds.clear();
|
|
|
|
QSet<AbstractRunner *> deadRunners;
|
|
QMutableListIterator<KPluginInfo> it(offers);
|
|
while (it.hasNext()) {
|
|
KPluginInfo &description = it.next();
|
|
//kDebug() << "Loading runner: " << service->name() << service->storageId();
|
|
QString tryExec = description.property("TryExec").toString();
|
|
//kDebug() << "TryExec is" << tryExec;
|
|
if (!tryExec.isEmpty() && KStandardDirs::findExe(tryExec).isEmpty()) {
|
|
// we don't actually have this application!
|
|
continue;
|
|
}
|
|
|
|
const QString runnerName = description.pluginName();
|
|
description.load(pluginConf);
|
|
|
|
const bool loaded = runners.contains(runnerName);
|
|
const bool selected = loadAll || (description.isPluginEnabled() && (noWhiteList || whiteList.contains(runnerName)));
|
|
|
|
const bool singleQueryModeEnabled = description.property("X-Plasma-AdvertiseSingleRunnerQueryMode").toBool();
|
|
|
|
if (singleQueryModeEnabled) {
|
|
advertiseSingleRunnerIds.insert(runnerName, description.name());
|
|
}
|
|
|
|
//kDebug() << loadAll << description.isPluginEnabled() << noWhiteList << whiteList.contains(runnerName);
|
|
if (selected) {
|
|
if (!loaded) {
|
|
AbstractRunner *runner = loadInstalledRunner(description.service());
|
|
|
|
if (runner) {
|
|
runners.insert(runnerName, runner);
|
|
}
|
|
}
|
|
} else if (loaded) {
|
|
//Remove runner
|
|
deadRunners.insert(runners.take(runnerName));
|
|
kDebug() << "Removing runner: " << runnerName;
|
|
}
|
|
}
|
|
|
|
if (!deadRunners.isEmpty()) {
|
|
qDeleteAll(deadRunners);
|
|
}
|
|
|
|
if (!singleRunnerWasLoaded) {
|
|
// in case we deleted it up above
|
|
clearSingleRunner();
|
|
}
|
|
|
|
kDebug() << "All runners loaded, total:" << runners.count();
|
|
}
|
|
|
|
AbstractRunner *loadInstalledRunner(const KService::Ptr service)
|
|
{
|
|
if (!service) {
|
|
return 0;
|
|
}
|
|
|
|
AbstractRunner *runner = PluginLoader::loadRunner(service->property("X-KDE-PluginInfo-Name", QVariant::String).toString());
|
|
|
|
if (runner) {
|
|
runner->setParent(q);
|
|
} else {
|
|
const QString api = service->property("X-Plasma-API").toString();
|
|
|
|
if (api.isEmpty()) {
|
|
QVariantList args;
|
|
args << service->storageId();
|
|
QString error;
|
|
runner = service->createInstance<AbstractRunner>(q, args, &error);
|
|
if (!runner) {
|
|
kDebug() << "Failed to load runner:" << service->name() << ". error reported:" << error;
|
|
}
|
|
} else {
|
|
//kDebug() << "got a script runner known as" << api;
|
|
runner = new AbstractRunner(service, q);
|
|
}
|
|
}
|
|
|
|
if (runner) {
|
|
kDebug() << "================= loading runner:" << service->name() << "=================";
|
|
QObject::connect(runner, SIGNAL(matchingSuspended(bool)), q, SLOT(runnerMatchingSuspended(bool)));
|
|
QMetaObject::invokeMethod(runner, "init");
|
|
if (prepped) {
|
|
emit runner->prepare();
|
|
}
|
|
}
|
|
|
|
return runner;
|
|
}
|
|
|
|
void checkTearDown()
|
|
{
|
|
//kDebug() << prepped << teardownRequested << threadPool->activeThreadCount();
|
|
|
|
if (!prepped || !teardownRequested) {
|
|
return;
|
|
}
|
|
|
|
if (threadPool->activeThreadCount() <= 0) {
|
|
if (allRunnersPrepped) {
|
|
foreach (AbstractRunner *runner, runners) {
|
|
emit runner->teardown();
|
|
}
|
|
|
|
allRunnersPrepped = false;
|
|
}
|
|
|
|
if (singleRunnerPrepped) {
|
|
if (currentSingleRunner) {
|
|
emit currentSingleRunner->teardown();
|
|
}
|
|
|
|
singleRunnerPrepped = false;
|
|
}
|
|
|
|
emit q->queryFinished();
|
|
|
|
prepped = false;
|
|
teardownRequested = false;
|
|
}
|
|
}
|
|
|
|
void runnerMatchingSuspended(bool suspended)
|
|
{
|
|
if (suspended || !prepped || teardownRequested) {
|
|
return;
|
|
}
|
|
|
|
AbstractRunner *runner = qobject_cast<AbstractRunner *>(q->sender());
|
|
|
|
if (runner) {
|
|
startJob(runner);
|
|
}
|
|
}
|
|
|
|
void startJob(AbstractRunner *runner)
|
|
{
|
|
if ((runner->ignoredTypes() & context.type()) == 0) {
|
|
FindMatchesJob *job = new FindMatchesJob(runner, &context);
|
|
threadPool->start(job);
|
|
}
|
|
}
|
|
|
|
RunnerManager *q;
|
|
RunnerContext context;
|
|
QTimer matchChangeTimer;
|
|
QHash<QString, AbstractRunner*> runners;
|
|
QHash<QString, QString> advertiseSingleRunnerIds;
|
|
AbstractRunner* currentSingleRunner;
|
|
QThreadPool *threadPool;
|
|
KConfigGroup conf;
|
|
QString singleModeRunnerId;
|
|
bool loadAll : 1;
|
|
bool prepped : 1;
|
|
bool allRunnersPrepped : 1;
|
|
bool singleRunnerPrepped : 1;
|
|
bool teardownRequested : 1;
|
|
bool singleMode : 1;
|
|
bool singleRunnerWasLoaded : 1;
|
|
};
|
|
|
|
/*****************************************************
|
|
* RunnerManager::Public class
|
|
*
|
|
*****************************************************/
|
|
RunnerManager::RunnerManager(QObject *parent)
|
|
: QObject(parent),
|
|
d(new RunnerManagerPrivate(this))
|
|
{
|
|
d->loadConfiguration();
|
|
}
|
|
|
|
RunnerManager::RunnerManager(KConfigGroup &c, QObject *parent)
|
|
: QObject(parent),
|
|
d(new RunnerManagerPrivate(this))
|
|
{
|
|
// Should this be really needed? Maybe d->loadConfiguration(c) would make
|
|
// more sense.
|
|
d->conf = KConfigGroup(&c, "PlasmaRunnerManager");
|
|
d->loadConfiguration();
|
|
}
|
|
|
|
RunnerManager::~RunnerManager()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void RunnerManager::reloadConfiguration()
|
|
{
|
|
d->loadConfiguration();
|
|
d->loadRunners();
|
|
}
|
|
|
|
void RunnerManager::setAllowedRunners(const QStringList &runners)
|
|
{
|
|
KConfigGroup config = d->configGroup();
|
|
config.writeEntry("pluginWhiteList", runners);
|
|
|
|
if (!d->runners.isEmpty()) {
|
|
// this has been called with runners already created. so let's do an instant reload
|
|
d->loadRunners();
|
|
}
|
|
}
|
|
|
|
QStringList RunnerManager::allowedRunners() const
|
|
{
|
|
KConfigGroup config = d->configGroup();
|
|
return config.readEntry("pluginWhiteList", QStringList());
|
|
}
|
|
|
|
void RunnerManager::loadRunner(const KService::Ptr service)
|
|
{
|
|
KPluginInfo description(service);
|
|
const QString runnerName = description.pluginName();
|
|
if (!runnerName.isEmpty() && !d->runners.contains(runnerName)) {
|
|
AbstractRunner *runner = d->loadInstalledRunner(service);
|
|
if (runner) {
|
|
d->runners.insert(runnerName, runner);
|
|
}
|
|
}
|
|
}
|
|
|
|
AbstractRunner* RunnerManager::runner(const QString &name) const
|
|
{
|
|
if (d->runners.isEmpty()) {
|
|
d->loadRunners();
|
|
}
|
|
|
|
return d->runners.value(name, 0);
|
|
}
|
|
|
|
AbstractRunner *RunnerManager::singleModeRunner() const
|
|
{
|
|
return d->currentSingleRunner;
|
|
}
|
|
|
|
void RunnerManager::setSingleModeRunnerId(const QString &id)
|
|
{
|
|
d->singleModeRunnerId = id;
|
|
d->loadSingleRunner();
|
|
}
|
|
|
|
QString RunnerManager::singleModeRunnerId() const
|
|
{
|
|
return d->singleModeRunnerId;
|
|
}
|
|
|
|
bool RunnerManager::singleMode() const
|
|
{
|
|
return d->singleMode;
|
|
}
|
|
|
|
void RunnerManager::setSingleMode(bool singleMode)
|
|
{
|
|
if (d->singleMode == singleMode) {
|
|
return;
|
|
}
|
|
|
|
|
|
Plasma::AbstractRunner *prevSingleRunner = d->currentSingleRunner;
|
|
d->singleMode = singleMode;
|
|
d->loadSingleRunner();
|
|
d->singleMode = d->currentSingleRunner;
|
|
|
|
if (prevSingleRunner != d->currentSingleRunner) {
|
|
if (d->prepped) {
|
|
matchSessionComplete();
|
|
|
|
if (d->singleMode) {
|
|
setupMatchSession();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QList<AbstractRunner *> RunnerManager::runners() const
|
|
{
|
|
return d->runners.values();
|
|
}
|
|
|
|
QStringList RunnerManager::singleModeAdvertisedRunnerIds() const
|
|
{
|
|
return d->advertiseSingleRunnerIds.keys();
|
|
}
|
|
|
|
QString RunnerManager::runnerName(const QString &id) const
|
|
{
|
|
if (runner(id)) {
|
|
return runner(id)->name();
|
|
} else {
|
|
return d->advertiseSingleRunnerIds.value(id, QString());
|
|
}
|
|
}
|
|
|
|
RunnerContext* RunnerManager::searchContext() const
|
|
{
|
|
return &d->context;
|
|
}
|
|
|
|
//Reordering is here so data is not reordered till strictly needed
|
|
QList<QueryMatch> RunnerManager::matches() const
|
|
{
|
|
return d->context.matches();
|
|
}
|
|
|
|
void RunnerManager::run(const QString &id)
|
|
{
|
|
run(d->context.match(id));
|
|
}
|
|
|
|
void RunnerManager::run(const QueryMatch &match)
|
|
{
|
|
if (!match.isEnabled()) {
|
|
return;
|
|
}
|
|
d->context.run(match);
|
|
}
|
|
|
|
QList<QAction*> RunnerManager::actionsForMatch(const QueryMatch &match)
|
|
{
|
|
AbstractRunner *runner = match.runner();
|
|
if (runner) {
|
|
return runner->actionsForMatch(match);
|
|
}
|
|
|
|
return QList<QAction*>();
|
|
}
|
|
|
|
QMimeData * RunnerManager::mimeDataForMatch(const QString &id) const
|
|
{
|
|
return mimeDataForMatch(d->context.match(id));
|
|
}
|
|
|
|
|
|
QMimeData * RunnerManager::mimeDataForMatch(const QueryMatch &match) const
|
|
{
|
|
AbstractRunner *runner = match.runner();
|
|
QMimeData *mimeData;
|
|
if (runner && QMetaObject::invokeMethod(
|
|
runner,
|
|
"mimeDataForMatch", Qt::DirectConnection,
|
|
Q_RETURN_ARG(QMimeData*, mimeData),
|
|
Q_ARG(const Plasma::QueryMatch *, &match)
|
|
)) {
|
|
return mimeData;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
KPluginInfo::List RunnerManager::listRunnerInfo(const QString &parentApp)
|
|
{
|
|
return PluginLoader::listRunnerInfo(parentApp);
|
|
}
|
|
|
|
void RunnerManager::setupMatchSession()
|
|
{
|
|
d->teardownRequested = false;
|
|
|
|
if (d->prepped) {
|
|
return;
|
|
}
|
|
|
|
d->prepped = true;
|
|
if (d->singleMode) {
|
|
if (d->currentSingleRunner) {
|
|
emit d->currentSingleRunner->prepare();
|
|
d->singleRunnerPrepped = true;
|
|
}
|
|
} else {
|
|
foreach (AbstractRunner *runner, d->runners) {
|
|
#ifdef MEASURE_PREPTIME
|
|
QTime t;
|
|
t.start();
|
|
#endif
|
|
emit runner->prepare();
|
|
#ifdef MEASURE_PREPTIME
|
|
kDebug() << t.elapsed() << runner->name();
|
|
#endif
|
|
}
|
|
|
|
d->allRunnersPrepped = true;
|
|
}
|
|
}
|
|
|
|
void RunnerManager::matchSessionComplete()
|
|
{
|
|
if (!d->prepped) {
|
|
return;
|
|
}
|
|
|
|
d->teardownRequested = true;
|
|
d->checkTearDown();
|
|
}
|
|
|
|
void RunnerManager::launchQuery(const QString &term)
|
|
{
|
|
launchQuery(term, QString());
|
|
}
|
|
|
|
void RunnerManager::launchQuery(const QString &untrimmedTerm, const QString &runnerName)
|
|
{
|
|
setupMatchSession();
|
|
QString term = untrimmedTerm.trimmed();
|
|
|
|
setSingleModeRunnerId(runnerName);
|
|
setSingleMode(!runnerName.isEmpty());
|
|
|
|
if (term.isEmpty()) {
|
|
if (d->singleMode && d->currentSingleRunner && d->currentSingleRunner->defaultSyntax()) {
|
|
term = d->currentSingleRunner->defaultSyntax()->exampleQueries().first().remove(QRegExp(":q:"));
|
|
} else {
|
|
reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (d->context.query() == term) {
|
|
// we already are searching for this!
|
|
return;
|
|
}
|
|
|
|
if (d->singleMode && !d->currentSingleRunner) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
if (d->runners.isEmpty()) {
|
|
d->loadRunners();
|
|
}
|
|
|
|
reset();
|
|
// kDebug() << "runners searching for" << term << "on" << runnerName;
|
|
d->context.setQuery(term);
|
|
|
|
QHash<QString, AbstractRunner*> runable;
|
|
|
|
//if the name is not empty we will launch only the specified runner
|
|
if (d->singleMode && d->currentSingleRunner) {
|
|
runable.insert(QString(), d->currentSingleRunner);
|
|
d->context.setSingleRunnerQueryMode(true);
|
|
} else {
|
|
runable = d->runners;
|
|
}
|
|
|
|
foreach (Plasma::AbstractRunner *r, runable) {
|
|
if (r->isMatchingSuspended()) {
|
|
continue;
|
|
}
|
|
|
|
d->startJob(r);
|
|
}
|
|
}
|
|
|
|
bool RunnerManager::execQuery(const QString &term)
|
|
{
|
|
return execQuery(term, QString());
|
|
}
|
|
|
|
bool RunnerManager::execQuery(const QString &untrimmedTerm, const QString &runnerName)
|
|
{
|
|
QString term = untrimmedTerm.trimmed();
|
|
|
|
if (term.isEmpty()) {
|
|
reset();
|
|
return false;
|
|
}
|
|
|
|
if (d->runners.isEmpty()) {
|
|
d->loadRunners();
|
|
}
|
|
|
|
if (d->context.query() == term) {
|
|
// we already are searching for this!
|
|
emit matchesChanged(d->context.matches());
|
|
return false;
|
|
}
|
|
|
|
reset();
|
|
//kDebug() << "executing query about " << term << "on" << runnerName;
|
|
d->context.setQuery(term);
|
|
AbstractRunner *r = runner(runnerName);
|
|
|
|
if (!r) {
|
|
//kDebug() << "failed to find the runner";
|
|
return false;
|
|
}
|
|
|
|
if ((r->ignoredTypes() & d->context.type()) != 0) {
|
|
//kDebug() << "ignored!";
|
|
return false;
|
|
}
|
|
|
|
r->performMatch(d->context);
|
|
//kDebug() << "succeeded with" << d->context.matches().count() << "results";
|
|
emit matchesChanged(d->context.matches());
|
|
return true;
|
|
}
|
|
|
|
QString RunnerManager::query() const
|
|
{
|
|
return d->context.query();
|
|
}
|
|
|
|
void RunnerManager::reset()
|
|
{
|
|
d->threadPool->waitForDone(3000);
|
|
|
|
d->context.reset();
|
|
}
|
|
|
|
} // Plasma namespace
|
|
|
|
#include "moc_runnermanager.cpp"
|