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 | |
43 | namespace Plasma |
44 | { |
45 | |
46 | K_GLOBAL_STATIC(QMutex, s_bigLock) |
47 | |
48 | AbstractRunner::AbstractRunner(QObject *parent, const QString &path) |
49 | : QObject(parent), |
50 | d(new AbstractRunnerPrivate(this)) |
51 | { |
52 | d->init(path); |
53 | } |
54 | |
55 | AbstractRunner::AbstractRunner(const KService::Ptr service, QObject *parent) |
56 | : QObject(parent), |
57 | d(new AbstractRunnerPrivate(this)) |
58 | { |
59 | d->init(service); |
60 | } |
61 | |
62 | AbstractRunner::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 | |
74 | AbstractRunner::~AbstractRunner() |
75 | { |
76 | delete d; |
77 | } |
78 | |
79 | KConfigGroup 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 | |
90 | void AbstractRunner::reloadConfiguration() |
91 | { |
92 | if (d->script) { |
93 | emit d->script->reloadConfiguration(); |
94 | } |
95 | } |
96 | |
97 | void AbstractRunner::addSyntax(const RunnerSyntax &syntax) |
98 | { |
99 | d->syntaxes.append(syntax); |
100 | } |
101 | |
102 | void AbstractRunner::setDefaultSyntax(const RunnerSyntax &syntax) |
103 | { |
104 | d->syntaxes.append(syntax); |
105 | d->defaultSyntax = &(d->syntaxes.last()); |
106 | } |
107 | |
108 | void AbstractRunner::setSyntaxes(const QList<RunnerSyntax> &syntaxes) |
109 | { |
110 | d->syntaxes = syntaxes; |
111 | } |
112 | |
113 | QList<RunnerSyntax> AbstractRunner::syntaxes() const |
114 | { |
115 | return d->syntaxes; |
116 | } |
117 | |
118 | RunnerSyntax *AbstractRunner::defaultSyntax() const |
119 | { |
120 | return d->defaultSyntax; |
121 | } |
122 | |
123 | void 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 | |
162 | QList<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 | |
172 | QAction* 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 | |
179 | void AbstractRunner::addAction(const QString &id, QAction *action) |
180 | { |
181 | d->actions.insert(id, action); |
182 | } |
183 | |
184 | void AbstractRunner::removeAction(const QString &id) |
185 | { |
186 | QAction *a = d->actions.take(id); |
187 | delete a; |
188 | } |
189 | |
190 | QAction* AbstractRunner::action(const QString &id) const |
191 | { |
192 | return d->actions.value(id); |
193 | } |
194 | |
195 | QHash<QString, QAction*> AbstractRunner::actions() const |
196 | { |
197 | return d->actions; |
198 | } |
199 | |
200 | void AbstractRunner::clearActions() |
201 | { |
202 | qDeleteAll(d->actions); |
203 | d->actions.clear(); |
204 | } |
205 | |
206 | QMimeData * AbstractRunner::mimeDataForMatch(const QueryMatch *match) |
207 | { |
208 | Q_UNUSED(match) |
209 | return 0; |
210 | } |
211 | |
212 | bool AbstractRunner::hasRunOptions() |
213 | { |
214 | return d->hasRunOptions; |
215 | } |
216 | |
217 | void AbstractRunner::setHasRunOptions(bool hasRunOptions) |
218 | { |
219 | d->hasRunOptions = hasRunOptions; |
220 | } |
221 | |
222 | void AbstractRunner::createRunOptions(QWidget *parent) |
223 | { |
224 | if (d->script) { |
225 | emit d->script->createRunOptions(parent); |
226 | } |
227 | } |
228 | |
229 | AbstractRunner::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 | |
243 | void AbstractRunner::setSpeed(Speed speed) |
244 | { |
245 | d->speedLock.lockForWrite(); |
246 | d->speed = speed; |
247 | d->speedLock.unlock(); |
248 | } |
249 | |
250 | AbstractRunner::Priority AbstractRunner::priority() const |
251 | { |
252 | return d->priority; |
253 | } |
254 | |
255 | void AbstractRunner::setPriority(Priority priority) |
256 | { |
257 | d->priority = priority; |
258 | } |
259 | |
260 | RunnerContext::Types AbstractRunner::ignoredTypes() const |
261 | { |
262 | return d->blackListed; |
263 | } |
264 | |
265 | void AbstractRunner::setIgnoredTypes(RunnerContext::Types types) |
266 | { |
267 | d->blackListed = types; |
268 | } |
269 | |
270 | KService::List AbstractRunner::serviceQuery(const QString &serviceType, const QString &constraint) const |
271 | { |
272 | return KServiceTypeTrader::self()->query(serviceType, constraint); |
273 | } |
274 | |
275 | QMutex* AbstractRunner::bigLock() |
276 | { |
277 | return s_bigLock; |
278 | } |
279 | |
280 | void 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 | |
287 | void AbstractRunner::match(Plasma::RunnerContext &search) |
288 | { |
289 | if (d->script) { |
290 | return d->script->match(search); |
291 | } |
292 | } |
293 | |
294 | QString 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 | |
307 | QIcon 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 | |
320 | QString 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 | |
333 | QString 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 | |
346 | const Package* AbstractRunner::package() const |
347 | { |
348 | return d->package; |
349 | } |
350 | |
351 | |
352 | void AbstractRunner::init() |
353 | { |
354 | if (d->script) { |
355 | d->setupScriptSupport(); |
356 | d->script->init(); |
357 | } |
358 | |
359 | reloadConfiguration(); |
360 | } |
361 | |
362 | DataEngine *AbstractRunner::dataEngine(const QString &name) const |
363 | { |
364 | return d->dataEngine(name); |
365 | } |
366 | |
367 | bool AbstractRunner::isMatchingSuspended() const |
368 | { |
369 | return d->suspendMatching; |
370 | } |
371 | |
372 | void AbstractRunner::suspendMatching(bool suspend) |
373 | { |
374 | if (d->suspendMatching == suspend) { |
375 | return; |
376 | } |
377 | |
378 | d->suspendMatching = suspend; |
379 | emit matchingSuspended(suspend); |
380 | } |
381 | |
382 | AbstractRunnerPrivate::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 | |
396 | AbstractRunnerPrivate::~AbstractRunnerPrivate() |
397 | { |
398 | delete script; |
399 | script = 0; |
400 | delete package; |
401 | package = 0; |
402 | } |
403 | |
404 | void 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 | |
419 | void AbstractRunnerPrivate::init(const QString &path) |
420 | { |
421 | prepScripting(path); |
422 | } |
423 | |
424 | void 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 |
456 | void 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 | |