1/*
2 * Copyright 2006-2007 Aaron Seigo <aseigo@kde.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include "abstractrunner.h"
21
22#include <QAction>
23#include <QHash>
24#include <QMenu>
25#include <QMimeData>
26#include <QMutex>
27#include <QMutexLocker>
28#include <QTimer>
29
30#include <kdebug.h>
31#include <kicon.h>
32#include <kplugininfo.h>
33#include <kservicetypetrader.h>
34#include <kstandarddirs.h>
35
36#include <plasma/package.h>
37#include <plasma/querymatch.h>
38
39#include "private/abstractrunner_p.h"
40#include "runnercontext.h"
41#include "scripting/runnerscript.h"
42
43namespace Plasma
44{
45
46K_GLOBAL_STATIC(QMutex, s_bigLock)
47
48AbstractRunner::AbstractRunner(QObject *parent, const QString &path)
49 : QObject(parent),
50 d(new AbstractRunnerPrivate(this))
51{
52 d->init(path);
53}
54
55AbstractRunner::AbstractRunner(const KService::Ptr service, QObject *parent)
56 : QObject(parent),
57 d(new AbstractRunnerPrivate(this))
58{
59 d->init(service);
60}
61
62AbstractRunner::AbstractRunner(QObject *parent, const QVariantList &args)
63 : QObject(parent),
64 d(new AbstractRunnerPrivate(this))
65{
66 if (args.count() > 0) {
67 KService::Ptr service = KService::serviceByStorageId(args[0].toString());
68 if (service) {
69 d->init(service);
70 }
71 }
72}
73
74AbstractRunner::~AbstractRunner()
75{
76 delete d;
77}
78
79KConfigGroup AbstractRunner::config() const
80{
81 QString group = id();
82 if (group.isEmpty()) {
83 group = "UnnamedRunner";
84 }
85
86 KConfigGroup runners(KGlobal::config(), "Runners");
87 return KConfigGroup(&runners, group);
88}
89
90void AbstractRunner::reloadConfiguration()
91{
92 if (d->script) {
93 emit d->script->reloadConfiguration();
94 }
95}
96
97void AbstractRunner::addSyntax(const RunnerSyntax &syntax)
98{
99 d->syntaxes.append(syntax);
100}
101
102void AbstractRunner::setDefaultSyntax(const RunnerSyntax &syntax)
103{
104 d->syntaxes.append(syntax);
105 d->defaultSyntax = &(d->syntaxes.last());
106}
107
108void AbstractRunner::setSyntaxes(const QList<RunnerSyntax> &syntaxes)
109{
110 d->syntaxes = syntaxes;
111}
112
113QList<RunnerSyntax> AbstractRunner::syntaxes() const
114{
115 return d->syntaxes;
116}
117
118RunnerSyntax *AbstractRunner::defaultSyntax() const
119{
120 return d->defaultSyntax;
121}
122
123void AbstractRunner::performMatch(Plasma::RunnerContext &localContext)
124{
125 static const int reasonableRunTime = 1500;
126 static const int fastEnoughTime = 250;
127
128 if (d->suspendMatching) {
129 return;
130 }
131
132 QTime time;
133 time.restart();
134
135 //The local copy is already obtained in the job
136 match(localContext);
137
138 // automatically rate limit runners that become slooow
139 const int runtime = time.elapsed();
140 bool slowed = speed() == SlowSpeed;
141
142 if (!slowed && runtime > reasonableRunTime) {
143 // we punish runners that return too slowly, even if they don't bring
144 // back matches
145 kDebug() << id() << "runner is too slow, putting it on the back burner.";
146 d->fastRuns = 0;
147 setSpeed(SlowSpeed);
148 }
149
150 if (slowed && runtime < fastEnoughTime && localContext.query().size() > 2) {
151 ++d->fastRuns;
152
153 if (d->fastRuns > 2) {
154 // we reward slowed runners who bring back matches fast enough
155 // 3 times in a row
156 kDebug() << id() << "runner is faster than we thought, kicking it up a notch";
157 setSpeed(NormalSpeed);
158 }
159 }
160}
161
162QList<QAction*> AbstractRunner::actionsForMatch(const Plasma::QueryMatch &match)
163{
164 Q_UNUSED(match)
165 QList<QAction*> ret;
166 if (d->script) {
167 emit d->script->actionsForMatch(match, &ret);
168 }
169 return ret;
170}
171
172QAction* AbstractRunner::addAction(const QString &id, const QIcon &icon, const QString &text)
173{
174 QAction *a = new QAction(icon, text, this);
175 d->actions.insert(id, a);
176 return a;
177}
178
179void AbstractRunner::addAction(const QString &id, QAction *action)
180{
181 d->actions.insert(id, action);
182}
183
184void AbstractRunner::removeAction(const QString &id)
185{
186 QAction *a = d->actions.take(id);
187 delete a;
188}
189
190QAction* AbstractRunner::action(const QString &id) const
191{
192 return d->actions.value(id);
193}
194
195QHash<QString, QAction*> AbstractRunner::actions() const
196{
197 return d->actions;
198}
199
200void AbstractRunner::clearActions()
201{
202 qDeleteAll(d->actions);
203 d->actions.clear();
204}
205
206QMimeData * AbstractRunner::mimeDataForMatch(const QueryMatch *match)
207{
208 Q_UNUSED(match)
209 return 0;
210}
211
212bool AbstractRunner::hasRunOptions()
213{
214 return d->hasRunOptions;
215}
216
217void AbstractRunner::setHasRunOptions(bool hasRunOptions)
218{
219 d->hasRunOptions = hasRunOptions;
220}
221
222void AbstractRunner::createRunOptions(QWidget *parent)
223{
224 if (d->script) {
225 emit d->script->createRunOptions(parent);
226 }
227}
228
229AbstractRunner::Speed AbstractRunner::speed() const
230{
231 // the only time the read lock will fail is if we were slow are going to speed up
232 // or if we were fast and are going to slow down; so don't wait in this case, just
233 // say we're slow. we either will be soon or were just a moment ago and it doesn't
234 // hurt to do one more run the slow way
235 if (!d->speedLock.tryLockForRead()) {
236 return SlowSpeed;
237 }
238 Speed s = d->speed;
239 d->speedLock.unlock();
240 return s;
241}
242
243void AbstractRunner::setSpeed(Speed speed)
244{
245 d->speedLock.lockForWrite();
246 d->speed = speed;
247 d->speedLock.unlock();
248}
249
250AbstractRunner::Priority AbstractRunner::priority() const
251{
252 return d->priority;
253}
254
255void AbstractRunner::setPriority(Priority priority)
256{
257 d->priority = priority;
258}
259
260RunnerContext::Types AbstractRunner::ignoredTypes() const
261{
262 return d->blackListed;
263}
264
265void AbstractRunner::setIgnoredTypes(RunnerContext::Types types)
266{
267 d->blackListed = types;
268}
269
270KService::List AbstractRunner::serviceQuery(const QString &serviceType, const QString &constraint) const
271{
272 return KServiceTypeTrader::self()->query(serviceType, constraint);
273}
274
275QMutex* AbstractRunner::bigLock()
276{
277 return s_bigLock;
278}
279
280void AbstractRunner::run(const Plasma::RunnerContext &search, const Plasma::QueryMatch &action)
281{
282 if (d->script) {
283 return d->script->run(search, action);
284 }
285}
286
287void AbstractRunner::match(Plasma::RunnerContext &search)
288{
289 if (d->script) {
290 return d->script->match(search);
291 }
292}
293
294QString AbstractRunner::name() const
295{
296 if (d->runnerDescription.isValid()) {
297 return d->runnerDescription.name();
298 }
299
300 if (d->package) {
301 return d->package->metadata().name();
302 }
303
304 return objectName();
305}
306
307QIcon AbstractRunner::icon() const
308{
309 if (d->runnerDescription.isValid()) {
310 return KIcon(d->runnerDescription.icon());
311 }
312
313 if (d->package) {
314 return KIcon(d->package->metadata().icon());
315 }
316
317 return QIcon();
318}
319
320QString AbstractRunner::id() const
321{
322 if (d->runnerDescription.isValid()) {
323 return d->runnerDescription.pluginName();
324 }
325
326 if (d->package) {
327 return d->package->metadata().pluginName();
328 }
329
330 return objectName();
331}
332
333QString AbstractRunner::description() const
334{
335 if (d->runnerDescription.isValid()) {
336 return d->runnerDescription.property("Comment").toString();
337 }
338
339 if (d->package) {
340 return d->package->metadata().description();
341 }
342
343 return objectName();
344}
345
346const Package* AbstractRunner::package() const
347{
348 return d->package;
349}
350
351
352void AbstractRunner::init()
353{
354 if (d->script) {
355 d->setupScriptSupport();
356 d->script->init();
357 }
358
359 reloadConfiguration();
360}
361
362DataEngine *AbstractRunner::dataEngine(const QString &name) const
363{
364 return d->dataEngine(name);
365}
366
367bool AbstractRunner::isMatchingSuspended() const
368{
369 return d->suspendMatching;
370}
371
372void AbstractRunner::suspendMatching(bool suspend)
373{
374 if (d->suspendMatching == suspend) {
375 return;
376 }
377
378 d->suspendMatching = suspend;
379 emit matchingSuspended(suspend);
380}
381
382AbstractRunnerPrivate::AbstractRunnerPrivate(AbstractRunner *r)
383 : priority(AbstractRunner::NormalPriority),
384 speed(AbstractRunner::NormalSpeed),
385 blackListed(0),
386 script(0),
387 runner(r),
388 fastRuns(0),
389 package(0),
390 defaultSyntax(0),
391 hasRunOptions(false),
392 suspendMatching(false)
393{
394}
395
396AbstractRunnerPrivate::~AbstractRunnerPrivate()
397{
398 delete script;
399 script = 0;
400 delete package;
401 package = 0;
402}
403
404void AbstractRunnerPrivate::init(const KService::Ptr service)
405{
406 runnerDescription = KPluginInfo(service);
407 if (runnerDescription.isValid()) {
408 const QString api = runnerDescription.property("X-Plasma-API").toString();
409 if (!api.isEmpty()) {
410 const QString path = KStandardDirs::locate("data", "plasma/runners/" + runnerDescription.pluginName() + '/');
411 prepScripting(path, api);
412 if (!script) {
413 kDebug() << "Could not create a(n)" << api << "ScriptEngine for the" << runnerDescription.name() << "Runner.";
414 }
415 }
416 }
417}
418
419void AbstractRunnerPrivate::init(const QString &path)
420{
421 prepScripting(path);
422}
423
424void AbstractRunnerPrivate::prepScripting(const QString &path, QString api)
425{
426 if (script) {
427 return;
428 }
429
430 delete package;
431
432 PackageStructure::Ptr structure = Plasma::packageStructure(api, Plasma::RunnerComponent);
433 structure->setPath(path);
434 package = new Package(path, structure);
435
436 if (!package->isValid()) {
437 kDebug() << "Invalid Runner package at" << path;
438 delete package;
439 package = 0;
440 return;
441 }
442
443 if (api.isEmpty()) {
444 api = package->metadata().implementationApi();
445 }
446
447 script = Plasma::loadScriptEngine(api, runner);
448 if (!script) {
449 delete package;
450 package = 0;
451 }
452}
453
454// put all setup routines for script here. at this point we can assume that
455// package exists and that we have a script engine
456void AbstractRunnerPrivate::setupScriptSupport()
457{
458 if (!package) {
459 return;
460 }
461
462 kDebug() << "setting up script support, package is in" << package->path()
463 << "which is a" << package->structure()->type() << "package"
464 << ", main script is" << package->filePath("mainscript");
465
466 QString translationsPath = package->filePath("translations");
467 if (!translationsPath.isEmpty()) {
468 //FIXME: we should _probably_ use a KComponentData to segregate the applets
469 // from each other; but I want to get the basics working first :)
470 KGlobal::dirs()->addResourceDir("locale", translationsPath);
471 KGlobal::locale()->insertCatalog(package->metadata().pluginName());
472 }
473}
474
475} // Plasma namespace
476
477#include "abstractrunner.moc"
478