1 | /* |
2 | * Copyright (C) 2006 Aaron Seigo <aseigo@kde.org> |
3 | * Copyright (C) 2007, 2009 Ryan P. Bitanga <ryan.bitanga@gmail.com> |
4 | * Copyright (C) 2008 Jordi Polo <mumismo@gmail.com> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU Library General Public License as |
8 | * published by the Free Software Foundation; either version 2, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details |
15 | * |
16 | * You should have received a copy of the GNU Library General Public |
17 | * License along with this program; if not, write to the |
18 | * Free Software Foundation, Inc., |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
20 | */ |
21 | |
22 | #include "runnermanager.h" |
23 | |
24 | #include "config-plasma.h" |
25 | |
26 | #include <QMutex> |
27 | #include <QTimer> |
28 | #include <QCoreApplication> |
29 | |
30 | #include <kdebug.h> |
31 | #include <kplugininfo.h> |
32 | #include <kservicetypetrader.h> |
33 | #include <kstandarddirs.h> |
34 | |
35 | #ifndef PLASMA_NO_SOLID |
36 | #include <solid/device.h> |
37 | #include <solid/deviceinterface.h> |
38 | #endif |
39 | |
40 | #include <Weaver/DebuggingAids.h> |
41 | #include <Weaver/State.h> |
42 | #include <Weaver/Thread.h> |
43 | #include <Weaver/ThreadWeaver.h> |
44 | |
45 | #include "private/runnerjobs_p.h" |
46 | #include "pluginloader.h" |
47 | #include "querymatch.h" |
48 | |
49 | using ThreadWeaver::Weaver; |
50 | using ThreadWeaver::Job; |
51 | |
52 | //#define MEASURE_PREPTIME |
53 | |
54 | namespace Plasma |
55 | { |
56 | |
57 | /***************************************************** |
58 | * RunnerManager::Private class |
59 | * |
60 | *****************************************************/ |
61 | class RunnerManagerPrivate |
62 | { |
63 | public: |
64 | |
65 | RunnerManagerPrivate(RunnerManager *parent) |
66 | : q(parent), |
67 | deferredRun(0), |
68 | currentSingleRunner(0), |
69 | prepped(false), |
70 | allRunnersPrepped(false), |
71 | singleRunnerPrepped(false), |
72 | teardownRequested(false), |
73 | singleMode(false), |
74 | singleRunnerWasLoaded(false) |
75 | { |
76 | matchChangeTimer.setSingleShot(true); |
77 | delayTimer.setSingleShot(true); |
78 | |
79 | QObject::connect(&matchChangeTimer, SIGNAL(timeout()), q, SLOT(matchesChanged())); |
80 | QObject::connect(&context, SIGNAL(matchesChanged()), q, SLOT(scheduleMatchesChanged())); |
81 | QObject::connect(&delayTimer, SIGNAL(timeout()), q, SLOT(unblockJobs())); |
82 | } |
83 | |
84 | ~RunnerManagerPrivate() |
85 | { |
86 | KConfigGroup config = configGroup(); |
87 | context.save(config); |
88 | } |
89 | |
90 | void scheduleMatchesChanged() |
91 | { |
92 | matchChangeTimer.start(100); |
93 | } |
94 | |
95 | void matchesChanged() |
96 | { |
97 | emit q->matchesChanged(context.matches()); |
98 | } |
99 | |
100 | void loadConfiguration() |
101 | { |
102 | KConfigGroup config = configGroup(); |
103 | |
104 | //The number of threads used scales with the number of processors. |
105 | #ifndef PLASMA_NO_SOLID |
106 | const int numProcs = |
107 | qMax(Solid::Device::listFromType(Solid::DeviceInterface::Processor).count(), 1); |
108 | #else |
109 | const int numProcs = 1; |
110 | #endif |
111 | //This entry allows to define a hard upper limit independent of the number of processors. |
112 | const int maxThreads = config.readEntry("maxThreads" , 16); |
113 | const int numThreads = qMin(maxThreads, 2 + ((numProcs - 1) * 2)); |
114 | //kDebug() << "setting up" << numThreads << "threads for" << numProcs << "processors"; |
115 | if (numThreads > Weaver::instance()->maximumNumberOfThreads()) { |
116 | Weaver::instance()->setMaximumNumberOfThreads(numThreads); |
117 | } |
118 | // Limit the number of instances of a single normal speed runner and all of the slow runners |
119 | // to half the number of threads |
120 | const int cap = qMax(2, numThreads/2); |
121 | DefaultRunnerPolicy::instance().setCap(cap); |
122 | |
123 | context.restore(config); |
124 | } |
125 | |
126 | KConfigGroup configGroup() |
127 | { |
128 | return conf.isValid() ? conf : KConfigGroup(KGlobal::config(), "PlasmaRunnerManager" ); |
129 | } |
130 | |
131 | void clearSingleRunner() |
132 | { |
133 | if (singleRunnerWasLoaded) { |
134 | delete currentSingleRunner; |
135 | } |
136 | |
137 | currentSingleRunner = 0; |
138 | } |
139 | |
140 | void loadSingleRunner() |
141 | { |
142 | if (!singleMode || singleModeRunnerId.isEmpty()) { |
143 | clearSingleRunner(); |
144 | return; |
145 | } |
146 | |
147 | if (currentSingleRunner) { |
148 | if (currentSingleRunner->id() == singleModeRunnerId) { |
149 | return; |
150 | } |
151 | |
152 | clearSingleRunner(); |
153 | } |
154 | |
155 | AbstractRunner *loadedRunner = q->runner(singleModeRunnerId); |
156 | if (loadedRunner) { |
157 | singleRunnerWasLoaded = false; |
158 | currentSingleRunner = loadedRunner; |
159 | return; |
160 | } |
161 | |
162 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Runner" , QString("[X-KDE-PluginInfo-Name] == '%1'" ).arg(singleModeRunnerId)); |
163 | if (!offers.isEmpty()) { |
164 | const KService::Ptr &service = offers[0]; |
165 | currentSingleRunner = loadInstalledRunner(service); |
166 | |
167 | if (currentSingleRunner) { |
168 | emit currentSingleRunner->prepare(); |
169 | singleRunnerWasLoaded = true; |
170 | } |
171 | } |
172 | } |
173 | |
174 | void loadRunners() |
175 | { |
176 | KConfigGroup config = configGroup(); |
177 | KPluginInfo::List offers = RunnerManager::listRunnerInfo(); |
178 | |
179 | const bool loadAll = config.readEntry("loadAll" , false); |
180 | const QStringList whiteList = config.readEntry("pluginWhiteList" , QStringList()); |
181 | const bool noWhiteList = whiteList.isEmpty(); |
182 | KConfigGroup pluginConf; |
183 | if (conf.isValid()) { |
184 | pluginConf = KConfigGroup(&conf, "Plugins" ); |
185 | } else { |
186 | pluginConf = KConfigGroup(KGlobal::config(), "Plugins" ); |
187 | } |
188 | |
189 | advertiseSingleRunnerIds.clear(); |
190 | |
191 | QSet<AbstractRunner *> deadRunners; |
192 | QMutableListIterator<KPluginInfo> it(offers); |
193 | while (it.hasNext()) { |
194 | KPluginInfo &description = it.next(); |
195 | //kDebug() << "Loading runner: " << service->name() << service->storageId(); |
196 | QString tryExec = description.property("TryExec" ).toString(); |
197 | //kDebug() << "TryExec is" << tryExec; |
198 | if (!tryExec.isEmpty() && KStandardDirs::findExe(tryExec).isEmpty()) { |
199 | // we don't actually have this application! |
200 | continue; |
201 | } |
202 | |
203 | const QString runnerName = description.pluginName(); |
204 | description.load(pluginConf); |
205 | |
206 | const bool loaded = runners.contains(runnerName); |
207 | const bool selected = loadAll || (description.isPluginEnabled() && (noWhiteList || whiteList.contains(runnerName))); |
208 | |
209 | const bool singleQueryModeEnabled = description.property("X-Plasma-AdvertiseSingleRunnerQueryMode" ).toBool(); |
210 | |
211 | if (singleQueryModeEnabled) { |
212 | advertiseSingleRunnerIds.insert(runnerName, description.name()); |
213 | } |
214 | |
215 | //kDebug() << loadAll << description.isPluginEnabled() << noWhiteList << whiteList.contains(runnerName); |
216 | if (selected) { |
217 | if (!loaded) { |
218 | AbstractRunner *runner = loadInstalledRunner(description.service()); |
219 | |
220 | if (runner) { |
221 | runners.insert(runnerName, runner); |
222 | } |
223 | } |
224 | } else if (loaded) { |
225 | //Remove runner |
226 | deadRunners.insert(runners.take(runnerName)); |
227 | kDebug() << "Removing runner: " << runnerName; |
228 | } |
229 | } |
230 | |
231 | if (!deadRunners.isEmpty()) { |
232 | QSet<FindMatchesJob *> deadJobs; |
233 | foreach (FindMatchesJob *job, searchJobs) { |
234 | if (deadRunners.contains(job->runner())) { |
235 | QObject::disconnect(job, SIGNAL(done(ThreadWeaver::Job*)), q, SLOT(jobDone(ThreadWeaver::Job*))); |
236 | searchJobs.remove(job); |
237 | deadJobs.insert(job); |
238 | } |
239 | } |
240 | |
241 | foreach (FindMatchesJob *job, oldSearchJobs) { |
242 | if (deadRunners.contains(job->runner())) { |
243 | oldSearchJobs.remove(job); |
244 | deadJobs.insert(job); |
245 | } |
246 | } |
247 | |
248 | if (deadJobs.isEmpty()) { |
249 | qDeleteAll(deadRunners); |
250 | } else { |
251 | new DelayedJobCleaner(deadJobs, deadRunners); |
252 | } |
253 | } |
254 | |
255 | if (!singleRunnerWasLoaded) { |
256 | // in case we deleted it up above |
257 | clearSingleRunner(); |
258 | } |
259 | |
260 | kDebug() << "All runners loaded, total:" << runners.count(); |
261 | } |
262 | |
263 | AbstractRunner *loadInstalledRunner(const KService::Ptr service) |
264 | { |
265 | if (!service) { |
266 | return 0; |
267 | } |
268 | |
269 | AbstractRunner *runner = PluginLoader::pluginLoader()->loadRunner(service->property("X-KDE-PluginInfo-Name" , QVariant::String).toString()); |
270 | |
271 | if (runner) { |
272 | runner->setParent(q); |
273 | } else { |
274 | const QString api = service->property("X-Plasma-API" ).toString(); |
275 | |
276 | if (api.isEmpty()) { |
277 | QVariantList args; |
278 | args << service->storageId(); |
279 | if (Plasma::isPluginVersionCompatible(KPluginLoader(*service).pluginVersion())) { |
280 | QString error; |
281 | runner = service->createInstance<AbstractRunner>(q, args, &error); |
282 | if (!runner) { |
283 | kDebug() << "Failed to load runner:" << service->name() << ". error reported:" << error; |
284 | } |
285 | } |
286 | } else { |
287 | //kDebug() << "got a script runner known as" << api; |
288 | runner = new AbstractRunner(service, q); |
289 | } |
290 | } |
291 | |
292 | if (runner) { |
293 | kDebug() << "================= loading runner:" << service->name() << "=================" ; |
294 | QObject::connect(runner, SIGNAL(matchingSuspended(bool)), q, SLOT(runnerMatchingSuspended(bool))); |
295 | QMetaObject::invokeMethod(runner, "init" ); |
296 | if (prepped) { |
297 | emit runner->prepare(); |
298 | } |
299 | } |
300 | |
301 | return runner; |
302 | } |
303 | |
304 | void jobDone(ThreadWeaver::Job *job) |
305 | { |
306 | FindMatchesJob *runJob = dynamic_cast<FindMatchesJob *>(job); |
307 | |
308 | if (!runJob) { |
309 | return; |
310 | } |
311 | |
312 | if (deferredRun.isEnabled() && runJob->runner() == deferredRun.runner()) { |
313 | //kDebug() << "job actually done, running now **************"; |
314 | QueryMatch tmpRun = deferredRun; |
315 | deferredRun = QueryMatch(0); |
316 | tmpRun.run(context); |
317 | } |
318 | |
319 | searchJobs.remove(runJob); |
320 | oldSearchJobs.remove(runJob); |
321 | runJob->deleteLater(); |
322 | |
323 | if (searchJobs.isEmpty() && context.matches().isEmpty()) { |
324 | // we finished our run, and there are no valid matches, and so no |
325 | // signal will have been sent out. so we need to emit the signal |
326 | // ourselves here |
327 | emit q->matchesChanged(context.matches()); |
328 | } |
329 | |
330 | checkTearDown(); |
331 | } |
332 | |
333 | void checkTearDown() |
334 | { |
335 | //kDebug() << prepped << teardownRequested << searchJobs.count() << oldSearchJobs.count(); |
336 | |
337 | if (!prepped || !teardownRequested) { |
338 | return; |
339 | } |
340 | |
341 | if (Weaver::instance()->isIdle()) { |
342 | qDeleteAll(searchJobs); |
343 | searchJobs.clear(); |
344 | qDeleteAll(oldSearchJobs); |
345 | oldSearchJobs.clear(); |
346 | } |
347 | |
348 | if (searchJobs.isEmpty() && oldSearchJobs.isEmpty()) { |
349 | if (allRunnersPrepped) { |
350 | foreach (AbstractRunner *runner, runners) { |
351 | emit runner->teardown(); |
352 | } |
353 | |
354 | allRunnersPrepped = false; |
355 | } |
356 | |
357 | if (singleRunnerPrepped) { |
358 | if (currentSingleRunner) { |
359 | emit currentSingleRunner->teardown(); |
360 | } |
361 | |
362 | singleRunnerPrepped = false; |
363 | } |
364 | |
365 | emit q->queryFinished(); |
366 | |
367 | prepped = false; |
368 | teardownRequested = false; |
369 | } |
370 | } |
371 | |
372 | void unblockJobs() |
373 | { |
374 | // WORKAROUND: Queue an empty job to force ThreadWeaver to awaken threads |
375 | if (searchJobs.isEmpty() && Weaver::instance()->isIdle()) { |
376 | qDeleteAll(oldSearchJobs); |
377 | oldSearchJobs.clear(); |
378 | checkTearDown(); |
379 | return; |
380 | } |
381 | |
382 | DummyJob *dummy = new DummyJob(q); |
383 | Weaver::instance()->enqueue(dummy); |
384 | QObject::connect(dummy, SIGNAL(done(ThreadWeaver::Job*)), dummy, SLOT(deleteLater())); |
385 | } |
386 | |
387 | void runnerMatchingSuspended(bool suspended) |
388 | { |
389 | if (suspended || !prepped || teardownRequested) { |
390 | return; |
391 | } |
392 | |
393 | AbstractRunner *runner = qobject_cast<AbstractRunner *>(q->sender()); |
394 | |
395 | if (runner) { |
396 | startJob(runner); |
397 | } |
398 | } |
399 | |
400 | void startJob(AbstractRunner *runner) |
401 | { |
402 | if ((runner->ignoredTypes() & context.type()) == 0) { |
403 | FindMatchesJob *job = new FindMatchesJob(runner, &context, Weaver::instance()); |
404 | QObject::connect(job, SIGNAL(done(ThreadWeaver::Job*)), q, SLOT(jobDone(ThreadWeaver::Job*))); |
405 | if (runner->speed() == AbstractRunner::SlowSpeed) { |
406 | job->setDelayTimer(&delayTimer); |
407 | } |
408 | Weaver::instance()->enqueue(job); |
409 | searchJobs.insert(job); |
410 | } |
411 | } |
412 | |
413 | // Delay in ms before slow runners are allowed to run |
414 | static const int slowRunDelay = 400; |
415 | |
416 | RunnerManager *q; |
417 | QueryMatch deferredRun; |
418 | RunnerContext context; |
419 | QTimer matchChangeTimer; |
420 | QTimer delayTimer; // Timer to control when to run slow runners |
421 | QHash<QString, AbstractRunner*> runners; |
422 | QHash<QString, QString> advertiseSingleRunnerIds; |
423 | AbstractRunner* currentSingleRunner; |
424 | QSet<FindMatchesJob*> searchJobs; |
425 | QSet<FindMatchesJob*> oldSearchJobs; |
426 | KConfigGroup conf; |
427 | QString singleModeRunnerId; |
428 | bool loadAll : 1; |
429 | bool prepped : 1; |
430 | bool allRunnersPrepped : 1; |
431 | bool singleRunnerPrepped : 1; |
432 | bool teardownRequested : 1; |
433 | bool singleMode : 1; |
434 | bool singleRunnerWasLoaded : 1; |
435 | }; |
436 | |
437 | /***************************************************** |
438 | * RunnerManager::Public class |
439 | * |
440 | *****************************************************/ |
441 | RunnerManager::RunnerManager(QObject *parent) |
442 | : QObject(parent), |
443 | d(new RunnerManagerPrivate(this)) |
444 | { |
445 | d->loadConfiguration(); |
446 | //ThreadWeaver::setDebugLevel(true, 4); |
447 | } |
448 | |
449 | RunnerManager::RunnerManager(KConfigGroup &c, QObject *parent) |
450 | : QObject(parent), |
451 | d(new RunnerManagerPrivate(this)) |
452 | { |
453 | // Should this be really needed? Maybe d->loadConfiguration(c) would make |
454 | // more sense. |
455 | d->conf = KConfigGroup(&c, "PlasmaRunnerManager" ); |
456 | d->loadConfiguration(); |
457 | //ThreadWeaver::setDebugLevel(true, 4); |
458 | } |
459 | |
460 | RunnerManager::~RunnerManager() |
461 | { |
462 | if (!qApp->closingDown() && (!d->searchJobs.isEmpty() || !d->oldSearchJobs.isEmpty())) { |
463 | new DelayedJobCleaner(d->searchJobs + d->oldSearchJobs); |
464 | } |
465 | |
466 | delete d; |
467 | } |
468 | |
469 | void RunnerManager::reloadConfiguration() |
470 | { |
471 | d->loadConfiguration(); |
472 | d->loadRunners(); |
473 | } |
474 | |
475 | void RunnerManager::setAllowedRunners(const QStringList &runners) |
476 | { |
477 | KConfigGroup config = d->configGroup(); |
478 | config.writeEntry("pluginWhiteList" , runners); |
479 | |
480 | if (!d->runners.isEmpty()) { |
481 | // this has been called with runners already created. so let's do an instant reload |
482 | d->loadRunners(); |
483 | } |
484 | } |
485 | |
486 | QStringList RunnerManager::allowedRunners() const |
487 | { |
488 | KConfigGroup config = d->configGroup(); |
489 | return config.readEntry("pluginWhiteList" , QStringList()); |
490 | } |
491 | |
492 | void RunnerManager::loadRunner(const KService::Ptr service) |
493 | { |
494 | KPluginInfo description(service); |
495 | const QString runnerName = description.pluginName(); |
496 | if (!runnerName.isEmpty() && !d->runners.contains(runnerName)) { |
497 | AbstractRunner *runner = d->loadInstalledRunner(service); |
498 | if (runner) { |
499 | d->runners.insert(runnerName, runner); |
500 | } |
501 | } |
502 | } |
503 | |
504 | void RunnerManager::loadRunner(const QString &path) |
505 | { |
506 | if (!d->runners.contains(path)) { |
507 | AbstractRunner *runner = new AbstractRunner(this, path); |
508 | connect(runner, SIGNAL(matchingSuspended(bool)), this, SLOT(runnerMatchingSuspended(bool))); |
509 | d->runners.insert(path, runner); |
510 | } |
511 | } |
512 | |
513 | AbstractRunner* RunnerManager::runner(const QString &name) const |
514 | { |
515 | if (d->runners.isEmpty()) { |
516 | d->loadRunners(); |
517 | } |
518 | |
519 | return d->runners.value(name, 0); |
520 | } |
521 | |
522 | AbstractRunner *RunnerManager::singleModeRunner() const |
523 | { |
524 | return d->currentSingleRunner; |
525 | } |
526 | |
527 | void RunnerManager::setSingleModeRunnerId(const QString &id) |
528 | { |
529 | d->singleModeRunnerId = id; |
530 | d->loadSingleRunner(); |
531 | } |
532 | |
533 | QString RunnerManager::singleModeRunnerId() const |
534 | { |
535 | return d->singleModeRunnerId; |
536 | } |
537 | |
538 | bool RunnerManager::singleMode() const |
539 | { |
540 | return d->singleMode; |
541 | } |
542 | |
543 | void RunnerManager::setSingleMode(bool singleMode) |
544 | { |
545 | if (d->singleMode == singleMode) { |
546 | return; |
547 | } |
548 | |
549 | |
550 | Plasma::AbstractRunner *prevSingleRunner = d->currentSingleRunner; |
551 | d->singleMode = singleMode; |
552 | d->loadSingleRunner(); |
553 | d->singleMode = d->currentSingleRunner; |
554 | |
555 | if (prevSingleRunner != d->currentSingleRunner) { |
556 | if (d->prepped) { |
557 | matchSessionComplete(); |
558 | |
559 | if (d->singleMode) { |
560 | setupMatchSession(); |
561 | } |
562 | } |
563 | } |
564 | } |
565 | |
566 | QList<AbstractRunner *> RunnerManager::runners() const |
567 | { |
568 | return d->runners.values(); |
569 | } |
570 | |
571 | QStringList RunnerManager::singleModeAdvertisedRunnerIds() const |
572 | { |
573 | return d->advertiseSingleRunnerIds.keys(); |
574 | } |
575 | |
576 | QString RunnerManager::runnerName(const QString &id) const |
577 | { |
578 | if (runner(id)) { |
579 | return runner(id)->name(); |
580 | } else { |
581 | return d->advertiseSingleRunnerIds.value(id, QString()); |
582 | } |
583 | } |
584 | |
585 | RunnerContext* RunnerManager::searchContext() const |
586 | { |
587 | return &d->context; |
588 | } |
589 | |
590 | //Reordering is here so data is not reordered till strictly needed |
591 | QList<QueryMatch> RunnerManager::matches() const |
592 | { |
593 | return d->context.matches(); |
594 | } |
595 | |
596 | void RunnerManager::run(const QString &id) |
597 | { |
598 | run(d->context.match(id)); |
599 | } |
600 | |
601 | void RunnerManager::run(const QueryMatch &match) |
602 | { |
603 | if (!match.isEnabled()) { |
604 | return; |
605 | } |
606 | |
607 | //TODO: this function is not const as it may be used for learning |
608 | AbstractRunner *runner = match.runner(); |
609 | |
610 | foreach (FindMatchesJob *job, d->searchJobs) { |
611 | if (job->runner() == runner && !job->isFinished()) { |
612 | kDebug() << "deferred run" ; |
613 | d->deferredRun = match; |
614 | return; |
615 | } |
616 | } |
617 | |
618 | if (d->deferredRun.isValid()) { |
619 | d->deferredRun = QueryMatch(0); |
620 | } |
621 | |
622 | d->context.run(match); |
623 | } |
624 | |
625 | QList<QAction*> RunnerManager::actionsForMatch(const QueryMatch &match) |
626 | { |
627 | AbstractRunner *runner = match.runner(); |
628 | if (runner) { |
629 | return runner->actionsForMatch(match); |
630 | } |
631 | |
632 | return QList<QAction*>(); |
633 | } |
634 | |
635 | QMimeData * RunnerManager::mimeDataForMatch(const QString &id) const |
636 | { |
637 | return mimeDataForMatch(d->context.match(id)); |
638 | } |
639 | |
640 | |
641 | QMimeData * RunnerManager::mimeDataForMatch(const QueryMatch &match) const |
642 | { |
643 | AbstractRunner *runner = match.runner(); |
644 | QMimeData *mimeData; |
645 | if (runner && QMetaObject::invokeMethod( |
646 | runner, |
647 | "mimeDataForMatch" , Qt::DirectConnection, |
648 | Q_RETURN_ARG(QMimeData*, mimeData), |
649 | Q_ARG(const Plasma::QueryMatch *, &match) |
650 | )) { |
651 | return mimeData; |
652 | } |
653 | |
654 | return 0; |
655 | } |
656 | |
657 | KPluginInfo::List RunnerManager::listRunnerInfo(const QString &parentApp) |
658 | { |
659 | return PluginLoader::pluginLoader()->listRunnerInfo(parentApp); |
660 | } |
661 | |
662 | void RunnerManager::setupMatchSession() |
663 | { |
664 | d->teardownRequested = false; |
665 | |
666 | if (d->prepped) { |
667 | return; |
668 | } |
669 | |
670 | d->prepped = true; |
671 | if (d->singleMode) { |
672 | if (d->currentSingleRunner) { |
673 | emit d->currentSingleRunner->prepare(); |
674 | d->singleRunnerPrepped = true; |
675 | } |
676 | } else { |
677 | foreach (AbstractRunner *runner, d->runners) { |
678 | #ifdef MEASURE_PREPTIME |
679 | QTime t; |
680 | t.start(); |
681 | #endif |
682 | emit runner->prepare(); |
683 | #ifdef MEASURE_PREPTIME |
684 | kDebug() << t.elapsed() << runner->name(); |
685 | #endif |
686 | } |
687 | |
688 | d->allRunnersPrepped = true; |
689 | } |
690 | } |
691 | |
692 | void RunnerManager::matchSessionComplete() |
693 | { |
694 | if (!d->prepped) { |
695 | return; |
696 | } |
697 | |
698 | d->teardownRequested = true; |
699 | d->checkTearDown(); |
700 | } |
701 | |
702 | void RunnerManager::launchQuery(const QString &term) |
703 | { |
704 | launchQuery(term, QString()); |
705 | } |
706 | |
707 | void RunnerManager::launchQuery(const QString &untrimmedTerm, const QString &runnerName) |
708 | { |
709 | setupMatchSession(); |
710 | QString term = untrimmedTerm.trimmed(); |
711 | |
712 | setSingleModeRunnerId(runnerName); |
713 | setSingleMode(!runnerName.isEmpty()); |
714 | |
715 | if (term.isEmpty()) { |
716 | if (d->singleMode && d->currentSingleRunner && d->currentSingleRunner->defaultSyntax()) { |
717 | term = d->currentSingleRunner->defaultSyntax()->exampleQueries().first().remove(QRegExp(":q:" )); |
718 | } else { |
719 | reset(); |
720 | return; |
721 | } |
722 | } |
723 | |
724 | if (d->context.query() == term) { |
725 | // we already are searching for this! |
726 | return; |
727 | } |
728 | |
729 | if (d->singleMode && !d->currentSingleRunner) { |
730 | reset(); |
731 | return; |
732 | } |
733 | |
734 | if (d->runners.isEmpty()) { |
735 | d->loadRunners(); |
736 | } |
737 | |
738 | reset(); |
739 | // kDebug() << "runners searching for" << term << "on" << runnerName; |
740 | d->context.setQuery(term); |
741 | |
742 | QHash<QString, AbstractRunner*> runable; |
743 | |
744 | //if the name is not empty we will launch only the specified runner |
745 | if (d->singleMode && d->currentSingleRunner) { |
746 | runable.insert(QString(), d->currentSingleRunner); |
747 | d->context.setSingleRunnerQueryMode(true); |
748 | } else { |
749 | runable = d->runners; |
750 | } |
751 | |
752 | foreach (Plasma::AbstractRunner *r, runable) { |
753 | if (r->isMatchingSuspended()) { |
754 | continue; |
755 | } |
756 | |
757 | d->startJob(r); |
758 | } |
759 | |
760 | // Start timer to unblock slow runners |
761 | d->delayTimer.start(RunnerManagerPrivate::slowRunDelay); |
762 | } |
763 | |
764 | bool RunnerManager::execQuery(const QString &term) |
765 | { |
766 | return execQuery(term, QString()); |
767 | } |
768 | |
769 | bool RunnerManager::execQuery(const QString &untrimmedTerm, const QString &runnerName) |
770 | { |
771 | QString term = untrimmedTerm.trimmed(); |
772 | |
773 | if (term.isEmpty()) { |
774 | reset(); |
775 | return false; |
776 | } |
777 | |
778 | if (d->runners.isEmpty()) { |
779 | d->loadRunners(); |
780 | } |
781 | |
782 | if (d->context.query() == term) { |
783 | // we already are searching for this! |
784 | emit matchesChanged(d->context.matches()); |
785 | return false; |
786 | } |
787 | |
788 | reset(); |
789 | //kDebug() << "executing query about " << term << "on" << runnerName; |
790 | d->context.setQuery(term); |
791 | AbstractRunner *r = runner(runnerName); |
792 | |
793 | if (!r) { |
794 | //kDebug() << "failed to find the runner"; |
795 | return false; |
796 | } |
797 | |
798 | if ((r->ignoredTypes() & d->context.type()) != 0) { |
799 | //kDebug() << "ignored!"; |
800 | return false; |
801 | } |
802 | |
803 | r->performMatch(d->context); |
804 | //kDebug() << "succeeded with" << d->context.matches().count() << "results"; |
805 | emit matchesChanged(d->context.matches()); |
806 | return true; |
807 | } |
808 | |
809 | QString RunnerManager::query() const |
810 | { |
811 | return d->context.query(); |
812 | } |
813 | |
814 | void RunnerManager::reset() |
815 | { |
816 | // If ThreadWeaver is idle, it is safe to clear previous jobs |
817 | if (Weaver::instance()->isIdle()) { |
818 | qDeleteAll(d->searchJobs); |
819 | qDeleteAll(d->oldSearchJobs); |
820 | d->oldSearchJobs.clear(); |
821 | } else { |
822 | Q_FOREACH(FindMatchesJob *job, d->searchJobs) { |
823 | Weaver::instance()->dequeue(job); |
824 | } |
825 | d->oldSearchJobs += d->searchJobs; |
826 | } |
827 | |
828 | d->searchJobs.clear(); |
829 | |
830 | if (d->deferredRun.isEnabled()) { |
831 | //kDebug() << "job actually done, running now **************"; |
832 | QueryMatch tmpRun = d->deferredRun; |
833 | d->deferredRun = QueryMatch(0); |
834 | tmpRun.run(d->context); |
835 | } |
836 | |
837 | d->context.reset(); |
838 | } |
839 | |
840 | } // Plasma namespace |
841 | |
842 | #include "runnermanager.moc" |
843 | |