Changeset View
Changeset View
Standalone View
Standalone View
runners/baloo/baloosearchrunner.cpp
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | * This file is part of the KDE Baloo Project | 2 | * This file is part of the KDE Baloo Project | ||
3 | * Copyright (C) 2014 Vishesh Handa <me@vhanda.in> | 3 | * Copyright (C) 2014 Vishesh Handa <me@vhanda.in> | ||
4 | * Copyright (C) 2017 David Edmundson <davidedmundson@kde.org> | ||||
4 | * | 5 | * | ||
5 | * This library is free software; you can redistribute it and/or | 6 | * This library is free software; you can redistribute it and/or | ||
6 | * modify it under the terms of the GNU Lesser General Public | 7 | * modify it under the terms of the GNU Lesser General Public | ||
7 | * License as published by the Free Software Foundation; either | 8 | * License as published by the Free Software Foundation; either | ||
8 | * version 2.1 of the License, or (at your option) any later version. | 9 | * version 2.1 of the License, or (at your option) any later version. | ||
9 | * | 10 | * | ||
10 | * This library is distributed in the hope that it will be useful, | 11 | * This library is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
Show All 12 Lines | |||||
24 | #include <QIcon> | 25 | #include <QIcon> | ||
25 | #include <QDir> | 26 | #include <QDir> | ||
26 | #include <KRun> | 27 | #include <KRun> | ||
27 | #include <KRunner/QueryMatch> | 28 | #include <KRunner/QueryMatch> | ||
28 | #include <KLocalizedString> | 29 | #include <KLocalizedString> | ||
29 | #include <QMimeDatabase> | 30 | #include <QMimeDatabase> | ||
30 | #include <QTimer> | 31 | #include <QTimer> | ||
31 | #include <QMimeData> | 32 | #include <QMimeData> | ||
33 | #include <QApplication> | ||||
34 | #include <QDBusConnection> | ||||
35 | #include <QTimer> | ||||
32 | 36 | | |||
33 | #include <Baloo/Query> | 37 | #include <Baloo/Query> | ||
34 | #include <Baloo/IndexerConfig> | 38 | #include <Baloo/IndexerConfig> | ||
35 | 39 | | |||
36 | #include <KIO/OpenFileManagerWindowJob> | 40 | #include <KIO/OpenFileManagerWindowJob> | ||
37 | 41 | | |||
42 | #include "dbusutils_p.h" | ||||
43 | #include "krunner1adaptor.h" | ||||
44 | | ||||
38 | static const QString s_openParentDirId = QStringLiteral("openParentDir"); | 45 | static const QString s_openParentDirId = QStringLiteral("openParentDir"); | ||
39 | 46 | | |||
40 | SearchRunner::SearchRunner(QObject* parent, const QVariantList& args) | 47 | int main (int argc, char **argv) | ||
41 | : Plasma::AbstractRunner(parent, args) | | |||
42 | { | 48 | { | ||
49 | Baloo::IndexerConfig config; | ||||
50 | if (!config.fileIndexingEnabled()) { | ||||
51 | return -1; | ||||
broulik: Won't we end up restarting this service over and over again whilst typing when Baloo is… | |||||
If baloo is disabled, but the runner is enabled, yes. Not a huge issue, not ideal either. Brainstorming options:
(most invasiave, but gives the best UI as we can even hide the option for this entry)
davidedmundson: If baloo is disabled, but the runner is enabled, yes.
Not a huge issue, not ideal either. | |||||
43 | } | 52 | } | ||
44 | 53 | QApplication::setQuitOnLastWindowClosed(false); | |||
45 | SearchRunner::SearchRunner(QObject* parent, const QString& serviceId) | 54 | QApplication app(argc, argv); //KRun needs widgets for error message boxes | ||
46 | : Plasma::AbstractRunner(parent, serviceId) | 55 | SearchRunner r; | ||
47 | { | 56 | return app.exec(); | ||
48 | } | 57 | } | ||
49 | 58 | | |||
50 | void SearchRunner::init() | 59 | SearchRunner::SearchRunner(QObject* parent) | ||
60 | : QObject(parent), | ||||
61 | m_timer(new QTimer(this)) | ||||
51 | { | 62 | { | ||
52 | Plasma::RunnerSyntax syntax(QStringLiteral(":q"), i18n("Search through files, emails and contacts")); | | |||
53 | 63 | | |||
KRun may spawn QMessageBox and the like (which makes me wonder if we really should make it not desktop settings aware) broulik: `KRun` may spawn `QMessageBox` and the like (which makes me wonder if we really should make it… | |||||
54 | addAction(s_openParentDirId, QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder")); | 64 | m_timer->setSingleShot(true); | ||
65 | connect(m_timer, &QTimer::timeout, this, &SearchRunner::performMatch); | ||||
broulik: This is a static and needs to be done before `QApplication` is constructed. | |||||
66 | | ||||
67 | new Krunner1Adaptor(this); | ||||
68 | qDBusRegisterMetaType<RemoteMatch>(); | ||||
69 | qDBusRegisterMetaType<RemoteMatches>(); | ||||
70 | qDBusRegisterMetaType<RemoteAction>(); | ||||
71 | qDBusRegisterMetaType<RemoteActions>(); | ||||
72 | QDBusConnection::sessionBus().registerService("org.kde.runners.baloo"); | ||||
73 | QDBusConnection::sessionBus().registerObject("/runner", this); | ||||
55 | } | 74 | } | ||
56 | 75 | | |||
Maybe we should name it org.kde.runners.baloo so if we group it with others in the future? broulik: Maybe we should name it `org.kde.runners.baloo` so if we group it with others in the future? | |||||
57 | SearchRunner::~SearchRunner() | 76 | SearchRunner::~SearchRunner() | ||
58 | { | 77 | { | ||
59 | } | 78 | } | ||
60 | 79 | | |||
61 | 80 | RemoteActions SearchRunner::Actions() | |||
62 | QStringList SearchRunner::categories() const | | |||
63 | { | 81 | { | ||
64 | QStringList list; | 82 | return RemoteActions({RemoteAction{ | ||
65 | list << i18n("Audio") | 83 | s_openParentDirId, | ||
66 | << i18n("Image") | 84 | i18n("Open Containing Folder"), | ||
67 | << i18n("Document") | 85 | QStringLiteral("document-open-folder") | ||
68 | << i18n("Video") | 86 | }}); | ||
69 | << i18n("Folder"); | | |||
70 | | ||||
71 | return list; | | |||
72 | } | 87 | } | ||
73 | 88 | | |||
74 | QIcon SearchRunner::categoryIcon(const QString& category) const | 89 | RemoteMatches SearchRunner::Match(const QString& searchTerm) | ||
75 | { | 90 | { | ||
76 | if (category == i18n("Audio")) { | 91 | setDelayedReply(true); | ||
77 | return QIcon::fromTheme(QStringLiteral("audio")); | 92 | | ||
78 | } else if (category == i18n("Image")) { | 93 | if (m_lastRequest.type() != QDBusMessage::InvalidMessage) { | ||
79 | return QIcon::fromTheme(QStringLiteral("image")); | 94 | QDBusConnection::sessionBus().send(m_lastRequest.createReply(QVariantList())); | ||
80 | } else if (category == i18n("Document")) { | | |||
81 | return QIcon::fromTheme(QStringLiteral("application-pdf")); | | |||
82 | } else if (category == i18n("Video")) { | | |||
83 | return QIcon::fromTheme(QStringLiteral("video")); | | |||
84 | } else if (category == i18n("Folder")) { | | |||
85 | return QIcon::fromTheme(QStringLiteral("folder")); | | |||
86 | } | 95 | } | ||
87 | 96 | | |||
88 | return Plasma::AbstractRunner::categoryIcon(category); | 97 | m_lastRequest = message(); | ||
98 | m_searchTerm = searchTerm; | ||||
99 | | ||||
100 | // Baloo (as of 2014-11-20) is single threaded. It has an internal mutex which results in | ||||
101 | // queries being queued one after another. Also, Baloo is really really slow for small queries | ||||
102 | // For example - on my SSD, it takes about 1.4 seconds for 'f' with an SSD. | ||||
103 | // When searching for "fire", it results in "f", "fi", "fir" and then "fire" being searched | ||||
104 | // We're therefore hacking around this by having a small delay for very short queries so that | ||||
105 | // they do not get queued internally in Baloo | ||||
106 | // | ||||
107 | int waitTimeMs = 0; | ||||
108 | | ||||
109 | if (searchTerm.length() <= 3) { | ||||
110 | waitTimeMs = 100; | ||||
89 | } | 111 | } | ||
112 | //we're still using the event delayed call even when the length is < 3 so that if we have multiple Match() calls in our DBus queue, we only process the last one | ||||
113 | m_timer->start(waitTimeMs); | ||||
90 | 114 | | |||
91 | QList<Plasma::QueryMatch> SearchRunner::match(Plasma::RunnerContext& context, const QString& type, | 115 | return RemoteMatches(); | ||
92 | const QString& category) | | |||
93 | { | | |||
94 | Baloo::IndexerConfig config; | | |||
95 | if (!config.fileIndexingEnabled()) { | | |||
96 | return QList<Plasma::QueryMatch>(); | | |||
97 | } | 116 | } | ||
98 | 117 | | |||
99 | if (!context.isValid()) | 118 | void SearchRunner::performMatch() | ||
100 | return QList<Plasma::QueryMatch>(); | 119 | { | ||
120 | RemoteMatches matches; | ||||
121 | matches << matchInternal(m_searchTerm, QStringLiteral("Audio"), i18n("Audio")); | ||||
122 | matches << matchInternal(m_searchTerm, QStringLiteral("Image"), i18n("Image")); | ||||
123 | matches << matchInternal(m_searchTerm, QStringLiteral("Document"), i18n("Document")); | ||||
124 | matches << matchInternal(m_searchTerm, QStringLiteral("Video"), i18n("Video")); | ||||
125 | matches << matchInternal(m_searchTerm, QStringLiteral("Folder"), i18n("Folder")); | ||||
101 | 126 | | |||
102 | const QStringList categories = context.enabledCategories(); | 127 | QDBusConnection::sessionBus().send(m_lastRequest.createReply(QVariant::fromValue(matches))); | ||
103 | if (!categories.isEmpty() && !categories.contains(category)) | 128 | m_lastRequest = QDBusMessage(); | ||
104 | return QList<Plasma::QueryMatch>(); | 129 | } | ||
105 | 130 | | |||
131 | RemoteMatches SearchRunner::matchInternal(const QString& searchTerm, const QString &type, const QString &category) | ||||
132 | { | ||||
106 | Baloo::Query query; | 133 | Baloo::Query query; | ||
107 | query.setSearchString(context.query()); | 134 | query.setSearchString(searchTerm); | ||
108 | query.setType(type); | 135 | query.setType(type); | ||
109 | query.setLimit(10); | 136 | query.setLimit(10); | ||
110 | 137 | | |||
111 | Baloo::ResultIterator it = query.exec(); | 138 | Baloo::ResultIterator it = query.exec(); | ||
112 | 139 | | |||
113 | QList<Plasma::QueryMatch> matches; | 140 | RemoteMatches matches; | ||
114 | 141 | | |||
115 | QMimeDatabase mimeDb; | 142 | QMimeDatabase mimeDb; | ||
116 | 143 | | |||
117 | // KRunner is absolutely retarded and allows plugins to set the global | 144 | // KRunner is absolutely daft and allows plugins to set the global | ||
118 | // relevance levels. so Baloo should not set the relevance of results too | 145 | // relevance levels. so Baloo should not set the relevance of results too | ||
119 | // high because then Applications will often appear after if the application | 146 | // high because then Applications will often appear after if the application | ||
120 | // runner has not a higher relevance. So stupid. | 147 | // runner has not a higher relevance. So stupid. | ||
121 | // Each runner plugin should not have to know about the others. | 148 | // Each runner plugin should not have to know about the others. | ||
122 | // Anyway, that's why we're starting with .75 | 149 | // Anyway, that's why we're starting with .75 | ||
123 | float relevance = .75; | 150 | float relevance = .75; | ||
124 | while (context.isValid() && it.next()) { | 151 | while (it.next()) { | ||
125 | Plasma::QueryMatch match(this); | 152 | RemoteMatch match; | ||
126 | QString localUrl = it.filePath(); | 153 | QString localUrl = it.filePath(); | ||
127 | const QUrl url = QUrl::fromLocalFile(localUrl); | 154 | const QUrl url = QUrl::fromLocalFile(localUrl); | ||
128 | 155 | match.id = it.filePath(); | |||
129 | QString iconName = mimeDb.mimeTypeForFile(localUrl).iconName(); | 156 | match.text = url.fileName(); | ||
130 | match.setIconName(iconName); | 157 | match.iconName = mimeDb.mimeTypeForFile(localUrl).iconName(); | ||
131 | match.setId(it.filePath()); | 158 | match.relevance = relevance; | ||
132 | match.setText(url.fileName()); | 159 | match.type = Plasma::QueryMatch::PossibleMatch; | ||
133 | match.setData(url); | 160 | QVariantMap properties; | ||
134 | match.setType(Plasma::QueryMatch::PossibleMatch); | | |||
135 | match.setMatchCategory(category); | | |||
136 | match.setRelevance(relevance); | | |||
137 | relevance -= 0.05; | | |||
138 | 161 | | |||
139 | QString folderPath = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); | 162 | QString folderPath = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); | ||
140 | if (folderPath.startsWith(QDir::homePath())) { | 163 | if (folderPath.startsWith(QDir::homePath())) { | ||
141 | folderPath.replace(0, QDir::homePath().length(), QStringLiteral("~")); | 164 | folderPath.replace(0, QDir::homePath().length(), QStringLiteral("~")); | ||
142 | } | 165 | } | ||
143 | match.setSubtext(folderPath); | | |||
144 | | ||||
145 | matches << match; | | |||
146 | } | | |||
147 | 166 | | |||
148 | return matches; | 167 | properties["urls"] = QStringList({url.toEncoded()}); | ||
149 | } | 168 | properties["subtext"] = folderPath; | ||
169 | properties["category"] = category; | ||||
150 | 170 | | |||
151 | void SearchRunner::match(Plasma::RunnerContext& context) | 171 | match.properties = properties; | ||
152 | { | 172 | relevance -= 0.05; | ||
153 | const QString text = context.query(); | | |||
154 | // | | |||
155 | // Baloo (as of 2014-11-20) is single threaded. It has an internal mutex which results in | | |||
156 | // queries being queued one after another. Also, Baloo is really really slow for small queries | | |||
157 | // For example - on my SSD, it takes about 1.4 seconds for 'f' with an SSD. | | |||
158 | // When searching for "fire", it results in "f", "fi", "fir" and then "fire" being searched | | |||
159 | // We're therefore hacking around this by having a small delay for very short queries so that | | |||
160 | // they do not get queued internally in Baloo | | |||
161 | // | | |||
162 | if (text.length() <= 3) { | | |||
163 | QEventLoop loop; | | |||
164 | QTimer timer; | | |||
165 | connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); | | |||
166 | timer.setSingleShot(true); | | |||
167 | timer.start(100); | | |||
168 | loop.exec(); | | |||
169 | 173 | | |||
170 | if (!context.isValid()) { | 174 | matches << match; | ||
171 | return; | | |||
172 | } | | |||
173 | } | 175 | } | ||
174 | 176 | | |||
175 | QList<Plasma::QueryMatch> matches; | 177 | return matches; | ||
176 | matches << match(context, QStringLiteral("Audio"), i18n("Audio")); | | |||
177 | matches << match(context, QStringLiteral("Image"), i18n("Image")); | | |||
178 | matches << match(context, QStringLiteral("Document"), i18n("Document")); | | |||
179 | matches << match(context, QStringLiteral("Video"), i18n("Video")); | | |||
180 | matches << match(context, QStringLiteral("Folder"), i18n("Folder")); | | |||
181 | | ||||
182 | context.addMatches(matches); | | |||
183 | } | 178 | } | ||
184 | 179 | | |||
185 | void SearchRunner::run(const Plasma::RunnerContext&, const Plasma::QueryMatch& match) | 180 | void SearchRunner::Run(const QString& id, const QString& actionId) | ||
186 | { | 181 | { | ||
187 | const QUrl url = match.data().toUrl(); | 182 | const QUrl url = QUrl::fromLocalFile(id); | ||
188 | 183 | if (actionId == s_openParentDirId) { | |||
189 | if (match.selectedAction() && match.selectedAction() == action(s_openParentDirId)) { | | |||
190 | KIO::highlightInFileManager({url}); | 184 | KIO::highlightInFileManager({url}); | ||
191 | return; | 185 | return; | ||
192 | } | 186 | } | ||
193 | 187 | | |||
194 | new KRun(url, 0); | 188 | new KRun(url, 0); | ||
195 | } | 189 | } | ||
196 | 190 | | |||
197 | QList<QAction *> SearchRunner::actionsForMatch(const Plasma::QueryMatch &match) | | |||
198 | { | | |||
199 | Q_UNUSED(match) | | |||
200 | | ||||
201 | return {action(s_openParentDirId)}; | | |||
202 | } | | |||
203 | | ||||
204 | QMimeData *SearchRunner::mimeDataForMatch(const Plasma::QueryMatch &match) | | |||
205 | { | | |||
206 | QMimeData *result = new QMimeData(); | | |||
207 | result->setUrls({match.data().toUrl()}); | | |||
208 | return result; | | |||
209 | } | | |||
210 | | ||||
211 | K_EXPORT_PLASMA_RUNNER(baloosearchrunner, SearchRunner) | | |||
212 | | ||||
213 | #include "baloosearchrunner.moc" | 191 | #include "baloosearchrunner.moc" |
Won't we end up restarting this service over and over again whilst typing when Baloo is dlsabled? Not sure how bad that is, though.