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