1/*
2 SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "runnercontext.h"
9
10#include <cmath>
11
12#include <QPointer>
13#include <QReadWriteLock>
14#include <QRegularExpression>
15#include <QSharedData>
16#include <QUrl>
17
18#include <KConfigGroup>
19#include <KShell>
20
21#include "abstractrunner.h"
22#include "abstractrunner_p.h"
23#include "querymatch.h"
24#include "runnermanager.h"
25
26namespace KRunner
27{
28class RunnerContextPrivate : public QSharedData
29{
30public:
31 explicit RunnerContextPrivate(RunnerManager *manager)
32 : QSharedData()
33 , m_manager(manager)
34 {
35 }
36
37 RunnerContextPrivate(const RunnerContextPrivate &p)
38 : QSharedData(p)
39 , m_manager(p.m_manager)
40 {
41 }
42
43 ~RunnerContextPrivate() = default;
44
45 void invalidate()
46 {
47 m_isValid = false;
48 }
49
50 void addMatch(const QueryMatch &match)
51 {
52 if (match.runner() && match.runner()->d->hasUniqueResults) {
53 if (uniqueIds.contains(key: match.id())) {
54 const QueryMatch &existentMatch = uniqueIds.value(key: match.id());
55 if (existentMatch.runner() && existentMatch.runner()->d->hasWeakResults) {
56 // There is an existing match with the same ID and we are allowed to replace it
57 matches.removeOne(t: existentMatch);
58 matches.append(t: match);
59 }
60 } else {
61 // There is no existing match with the same id
62 uniqueIds.insert(key: match.id(), value: match);
63 matches.append(t: match);
64 }
65 } else {
66 // Runner has the unique results property not set
67 matches.append(t: match);
68 }
69 }
70
71 void matchesChanged()
72 {
73 if (m_manager) {
74 QMetaObject::invokeMethod(obj: m_manager, member: "onMatchesChanged");
75 }
76 }
77
78 QReadWriteLock lock;
79 QPointer<RunnerManager> m_manager;
80 bool m_isValid = true;
81 QList<QueryMatch> matches;
82 QString term;
83 bool singleRunnerQueryMode = false;
84 bool shouldIgnoreCurrentMatchForHistory = false;
85 QMap<QString, QueryMatch> uniqueIds;
86 QString requestedText;
87 int requestedCursorPosition = 0;
88 qint64 queryStartTs = 0;
89};
90
91RunnerContext::RunnerContext(RunnerManager *manager)
92 : d(new RunnerContextPrivate(manager))
93{
94}
95
96// copy ctor
97RunnerContext::RunnerContext(const RunnerContext &other)
98{
99 QReadLocker locker(&other.d->lock);
100 d = other.d;
101}
102
103RunnerContext::~RunnerContext() = default;
104
105RunnerContext &RunnerContext::operator=(const RunnerContext &other)
106{
107 if (this->d == other.d) {
108 return *this;
109 }
110
111 auto oldD = d; // To avoid the old ptr getting destroyed while the mutex is locked
112 QWriteLocker locker(&d->lock);
113 QReadLocker otherLocker(&other.d->lock);
114 d = other.d;
115 return *this;
116}
117
118/*!
119 * Resets the search term for this object.
120 * This removes all current matches in the process and
121 * turns off single runner query mode.
122 * Copies of this object that are used by runner are invalidated
123 * and adding matches will be a noop.
124 */
125void RunnerContext::reset()
126{
127 {
128 QWriteLocker locker(&d->lock);
129 // We will detach if we are a copy of someone. But we will reset
130 // if we are the 'main' context others copied from. Resetting
131 // one RunnerContext makes all the copies obsolete.
132
133 // We need to mark the q pointer of the detached RunnerContextPrivate
134 // as dirty on detach to avoid receiving results for old queries
135 d->invalidate();
136 }
137
138 d.detach();
139 // But out detached version is valid!
140 d->m_isValid = true;
141
142 // we still have to remove all the matches, since if the
143 // ref count was 1 (e.g. only the RunnerContext is using
144 // the dptr) then we won't get a copy made
145 d->matches.clear();
146 d->term.clear();
147 d->matchesChanged();
148
149 d->uniqueIds.clear();
150 d->singleRunnerQueryMode = false;
151 d->shouldIgnoreCurrentMatchForHistory = false;
152}
153
154void RunnerContext::setQuery(const QString &term)
155{
156 if (!this->query().isEmpty()) {
157 reset();
158 }
159
160 if (term.isEmpty()) {
161 return;
162 }
163
164 d->requestedText.clear(); // Invalidate this field whenever the query changes
165 d->term = term;
166}
167
168QString RunnerContext::query() const
169{
170 // the query term should never be set after
171 // a search starts. in fact, reset() ensures this
172 // and setQuery(QString) calls reset()
173 return d->term;
174}
175
176bool RunnerContext::isValid() const
177{
178 QReadLocker locker(&d->lock);
179 return d->m_isValid;
180}
181
182bool RunnerContext::addMatches(const QList<QueryMatch> &matches)
183{
184 if (matches.isEmpty() || !isValid()) {
185 // Bail out if the query is empty or the qptr is dirty
186 return false;
187 }
188
189 {
190 QWriteLocker locker(&d->lock);
191 for (const QueryMatch &match : matches) {
192 d->addMatch(match);
193 }
194 }
195 d->matchesChanged();
196
197 return true;
198}
199
200bool RunnerContext::addMatch(const QueryMatch &match)
201{
202 return addMatches(matches: {match});
203}
204
205QList<QueryMatch> RunnerContext::matches() const
206{
207 QReadLocker locker(&d->lock);
208 QList<QueryMatch> matches = d->matches;
209 return matches;
210}
211
212void RunnerContext::requestQueryStringUpdate(const QString &text, int cursorPosition) const
213{
214 d->requestedText = text;
215 d->requestedCursorPosition = cursorPosition;
216}
217
218void RunnerContext::setSingleRunnerQueryMode(bool enabled)
219{
220 d->singleRunnerQueryMode = enabled;
221}
222
223bool RunnerContext::singleRunnerQueryMode() const
224{
225 return d->singleRunnerQueryMode;
226}
227
228void RunnerContext::ignoreCurrentMatchForHistory() const
229{
230 d->shouldIgnoreCurrentMatchForHistory = true;
231}
232
233bool RunnerContext::shouldIgnoreCurrentMatchForHistory() const
234{
235 return d->shouldIgnoreCurrentMatchForHistory;
236}
237
238void RunnerContext::restore([[maybe_unused]] const KConfigGroup &config)
239{
240 // TODO KF7: Drop
241}
242
243void RunnerContext::save([[maybe_unused]] KConfigGroup &config)
244{
245 // TODO KF7: Drop
246}
247
248void RunnerContext::increaseLaunchCount([[maybe_unused]] const QueryMatch &match)
249{
250 // TODO KF7: Drop
251}
252
253QString RunnerContext::requestedQueryString() const
254{
255 return d->requestedText;
256}
257int RunnerContext::requestedCursorPosition() const
258{
259 return d->requestedCursorPosition;
260}
261
262void RunnerContext::setJobStartTs(qint64 queryStartTs)
263{
264 d->queryStartTs = queryStartTs;
265}
266QString RunnerContext::runnerJobId(AbstractRunner *runner) const
267{
268 return QLatin1String("%1-%2-%3").arg(args: runner->id(), args: query(), args: QString::number(d->queryStartTs));
269}
270
271} // KRunner namespace
272

source code of krunner/src/runnercontext.cpp