1/*
2 SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2020-2023 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "abstractrunner.h"
9#include "abstractrunner_p.h"
10
11#include <QHash>
12#include <QIcon>
13#include <QMimeData>
14#include <QRegularExpression>
15#include <QTimer>
16
17#include <KConfigGroup>
18#include <KLocalizedString>
19#include <KSharedConfig>
20
21#include "krunner_debug.h"
22
23namespace KRunner
24{
25AbstractRunner::AbstractRunner(QObject *parent, const KPluginMetaData &pluginMetaData)
26 : QObject(nullptr)
27 , d(new AbstractRunnerPrivate(this, pluginMetaData))
28{
29 // By now, runners who do "qobject_cast<Krunner::RunnerManager*>(parent)" should have saved the value
30 // By setting the parent to a nullptr, we are allowed to move the object to another thread
31 Q_ASSERT(parent);
32 setObjectName(pluginMetaData.pluginId()); // Only for debugging purposes
33
34 // Suspend matching while we initialize the runner. Once it is ready, the last query will be run
35 QTimer::singleShot(0, this, [this]() {
36 init();
37 // In case the runner didn't specify anything explicitly, we resume matching after the initialization
38 bool doesNotHaveExplicitSuspend = true;
39 {
40 QReadLocker l(&d->lock);
41 doesNotHaveExplicitSuspend = !d->suspendMatching.has_value();
42 }
43 if (doesNotHaveExplicitSuspend) {
44 suspendMatching(false);
45 }
46 });
47}
48
49AbstractRunner::~AbstractRunner() = default;
50
51KConfigGroup AbstractRunner::config() const
52{
53 KConfigGroup runners(KSharedConfig::openConfig(QStringLiteral("krunnerrc")), QStringLiteral("Runners"));
54 return runners.group(id());
55}
56
57void AbstractRunner::reloadConfiguration()
58{
59}
60
61void AbstractRunner::addSyntax(const RunnerSyntax &syntax)
62{
63 d->syntaxes.append(syntax);
64}
65
66void AbstractRunner::setSyntaxes(const QList<RunnerSyntax> &syntaxes)
67{
68 d->syntaxes = syntaxes;
69}
70
71QList<RunnerSyntax> AbstractRunner::syntaxes() const
72{
73 return d->syntaxes;
74}
75
76QMimeData *AbstractRunner::mimeDataForMatch(const QueryMatch &match)
77{
78 if (match.urls().isEmpty()) {
79 return nullptr;
80 }
81 QMimeData *result = new QMimeData();
82 result->setUrls(match.urls());
83 return result;
84}
85
86void AbstractRunner::run(const KRunner::RunnerContext & /*search*/, const KRunner::QueryMatch & /*action*/)
87{
88}
89
90QString AbstractRunner::name() const
91{
92 return d->translatedName;
93}
94
95QString AbstractRunner::id() const
96{
97 return d->runnerDescription.pluginId();
98}
99
100KPluginMetaData AbstractRunner::metadata() const
101{
102 return d->runnerDescription;
103}
104
105void AbstractRunner::init()
106{
107 reloadConfiguration();
108}
109
110bool AbstractRunner::isMatchingSuspended() const
111{
112 QReadLocker lock(&d->lock);
113 return d->suspendMatching.value_or(u: true);
114}
115
116void AbstractRunner::suspendMatching(bool suspend)
117{
118 QWriteLocker lock(&d->lock);
119 if (d->suspendMatching.has_value() && d->suspendMatching.value() == suspend) {
120 return;
121 }
122
123 d->suspendMatching = suspend;
124 if (!suspend) {
125 Q_EMIT matchingResumed();
126 }
127}
128
129int AbstractRunner::minLetterCount() const
130{
131 return d->minLetterCount;
132}
133
134void AbstractRunner::setMinLetterCount(int count)
135{
136 d->minLetterCount = count;
137}
138
139QRegularExpression AbstractRunner::matchRegex() const
140{
141 return d->matchRegex;
142}
143
144void AbstractRunner::setMatchRegex(const QRegularExpression &regex)
145{
146 d->matchRegex = regex;
147 d->hasMatchRegex = regex.isValid() && !regex.pattern().isEmpty();
148}
149
150void AbstractRunner::setTriggerWords(const QStringList &triggerWords)
151{
152 int minTriggerWordLetters = 0;
153 QString constructedRegex = QStringLiteral("^");
154 for (const QString &triggerWord : triggerWords) {
155 // We want to link them with an or
156 if (constructedRegex.length() > 1) {
157 constructedRegex += QLatin1Char('|');
158 }
159 constructedRegex += QRegularExpression::escape(triggerWord);
160 if (minTriggerWordLetters == 0 || triggerWord.length() < minTriggerWordLetters) {
161 minTriggerWordLetters = triggerWord.length();
162 }
163 }
164 // If we can reject the query because of the length we don't need the regex
165 setMinLetterCount(minTriggerWordLetters);
166 setMatchRegex(QRegularExpression(constructedRegex));
167}
168
169bool AbstractRunner::hasMatchRegex() const
170{
171 return d->hasMatchRegex;
172}
173
174void AbstractRunner::matchInternal(KRunner::RunnerContext context)
175{
176 if (context.isValid()) { // Otherwise, we would just waste resources
177 match(context);
178 }
179 Q_EMIT matchInternalFinished(context.runnerJobId(this));
180}
181// Suspend the runner while reloading the config
182void AbstractRunner::reloadConfigurationInternal()
183{
184 bool isSuspended = isMatchingSuspended();
185 suspendMatching(suspend: true);
186 reloadConfiguration();
187 suspendMatching(suspend: isSuspended);
188}
189
190} // KRunner namespace
191
192#include "moc_abstractrunner.cpp"
193

source code of krunner/src/abstractrunner.cpp