1 // Copyright (C) 2014-2018 Manuel Schneider
2
3 #include <QDebug>
4 #include <QString>
5 #include <QVariant>
6 #include <QtConcurrent>
7 #include <algorithm>
8 #include <chrono>
9 #include <functional>
10 #include <utility>
11 #include "albert/action.h"
12 #include "albert/fallbackprovider.h"
13 #include "albert/item.h"
14 #include "albert/queryhandler.h"
15 #include "albert/util/itemroles.h"
16 #include "matchcompare.h"
17 #include "queryexecution.h"
18 using namespace std;
19 using namespace chrono;
20
21 namespace {
22 const int FETCH_SIZE = 20;
23 }
24
25
26 /** ***************************************************************************/
QueryExecution(const set<QueryHandler * > & queryHandlers,const set<FallbackProvider * > & fallbackProviders,const QString & queryString,std::map<QString,uint> scores,bool fetchIncrementally)27 Core::QueryExecution::QueryExecution(const set<QueryHandler*> & queryHandlers,
28 const set<FallbackProvider*> &fallbackProviders,
29 const QString &queryString,
30 std::map<QString,uint> scores,
31 bool fetchIncrementally) {
32
33 fetchIncrementally_ = fetchIncrementally;
34 query_.rawString_ = queryString;
35 query_.string_ = queryString;
36 query_.scores_ = move(scores);
37 stats.input = queryString;
38
39 // Get fallbacks
40 if ( !query_.string_.trimmed().isEmpty() )
41 for ( FallbackProvider *fallbackProvider : fallbackProviders )
42 for ( shared_ptr<Item> & item : fallbackProvider->fallbacks(queryString) )
43 fallbacks_.emplace_back(move(item), 0);
44
45 // Run with a single handler if the trigger matches
46 for ( QueryHandler *handler : queryHandlers ) {
47 for ( const QString& trigger : handler->triggers() ) {
48 if ( !trigger.isNull() && queryString.startsWith(trigger) ) {
49 query_.trigger_ = trigger;
50 query_.string_ = queryString.mid(trigger.size());
51 ( handler->executionType()==QueryHandler::ExecutionType::Batch )
52 ? batchHandlers_.insert(handler)
53 : realtimeHandlers_.insert(handler);
54 return;
55 }
56 }
57 }
58
59 // Else run all batched handlers
60 for ( QueryHandler *queryHandler : queryHandlers )
61 if ( queryHandler->executionType()==QueryHandler::ExecutionType::Batch )
62 batchHandlers_.insert(queryHandler);
63 }
64
65
66 /** ***************************************************************************/
~QueryExecution()67 Core::QueryExecution::~QueryExecution() {
68
69 }
70
71
72 /** ***************************************************************************/
query()73 const Core::Query *Core::QueryExecution::query() {
74 return &query_;
75 }
76
77
78 /** ***************************************************************************/
state() const79 const Core::QueryExecution::State &Core::QueryExecution::state() const {
80 return state_;
81 }
82
83
84 /** ***************************************************************************/
setState(State state)85 void Core::QueryExecution::setState(State state) {
86 state_ = state;
87 if (state_ == State::Finished)
88 stats.end = system_clock::now();
89 emit stateChanged(state_);
90 }
91
92
93 /** ***************************************************************************/
run()94 void Core::QueryExecution::run() {
95
96 setState(State::Running);
97
98 stats.start = system_clock::now();
99
100 if ( !batchHandlers_.empty() )
101 return runBatchHandlers();
102
103 emit resultsReady(this);
104
105 if ( !realtimeHandlers_.empty() )
106 return runRealtimeHandlers();
107
108 setState(State::Finished);
109 }
110
111
112 /** ***************************************************************************/
cancel()113 void Core::QueryExecution::cancel() {
114 futureWatcher_.disconnect();
115 future_.cancel();
116 query_.isValid_ = false;
117 stats.cancelled = true;
118 }
119
120
121 /** ***************************************************************************/
runBatchHandlers()122 void Core::QueryExecution::runBatchHandlers() {
123
124 // Call onBatchHandlersFinished when all handlers finished
125 connect(&futureWatcher_, &QFutureWatcher<pair<QueryHandler*,uint>>::finished,
126 this, &QueryExecution::onBatchHandlersFinished);
127
128 // Run the handlers concurrently and measure the runtimes
129 function<pair<QueryHandler*,uint>(QueryHandler*)> func = [this](QueryHandler* queryHandler){
130 system_clock::time_point start = system_clock::now();
131 queryHandler->handleQuery(&query_);
132 long duration = duration_cast<microseconds>(system_clock::now()-start).count();
133 qDebug() << qPrintable(QString("TIME: %1 µs MATCHES [%2]").arg(duration, 6).arg(queryHandler->id));
134 return make_pair(queryHandler, static_cast<int>(duration));
135 };
136 future_ = QtConcurrent::mapped(batchHandlers_.begin(), batchHandlers_.end(), func);
137 futureWatcher_.setFuture(future_);
138 }
139
140
141 /** ***************************************************************************/
onBatchHandlersFinished()142 void Core::QueryExecution::onBatchHandlersFinished() {
143
144 // Save the runtimes of the current future
145 for ( auto it = future_.begin(); it != future_.end(); ++it )
146 stats.runtimes.emplace(it->first->id, it->second);
147
148 // Move the items of the "pending results" into "results"
149 query_.mutex_.lock();
150 swap(query_.results_, results_);
151 query_.mutex_.unlock();
152
153 // Sort the results
154 if (query_.trigger_.isNull()){
155 if ( fetchIncrementally_ ) {
156 int sortUntil = min(sortedItems_ + FETCH_SIZE, static_cast<int>(results_.size()));
157 partial_sort(results_.begin() + sortedItems_, results_.begin() + sortUntil,
158 results_.end(), MatchCompare());
159 sortedItems_ = sortUntil;
160 }
161 else
162 std::sort(results_.begin(), results_.end(), MatchCompare());
163 }
164
165 if ( realtimeHandlers_.empty() ){
166 if( results_.empty() && !query_.rawString_.isEmpty() ){
167 results_ = fallbacks_;
168 sortedItems_ = static_cast<int>(fallbacks_.size());
169 fetchIncrementally_ = false;
170 }
171 setState(State::Finished);
172 }
173 else
174 runRealtimeHandlers();
175
176 emit resultsReady(this);
177 }
178
179
180 /** ***************************************************************************/
runRealtimeHandlers()181 void Core::QueryExecution::runRealtimeHandlers() {
182
183 // Call onRealtimeHandlersFinsished when all handlers finished
184 disconnect(&futureWatcher_, &QFutureWatcher<pair<QueryHandler*,uint>>::finished,
185 this, &QueryExecution::onBatchHandlersFinished);
186
187 connect(&futureWatcher_, &QFutureWatcher<pair<QueryHandler*,uint>>::finished,
188 this, &QueryExecution::onRealtimeHandlersFinsished);
189
190 // Run the handlers concurrently and measure the runtimes
191 function<pair<QueryHandler*,uint>(QueryHandler*)> func = [this](QueryHandler* queryHandler){
192 system_clock::time_point start = system_clock::now();
193 queryHandler->handleQuery(&query_);
194 long duration = duration_cast<microseconds>(system_clock::now()-start).count();
195 qDebug() << qPrintable(QString("TIME: %1 µs MATCHES REALTIME [%2]").arg(duration, 6).arg(queryHandler->id));
196 return make_pair(queryHandler, static_cast<int>(duration));
197 };
198 future_ = QtConcurrent::mapped(realtimeHandlers_.begin(), realtimeHandlers_.end(), func);
199 futureWatcher_.setFuture(future_);
200
201 // Insert pending results every 50 milliseconds
202 connect(&fiftyMsTimer_, &QTimer::timeout, this, &QueryExecution::insertPendingResults);
203 fiftyMsTimer_.start(50);
204 }
205
206
207 /** ***************************************************************************/
onRealtimeHandlersFinsished()208 void Core::QueryExecution::onRealtimeHandlersFinsished() {
209
210 // Save the runtimes of the current future
211 for ( auto it = future_.begin(); it != future_.end(); ++it )
212 stats.runtimes.emplace(it->first->id, it->second);
213
214 // Finally done
215 fiftyMsTimer_.stop();
216 fiftyMsTimer_.disconnect();
217 insertPendingResults();
218
219 if( results_.empty() && !query_.rawString_.isEmpty() ){
220 beginInsertRows(QModelIndex(), 0, static_cast<int>(fallbacks_.size()-1));
221 results_ = fallbacks_;
222 endInsertRows();
223 fetchIncrementally_ = false;
224 }
225 setState(State::Finished);
226 }
227
228
229 /** ***************************************************************************/
insertPendingResults()230 void Core::QueryExecution::insertPendingResults() {
231
232 if(query_.results_.size()) {
233 QMutexLocker lock(&query_.mutex_);
234
235 // When fetching incrementally, only emit if this is in the fetched range
236 if ( !fetchIncrementally_ || sortedItems_ == static_cast<int>(results_.size()) ){
237 beginInsertRows(QModelIndex(),
238 static_cast<int>(results_.size()),
239 static_cast<int>(results_.size() + query_.results_.size() - 1));
240 results_.reserve(results_.size() + query_.results_.size());
241 move(query_.results_.begin(), query_.results_.end(), back_inserter(results_));
242 endInsertRows();
243 } else {
244 results_.reserve(results_.size() + query_.results_.size());
245 move(query_.results_.begin(), query_.results_.end(), back_inserter(results_));
246 }
247 query_.results_.clear();
248 }
249 }
250
251
252 /** ***************************************************************************/
rowCount(const QModelIndex &) const253 int Core::QueryExecution::rowCount(const QModelIndex &) const {
254 return query_.trigger_.isNull() && fetchIncrementally_ ? sortedItems_ : static_cast<int>(results_.size());
255 }
256
257
258 /** ***************************************************************************/
roleNames() const259 QHash<int,QByteArray> Core::QueryExecution::roleNames() const {
260 QHash<int, QByteArray> roles;
261 roles[static_cast<int>(ItemRoles::TextRole)] = "itemTextRole";
262 roles[static_cast<int>(ItemRoles::ToolTipRole)] = "itemToolTipRole";
263 roles[static_cast<int>(ItemRoles::DecorationRole)] = "itemDecorationRole";
264 roles[static_cast<int>(ItemRoles::CompletionRole)] = "itemCompletionStringRole";
265 roles[static_cast<int>(ItemRoles::ActionRole)] = "itemActionRole"; // used to activate items as well
266 roles[static_cast<int>(ItemRoles::AltActionRole)] = "itemAltActionsRole"; // used to activate items as well
267 roles[static_cast<int>(ItemRoles::FallbackRole)] = "itemFallbackRole"; // used to activate items as well
268 return roles;
269 }
270
271
272 /** ***************************************************************************/
data(const QModelIndex & index,int role) const273 QVariant Core::QueryExecution::data(const QModelIndex &index, int role) const {
274 if (index.isValid()) {
275 const shared_ptr<Item> &item = results_[static_cast<size_t>(index.row())].first;
276
277 switch ( role ) {
278 case ItemRoles::TextRole:
279 return item->text();
280 case ItemRoles::ToolTipRole:
281 return item->subtext();
282 case ItemRoles::DecorationRole:
283 return item->iconPath();
284 case ItemRoles::CompletionRole:
285 return item->completion();
286 case ItemRoles::ActionRole:
287 return (0 < static_cast<int>(item->actions().size()))
288 ? item->actions()[0]->text()
289 : item->subtext();
290 case ItemRoles::AltActionRole: { // Actions list
291 QStringList actionTexts;
292 for (auto &action : item->actions())
293 actionTexts.append(action->text());
294 return actionTexts;
295 }
296 case ItemRoles::FallbackRole:
297 return QString("Search '%1' using default fallback").arg(query_.rawString_);
298 }
299 }
300 return QVariant();
301 }
302
303
304 /** ***************************************************************************/
canFetchMore(const QModelIndex &) const305 bool Core::QueryExecution::canFetchMore(const QModelIndex & /* index */) const
306 {
307 if (query_.trigger_.isNull() && fetchIncrementally_ && sortedItems_ < static_cast<int>(results_.size()))
308 return true;
309 else
310 return false;
311 }
312
313
314 /** ***************************************************************************/
fetchMore(const QModelIndex &)315 void Core::QueryExecution::fetchMore(const QModelIndex & /* index */)
316 {
317 int sortUntil = min(sortedItems_ + FETCH_SIZE, static_cast<int>(results_.size()));
318 if (query_.trigger_.isNull())
319 partial_sort(results_.begin() + sortedItems_,
320 results_.begin() + sortUntil,
321 results_.end(),
322 MatchCompare());
323 beginInsertRows(QModelIndex(), sortedItems_, sortUntil-1);
324 sortedItems_ = sortUntil;
325 endInsertRows();
326 sortedItems_ = sortUntil;
327 }
328
329
330 /** ***************************************************************************/
setData(const QModelIndex & index,const QVariant & value,int role)331 bool Core::QueryExecution::setData(const QModelIndex &index, const QVariant &value, int role) {
332
333 if (index.isValid()) {
334 shared_ptr<Item> &item = results_[static_cast<size_t>(index.row())].first;
335 switch ( role ) {
336 case ItemRoles::ActionRole:{
337 if (0U < item->actions().size()){
338 item->actions()[0]->activate();
339 stats.activatedItem = item->id();
340 }
341 break;
342 }
343 case ItemRoles::AltActionRole:{
344 size_t actionValue = static_cast<size_t>(value.toInt());
345 if (actionValue < item->actions().size()) {
346 item->actions()[actionValue]->activate();
347 stats.activatedItem = item->id();
348 }
349 break;
350 }
351 case ItemRoles::FallbackRole:{
352 if (0U < fallbacks_.size() && 0U < fallbacks_[0].first->actions().size()) {
353 fallbacks_[0].first->actions()[0]->activate();
354 stats.activatedItem = fallbacks_[0].first->id();
355 }
356 break;
357 }
358 }
359 return true;
360 }
361 return false;
362 }
363