Merge lp:~stolowski/unity-scopes-shell/diff-updates into lp:unity-scopes-shell
- diff-updates
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Marcus Tomlinson |
Approved revision: | 283 |
Merged at revision: | 276 |
Proposed branch: | lp:~stolowski/unity-scopes-shell/diff-updates |
Merge into: | lp:unity-scopes-shell |
Prerequisite: | lp:~stolowski/unity-scopes-shell/in-card-activation |
Diff against target: |
1077 lines (+555/-134) 16 files modified
debian/control.in (+1/-1) po/POTFILES.in (+0/-10) src/Unity/CMakeLists.txt (+1/-0) src/Unity/categories.cpp (+35/-50) src/Unity/categories.h (+5/-2) src/Unity/resultsmap.cpp (+65/-0) src/Unity/resultsmap.h (+48/-0) src/Unity/resultsmodel.cpp (+75/-2) src/Unity/resultsmodel.h (+4/-0) src/Unity/scope.cpp (+40/-30) src/Unity/scope.h (+3/-3) tests/data/CMakeLists.txt (+1/-0) tests/data/mock-scope-manyresults/CMakeLists.txt (+6/-0) tests/data/mock-scope-manyresults/mock-scope-manyresults.cpp (+185/-0) tests/data/mock-scope-manyresults/mock-scope-manyresults.ini.in (+14/-0) tests/resultstest.cpp (+72/-36) |
To merge this branch: | bzr merge lp:~stolowski/unity-scopes-shell/diff-updates |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marcus Tomlinson (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Needs Fixing | |
Review via email: mp+273554@code.launchpad.net |
Commit message
Apply updates to results model instead of clearing. Removed obsolete code which deals with special categories.
Description of the change
Apply updates to results model instead of clearing.
These changes are available for testing in silo 20.
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:277
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 278. By Paweł Stołowski
-
Minor test improvement
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:278
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 279. By Paweł Stołowski
-
Oops, forgot to commit
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:279
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Marcus Tomlinson (marcustomlinson) wrote : | # |
Hmmm, some scopes seems to crash now. I'm testing on krillin rc-proposed #147, between the standard shell and unity-api packages and those from silo 20.
With your silo, BBC, BBC Sport, and cnet scopes (probably more) crash as soon as you change the department.
Paweł Stołowski (stolowski) wrote : | # |
> Hmmm, some scopes seems to crash now. I'm testing on krillin rc-proposed #147,
> between the standard shell and unity-api packages and those from silo 20.
>
> With your silo, BBC, BBC Sport, and cnet scopes (probably more) crash as soon
> as you change the department.
Yeah, I can reproduce. Looks like it's related to vertical journal, the last message in the dash log before it crashes comes from unity8 and says:
"VerticalJournal only supports removal from the end of the model"
Need to check with Albert about what can be done about it. Unfortunately, according to him, veritcal journal is "unfixable" in that respect, so I may need to resort to full model clearing for this kind of renderer...
Paweł Stołowski (stolowski) wrote : | # |
Okay, silo 20 now has the fix for unity8 crash caused by unexpected model updates from plugin.
- 280. By Paweł Stołowski
-
Merged trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:280
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Marcus Tomlinson (marcustomlinson) wrote : | # |
There seems to be a few issues with aggregator scopes now. It looks like while new results are being loaded the first category's results get duplicated. Then when the refresh completes, the duplicates disappear.
The most noticeable scope I see this with is the Photos scope. All results in the My Photos category at the top get duplicated while the refresh is busy, then suddenly disappear when its done.
For aggregators that load quickly I think we get away with not revealing this issue because the duplicates get removed before the new set is displayed. Perhaps we also get away with duplicating results in a top category that is full as we don't see the extras being appended to the end.
Other scope's I've seen this on (although harder to catch) are:
* Food (duplicate "Add your fitbit account" item)
* Nearby (deplete "Where am I" result)
Marcus Tomlinson (marcustomlinson) wrote : | # |
> There seems to be a few issues with aggregator scopes now. It looks like while
> new results are being loaded the first category's results get duplicated. Then
> when the refresh completes, the duplicates disappear.
>
> The most noticeable scope I see this with is the Photos scope. All results in
> the My Photos category at the top get duplicated while the refresh is busy,
> then suddenly disappear when its done.
>
> For aggregators that load quickly I think we get away with not revealing this
> issue because the duplicates get removed before the new set is displayed.
> Perhaps we also get away with duplicating results in a top category that is
> full as we don't see the extras being appended to the end.
>
> Other scope's I've seen this on (although harder to catch) are:
>
> * Food (duplicate "Add your fitbit account" item)
> * Nearby (deplete "Where am I" result)
That last "deplete" should have been "duplicate" as well (auto-correct fail)
- 281. By Paweł Stołowski
-
Use only one timer
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:281
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Paweł Stołowski (stolowski) wrote : | # |
> There seems to be a few issues with aggregator scopes now. It looks like while
> new results are being loaded the first category's results get duplicated. Then
> when the refresh completes, the duplicates disappear.
>
> The most noticeable scope I see this with is the Photos scope. All results in
> the My Photos category at the top get duplicated while the refresh is busy,
> then suddenly disappear when its done.
>
> For aggregators that load quickly I think we get away with not revealing this
> issue because the duplicates get removed before the new set is displayed.
> Perhaps we also get away with duplicating results in a top category that is
> full as we don't see the extras being appended to the end.
>
> Other scope's I've seen this on (although harder to catch) are:
>
> * Food (duplicate "Add your fitbit account" item)
> * Nearby (deplete "Where am I" result)
I debugged this and it turned out it's due to the way these scopes work... They create *unique* category ids on every search (an id, plus a timestamp each time), e.g. "nominatim-
Now, this creates a serious issue with the existing implementation of the plugin, where we never remove categories, only move them down (and hide) in the model when they are empty - effectively a leak of resources. I think we could stop shell plugin from caching category objects indefinitely, but I would definately leave it for a separate MP if we deem it a good idea.
I talked to Kyle about this problem and he is going to fix the aggregator scopes (or ask respective people from their team to do that), which should fix the issue you saw. Apparently this MP needs to wait till aggregators are updated, otherwise the experience will be suboptimal.
Paweł Stołowski (stolowski) wrote : | # |
> > There seems to be a few issues with aggregator scopes now. It looks like
> while
> > new results are being loaded the first category's results get duplicated.
> Then
> > when the refresh completes, the duplicates disappear.
> >
> > The most noticeable scope I see this with is the Photos scope. All results
> in
> > the My Photos category at the top get duplicated while the refresh is busy,
> > then suddenly disappear when its done.
> >
> > For aggregators that load quickly I think we get away with not revealing
> this
> > issue because the duplicates get removed before the new set is displayed.
> > Perhaps we also get away with duplicating results in a top category that is
> > full as we don't see the extras being appended to the end.
> >
> > Other scope's I've seen this on (although harder to catch) are:
> >
> > * Food (duplicate "Add your fitbit account" item)
> > * Nearby (deplete "Where am I" result)
>
> I debugged this and it turned out it's due to the way these scopes work...
> They create *unique* category ids on every search (an id, plus a timestamp
> each time), e.g. "nominatim-
> how:declared:
>
> Now, this creates a serious issue with the existing implementation of the
> plugin, where we never remove categories, only move them down (and hide) in
> the model when they are empty - effectively a leak of resources. I think we
> could stop shell plugin from caching category objects indefinitely, but I
> would definately leave it for a separate MP if we deem it a good idea.
>
> I talked to Kyle about this problem and he is going to fix the aggregator
> scopes (or ask respective people from their team to do that), which should fix
> the issue you saw. Apparently this MP needs to wait till aggregators are
> updated, otherwise the experience will be suboptimal.
Related bug for Kyle's aggregators: https:/
Michi Henning (michihenning) wrote : | # |
In effect, this constitutes a memory leak in the shell, so it needs fixing regardless. There has to be some upper limit on the number of categories that cached for each scope. Some largish number (maybe 200?) per scope should do.
We also should add something to the scopes api doc to day "don't do this".
- 282. By Paweł Stołowski
-
Increse typing timeout to 700ms
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:282
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 283. By Paweł Stołowski
-
Merged trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:283
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Marcus Tomlinson (marcustomlinson) wrote : | # |
I'm happy with these changes, looks really nice. Good job Pawel!
- 284. By Paweł Stołowski
-
Merged trunk
- 285. By Paweł Stołowski
-
Bump unity-api dep
Preview Diff
1 | === modified file 'debian/control.in' |
2 | --- debian/control.in 2015-11-02 10:11:24 +0000 |
3 | +++ debian/control.in 2015-11-02 10:11:24 +0000 |
4 | @@ -8,7 +8,7 @@ |
5 | dh-python, |
6 | libboost-python-dev, |
7 | libboost-regex-dev, |
8 | - libunity-api-dev (>= 7.101), |
9 | + libunity-api-dev (>= 7.102), |
10 | libunity-scopes-dev (>= 1.0.1~), |
11 | libgsettings-qt-dev (>= 0.1), |
12 | libqtdbustest1-dev (>= 0.2), |
13 | |
14 | === modified file 'po/POTFILES.in' |
15 | --- po/POTFILES.in 2015-11-02 10:11:24 +0000 |
16 | +++ po/POTFILES.in 2015-09-22 11:04:14 +0000 |
17 | @@ -59,18 +59,11 @@ |
18 | src/python/scope_harness/results-view-py.cpp |
19 | src/python/scope_harness/preview-widget-py.cpp |
20 | src/python/scope_harness/preview-widget-list-py.cpp |
21 | -<<<<<<< TREE |
22 | src/Unity/previewwidgetmodel.cpp |
23 | src/Unity/departmentnode.cpp |
24 | src/Unity/logintoaccount.cpp |
25 | src/Unity/overviewcategories.cpp |
26 | src/Unity/scope.cpp |
27 | -======= |
28 | -src/Unity/previewwidgetmodel.cpp |
29 | -src/Unity/departmentnode.cpp |
30 | -src/Unity/overviewcategories.cpp |
31 | -src/Unity/scope.cpp |
32 | ->>>>>>> MERGE-SOURCE |
33 | src/Unity/overviewscope.cpp |
34 | src/Unity/collectors.cpp |
35 | src/Unity/previewmodel.cpp |
36 | @@ -133,10 +126,7 @@ |
37 | src/Unity/department.h |
38 | src/Unity/scope.h |
39 | src/Unity/previewstack.h |
40 | -<<<<<<< TREE |
41 | src/Unity/logintoaccount.h |
42 | -======= |
43 | ->>>>>>> MERGE-SOURCE |
44 | src/Unity/overviewcategories.h |
45 | src/Unity/locationservice.h |
46 | src/Unity/overviewscope.h |
47 | |
48 | === modified file 'src/Unity/CMakeLists.txt' |
49 | --- src/Unity/CMakeLists.txt 2015-09-08 15:08:55 +0000 |
50 | +++ src/Unity/CMakeLists.txt 2015-11-02 10:11:24 +0000 |
51 | @@ -29,6 +29,7 @@ |
52 | previewmodel.cpp |
53 | previewstack.cpp |
54 | previewwidgetmodel.cpp |
55 | + resultsmap.cpp |
56 | resultsmodel.cpp |
57 | scope.cpp |
58 | scopes.cpp |
59 | |
60 | === modified file 'src/Unity/categories.cpp' |
61 | --- src/Unity/categories.cpp 2015-11-02 10:11:24 +0000 |
62 | +++ src/Unity/categories.cpp 2015-11-02 10:11:24 +0000 |
63 | @@ -46,20 +46,13 @@ |
64 | class CategoryData |
65 | { |
66 | public: |
67 | - CategoryData(scopes::Category::SCPtr const& category): m_isSpecial(false) |
68 | + CategoryData(scopes::Category::SCPtr const& category) |
69 | { |
70 | setCategory(category); |
71 | } |
72 | |
73 | CategoryData(CategoryData const& other) = delete; |
74 | |
75 | - // constructor for special (shell-overriden) categories |
76 | - CategoryData(QString const& id, QString const& title, QString const& icon, QString const& rawTemplate, QObject* countObject): |
77 | - m_catId(id), m_catTitle(title), m_catIcon(icon), m_rawTemplate(rawTemplate.toStdString()), m_countObject(countObject), m_isSpecial(true) |
78 | - { |
79 | - parseTemplate(m_rawTemplate, &m_rendererTemplate, &m_components); |
80 | - } |
81 | - |
82 | void setCategory(scopes::Category::SCPtr const& category) |
83 | { |
84 | m_category = category; |
85 | @@ -211,11 +204,6 @@ |
86 | return 0; |
87 | } |
88 | |
89 | - bool isSpecial() const |
90 | - { |
91 | - return m_isSpecial; |
92 | - } |
93 | - |
94 | static bool parseTemplate(std::string const& raw_template, QJsonValue* renderer, QJsonValue* components) |
95 | { |
96 | // lazy init of the defaults |
97 | @@ -260,7 +248,6 @@ |
98 | QJsonValue m_components; |
99 | QSharedPointer<ResultsModel> m_resultsModel; |
100 | QPointer<QObject> m_countObject; |
101 | - bool m_isSpecial; |
102 | |
103 | static QJsonValue mergeOverrides(QJsonValue const& defaultVal, QJsonValue const& overrideVal) |
104 | { |
105 | @@ -299,7 +286,8 @@ |
106 | QJsonValue* CategoryData::DEFAULTS = nullptr; |
107 | |
108 | Categories::Categories(QObject* parent) |
109 | - : unity::shell::scopes::CategoriesInterface(parent) |
110 | + : unity::shell::scopes::CategoriesInterface(parent), |
111 | + m_categoryIndex(0) |
112 | { |
113 | } |
114 | |
115 | @@ -333,25 +321,16 @@ |
116 | return -1; |
117 | } |
118 | |
119 | -int Categories::getFirstEmptyCategoryIndex() const |
120 | -{ |
121 | - for (int i = 0; i < m_categories.size(); i++) { |
122 | - if (m_categories[i]->isSpecial()) { |
123 | - continue; |
124 | - } |
125 | - if (m_categories[i]->resultsModelCount() == 0) { |
126 | - return i; |
127 | - } |
128 | - } |
129 | - |
130 | - return m_categories.size(); |
131 | -} |
132 | - |
133 | void Categories::registerCategory(const scopes::Category::SCPtr& category, QSharedPointer<ResultsModel> resultsModel) |
134 | { |
135 | // do we already have a category with this id? |
136 | + if (m_registeredCategories.find(category->id()) != m_registeredCategories.end()) { |
137 | + return; |
138 | + } |
139 | + m_registeredCategories.insert(category->id()); |
140 | + |
141 | int index = getCategoryIndex(QString::fromStdString(category->id())); |
142 | - int emptyIndex = getFirstEmptyCategoryIndex(); |
143 | + int emptyIndex = m_categoryIndex++; |
144 | if (index >= 0) { |
145 | // re-registering an existing category will move it after the first non-empty category |
146 | if (emptyIndex < index) { |
147 | @@ -374,8 +353,9 @@ |
148 | m_categories.insert(emptyIndex, catData); |
149 | endInsertRows(); |
150 | } else { |
151 | + // the category has already been registered for current search, |
152 | + // check if any attributes of the category changed |
153 | QSharedPointer<CategoryData> catData = m_categories[index]; |
154 | - // check if any attributes of the category changed |
155 | QVector<int> changedRoles(catData->updateAttributes(category)); |
156 | |
157 | if (changedRoles.size() > 0) { |
158 | @@ -463,6 +443,30 @@ |
159 | dataChanged(changeStart, changeEnd, roles); |
160 | } |
161 | |
162 | +void Categories::markNewSearch() |
163 | +{ |
164 | + m_categoryIndex = 0; |
165 | + m_registeredCategories.clear(); |
166 | + for (auto model: m_categoryResults) { |
167 | + model->markNewSearch(); |
168 | + } |
169 | +} |
170 | + |
171 | +void Categories::purgeResults() |
172 | +{ |
173 | + QVector<int> roles; |
174 | + roles.append(RoleCount); |
175 | + |
176 | + for (auto it = m_categoryResults.begin(); it != m_categoryResults.end(); it++) { |
177 | + auto model = it.value(); |
178 | + if (model->needsPurging()) { |
179 | + model->clearResults(); |
180 | + |
181 | + QModelIndex idx(index(getCategoryIndex(QString::fromStdString(it.key())))); |
182 | + Q_EMIT dataChanged(idx, idx, roles); |
183 | + } |
184 | + } |
185 | +} |
186 | |
187 | bool Categories::parseTemplate(std::string const& raw_template, QJsonValue* renderer, QJsonValue* components) |
188 | { |
189 | @@ -494,25 +498,6 @@ |
190 | return false; |
191 | } |
192 | |
193 | -void Categories::addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject) |
194 | -{ |
195 | - int index = getCategoryIndex(categoryId); |
196 | - if (index >= 0) { |
197 | - qWarning("ERROR! Category with id \"%s\" already exists!", categoryId.toStdString().c_str()); |
198 | - } else { |
199 | - QSharedPointer<CategoryData> catData(new CategoryData(categoryId, name, icon, rawTemplate, countObject)); |
200 | - // prepend the category |
201 | - beginInsertRows(QModelIndex(), 0, 0); |
202 | - m_categories.prepend(catData); |
203 | - endInsertRows(); |
204 | - |
205 | - if (countObject) { |
206 | - m_countObjects[countObject] = categoryId; |
207 | - QObject::connect(countObject, SIGNAL(countChanged()), this, SLOT(countChanged())); |
208 | - } |
209 | - } |
210 | -} |
211 | - |
212 | void Categories::countChanged() |
213 | { |
214 | QObject* countObject = sender(); |
215 | |
216 | === modified file 'src/Unity/categories.h' |
217 | --- src/Unity/categories.h 2015-11-02 10:11:24 +0000 |
218 | +++ src/Unity/categories.h 2015-11-02 10:11:24 +0000 |
219 | @@ -26,6 +26,7 @@ |
220 | |
221 | #include <QSharedPointer> |
222 | #include <QJsonValue> |
223 | +#include <set> |
224 | |
225 | #include <unity/scopes/Category.h> |
226 | |
227 | @@ -55,12 +56,13 @@ |
228 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; |
229 | |
230 | Q_INVOKABLE bool overrideCategoryJson(QString const& categoryId, QString const& json) override; |
231 | - Q_INVOKABLE void addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject) override; |
232 | |
233 | QSharedPointer<ResultsModel> lookupCategory(std::string const& category_id); |
234 | void registerCategory(const unity::scopes::Category::SCPtr& category, QSharedPointer<ResultsModel> model); |
235 | void updateResultCount(const QSharedPointer<ResultsModel>& resultsModel); |
236 | void clearAll(); |
237 | + void markNewSearch(); |
238 | + void purgeResults(); |
239 | void updateResult(unity::scopes::Result const& result, QString const& categoryId, unity::scopes::Result const& updated_result); |
240 | |
241 | static bool parseTemplate(std::string const& raw_template, QJsonValue* renderer, QJsonValue* components); |
242 | @@ -70,11 +72,12 @@ |
243 | |
244 | private: |
245 | int getCategoryIndex(QString const& categoryId) const; |
246 | - int getFirstEmptyCategoryIndex() const; |
247 | |
248 | QList<QSharedPointer<CategoryData>> m_categories; |
249 | QMap<std::string, QSharedPointer<ResultsModel>> m_categoryResults; |
250 | QMap<QObject*, QString> m_countObjects; |
251 | + std::set<std::string> m_registeredCategories; |
252 | + int m_categoryIndex; |
253 | }; |
254 | |
255 | } // namespace scopes_ng |
256 | |
257 | === added file 'src/Unity/resultsmap.cpp' |
258 | --- src/Unity/resultsmap.cpp 1970-01-01 00:00:00 +0000 |
259 | +++ src/Unity/resultsmap.cpp 2015-11-02 10:11:24 +0000 |
260 | @@ -0,0 +1,65 @@ |
261 | +/* |
262 | + * Copyright (C) 2015 Canonical, Ltd. |
263 | + * |
264 | + * Authors: |
265 | + * Pawel Stolowski <pawel.stolowski@canonical.com> |
266 | + * |
267 | + * This program is free software; you can redistribute it and/or modify |
268 | + * it under the terms of the GNU General Public License as published by |
269 | + * the Free Software Foundation; version 3. |
270 | + * |
271 | + * This program is distributed in the hope that it will be useful, |
272 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
273 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
274 | + * GNU General Public License for more details. |
275 | + * |
276 | + * You should have received a copy of the GNU General Public License |
277 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
278 | + */ |
279 | + |
280 | +#include "resultsmap.h" |
281 | +#include <cassert> |
282 | + |
283 | +ResultsMap::ResultsMap(QList<std::shared_ptr<unity::scopes::Result>> const &results) |
284 | +{ |
285 | + rebuild(results); |
286 | +} |
287 | + |
288 | +ResultsMap::ResultsMap(QList<std::shared_ptr<unity::scopes::CategorisedResult>> const &results) |
289 | +{ |
290 | + int pos = 0; |
291 | + for (auto result: results) { |
292 | + std::shared_ptr<unity::scopes::Result> res = result; |
293 | + assert(res); |
294 | + const ResultPos rpos { res, pos++ }; |
295 | + m_results.insert({result->uri(), rpos }); |
296 | + } |
297 | +} |
298 | + |
299 | +void ResultsMap::rebuild(QList<std::shared_ptr<unity::scopes::Result>> const &results) |
300 | +{ |
301 | + m_results.clear(); |
302 | + int pos = 0; |
303 | + for (auto result: results) { |
304 | + assert(result); |
305 | + const ResultPos rpos { result, pos++ }; |
306 | + m_results.insert({result->uri(), rpos }); |
307 | + } |
308 | +} |
309 | + |
310 | +int ResultsMap::find(std::shared_ptr<unity::scopes::Result> const& result) const |
311 | +{ |
312 | + assert(result); |
313 | + auto it = m_results.find(result->uri()); |
314 | + if (it != m_results.end()) { |
315 | + assert(it->second.result); |
316 | + while (it != m_results.end() && it->second.result->uri() == result->uri()) |
317 | + { |
318 | + if (*(it->second.result) == *result) { |
319 | + return it->second.index; |
320 | + } |
321 | + ++it; |
322 | + } |
323 | + } |
324 | + return -1; |
325 | +} |
326 | |
327 | === added file 'src/Unity/resultsmap.h' |
328 | --- src/Unity/resultsmap.h 1970-01-01 00:00:00 +0000 |
329 | +++ src/Unity/resultsmap.h 2015-11-02 10:11:24 +0000 |
330 | @@ -0,0 +1,48 @@ |
331 | +/* |
332 | + * Copyright (C) 2015 Canonical, Ltd. |
333 | + * |
334 | + * Authors: |
335 | + * Pawel Stolowski <pawel.stolowski@canonical.com> |
336 | + * |
337 | + * This program is free software; you can redistribute it and/or modify |
338 | + * it under the terms of the GNU General Public License as published by |
339 | + * the Free Software Foundation; version 3. |
340 | + * |
341 | + * This program is distributed in the hope that it will be useful, |
342 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
343 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
344 | + * GNU General Public License for more details. |
345 | + * |
346 | + * You should have received a copy of the GNU General Public License |
347 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
348 | + */ |
349 | + |
350 | +#ifndef NG_RESULTS_MAP_H |
351 | +#define NGRESULTS_MAP_H |
352 | + |
353 | +#include <QList> |
354 | +#include <memory> |
355 | +#include <unity/scopes/CategorisedResult.h> |
356 | +#include <map> |
357 | + |
358 | +/** |
359 | + Helper class for Result -> row lookups, maintains a multimap internally |
360 | + allowing for duplicated Result uris. |
361 | +*/ |
362 | +class ResultsMap |
363 | +{ |
364 | + public: |
365 | + ResultsMap(QList<std::shared_ptr<unity::scopes::Result>> const &results); |
366 | + ResultsMap(QList<std::shared_ptr<unity::scopes::CategorisedResult>> const &results); |
367 | + int find(std::shared_ptr<unity::scopes::Result> const& result) const; |
368 | + |
369 | + void rebuild(QList<std::shared_ptr<unity::scopes::Result>> const &results); |
370 | + private: |
371 | + struct ResultPos { |
372 | + std::shared_ptr<unity::scopes::Result> result; |
373 | + int index; |
374 | + }; |
375 | + std::multimap<std::string, ResultPos> m_results; |
376 | +}; |
377 | + |
378 | +#endif |
379 | |
380 | === modified file 'src/Unity/resultsmodel.cpp' |
381 | --- src/Unity/resultsmodel.cpp 2015-11-02 10:11:24 +0000 |
382 | +++ src/Unity/resultsmodel.cpp 2015-11-02 10:11:24 +0000 |
383 | @@ -23,7 +23,9 @@ |
384 | // local |
385 | #include "utils.h" |
386 | #include "iconutils.h" |
387 | +#include "resultsmap.h" |
388 | |
389 | +#include <map> |
390 | #include <QDebug> |
391 | |
392 | namespace scopes_ng { |
393 | @@ -33,6 +35,7 @@ |
394 | ResultsModel::ResultsModel(QObject* parent) |
395 | : unity::shell::scopes::ResultsModelInterface(parent) |
396 | , m_maxAttributes(2) |
397 | + , m_purge(true) |
398 | { |
399 | } |
400 | |
401 | @@ -70,10 +73,70 @@ |
402 | m_maxAttributes = count; |
403 | } |
404 | |
405 | +void ResultsModel::addUpdateResults(QList<std::shared_ptr<unity::scopes::CategorisedResult>> const& results) |
406 | +{ |
407 | + if (results.count() == 0) { |
408 | + return; |
409 | + } |
410 | + |
411 | + m_purge = false; |
412 | + |
413 | + const int oldCount = m_results.count(); |
414 | + |
415 | + ResultsMap newResultsMap(results); |
416 | + |
417 | + int row = 0; |
418 | + // iterate over old (i.e. currently visible) results, remove results which are no longer present in new set |
419 | + for (auto it = m_results.begin(); it != m_results.end(); ) { |
420 | + int newPos = newResultsMap.find(*it); |
421 | + bool haveNow = (newPos >= 0); |
422 | + if (!haveNow) { |
423 | + // delete row |
424 | + beginRemoveRows(QModelIndex(), row, row); |
425 | + it = m_results.erase(it); |
426 | + endRemoveRows(); |
427 | + } else { |
428 | + ++it; |
429 | + ++row; |
430 | + } |
431 | + } |
432 | + |
433 | + ResultsMap oldResultsMap(m_results); |
434 | + |
435 | + // iterate over new results |
436 | + for (row = 0; row<results.count(); ++row) { |
437 | + const int oldPos = oldResultsMap.find(results[row]); |
438 | + const bool hadBefore = (oldPos >= 0); |
439 | + if (hadBefore) { |
440 | + if (row != oldPos) { |
441 | + // move row |
442 | + beginMoveRows(QModelIndex(), oldPos, oldPos, QModelIndex(), row + (row > oldPos ? 1 : 0)); |
443 | + m_results.move(oldPos, row); |
444 | + oldResultsMap.rebuild(m_results); |
445 | + endMoveRows(); |
446 | + } |
447 | + } else { |
448 | + // insert row |
449 | + beginInsertRows(QModelIndex(), row, row); |
450 | + m_results.insert(row, results[row]); |
451 | + oldResultsMap.rebuild(m_results); |
452 | + endInsertRows(); |
453 | + } |
454 | + } |
455 | + |
456 | + if (oldCount != m_results.count()) { |
457 | + Q_EMIT countChanged(); |
458 | + } |
459 | +} |
460 | + |
461 | void ResultsModel::addResults(QList<std::shared_ptr<unity::scopes::CategorisedResult>> const& results) |
462 | { |
463 | - if (results.count() == 0) return; |
464 | - |
465 | + if (results.count() == 0) { |
466 | + return; |
467 | + } |
468 | + |
469 | + m_purge = false; |
470 | + |
471 | beginInsertRows(QModelIndex(), m_results.count(), m_results.count() + results.count() - 1); |
472 | Q_FOREACH(std::shared_ptr<scopes::CategorisedResult> const& result, results) { |
473 | m_results.append(result); |
474 | @@ -264,4 +327,14 @@ |
475 | } |
476 | } |
477 | |
478 | +void ResultsModel::markNewSearch() |
479 | +{ |
480 | + m_purge = true; |
481 | +} |
482 | + |
483 | +bool ResultsModel::needsPurging() const |
484 | +{ |
485 | + return m_purge; |
486 | +} |
487 | + |
488 | } // namespace scopes_ng |
489 | |
490 | === modified file 'src/Unity/resultsmodel.h' |
491 | --- src/Unity/resultsmodel.h 2015-11-02 10:11:24 +0000 |
492 | +++ src/Unity/resultsmodel.h 2015-11-02 10:11:24 +0000 |
493 | @@ -45,6 +45,7 @@ |
494 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; |
495 | |
496 | void addResults(QList<std::shared_ptr<unity::scopes::CategorisedResult>> const&); |
497 | + void addUpdateResults(QList<std::shared_ptr<unity::scopes::CategorisedResult>> const&); |
498 | void clearResults(); |
499 | |
500 | /* getters */ |
501 | @@ -58,6 +59,8 @@ |
502 | |
503 | QHash<int, QByteArray> roleNames() const override; |
504 | void updateResult(unity::scopes::Result const& result, unity::scopes::Result const& updatedResult); |
505 | + void markNewSearch(); |
506 | + bool needsPurging() const; |
507 | |
508 | private: |
509 | QVariant componentValue(unity::scopes::Result const* result, std::string const& fieldName) const; |
510 | @@ -67,6 +70,7 @@ |
511 | QList<std::shared_ptr<unity::scopes::Result>> m_results; |
512 | QString m_categoryId; |
513 | int m_maxAttributes; |
514 | + bool m_purge; |
515 | }; |
516 | |
517 | } // namespace scopes_ng |
518 | |
519 | === modified file 'src/Unity/scope.cpp' |
520 | --- src/Unity/scope.cpp 2015-11-02 10:11:24 +0000 |
521 | +++ src/Unity/scope.cpp 2015-11-02 10:11:24 +0000 |
522 | @@ -67,9 +67,8 @@ |
523 | |
524 | using namespace unity; |
525 | |
526 | -const int AGGREGATION_TIMEOUT = 110; |
527 | -const int TYPING_TIMEOUT = 300; |
528 | -const int CLEAR_TIMEOUT = 240; |
529 | +const int TYPING_TIMEOUT = 700; |
530 | +const int SEARCH_PROCESSING_DELAY = 1000; |
531 | const int RESULTS_TTL_SMALL = 30000; // 30 seconds |
532 | const int RESULTS_TTL_MEDIUM = 300000; // 5 minutes |
533 | const int RESULTS_TTL_LARGE = 3600000; // 1 hour |
534 | @@ -85,7 +84,7 @@ |
535 | , m_isActive(false) |
536 | , m_searchInProgress(false) |
537 | , m_resultsDirty(false) |
538 | - , m_delayedClear(false) |
539 | + , m_delayedSearchProcessing(false) |
540 | , m_hasNavigation(false) |
541 | , m_hasAltNavigation(false) |
542 | , m_favorite(false) |
543 | @@ -109,10 +108,8 @@ |
544 | m_typingTimer.setInterval(TYPING_TIMEOUT); |
545 | } |
546 | QObject::connect(&m_typingTimer, &QTimer::timeout, this, &Scope::typingFinished); |
547 | - m_aggregatorTimer.setSingleShot(true); |
548 | - QObject::connect(&m_aggregatorTimer, SIGNAL(timeout()), this, SLOT(flushUpdates())); |
549 | - m_clearTimer.setSingleShot(true); |
550 | - QObject::connect(&m_clearTimer, SIGNAL(timeout()), this, SLOT(flushUpdates())); |
551 | + m_searchProcessingDelayTimer.setSingleShot(true); |
552 | + QObject::connect(&m_searchProcessingDelayTimer, SIGNAL(timeout()), this, SLOT(flushUpdates())); |
553 | m_invalidateTimer.setSingleShot(true); |
554 | m_invalidateTimer.setTimerType(Qt::CoarseTimer); |
555 | QObject::connect(&m_invalidateTimer, &QTimer::timeout, this, &Scope::invalidateResults); |
556 | @@ -146,14 +143,14 @@ |
557 | } |
558 | |
559 | if (status == CollectorBase::Status::INCOMPLETE) { |
560 | - if (!m_aggregatorTimer.isActive()) { |
561 | + if (!m_searchProcessingDelayTimer.isActive()) { |
562 | // the longer we've been waiting for the results, the shorter the timeout |
563 | qint64 inProgressMs = pushEvent->msecsSinceStart(); |
564 | double mult = 1.0 / std::max(1, static_cast<int>((inProgressMs / 150) + 1)); |
565 | - m_aggregatorTimer.start(AGGREGATION_TIMEOUT * mult); |
566 | + m_searchProcessingDelayTimer.start(SEARCH_PROCESSING_DELAY * mult); |
567 | } |
568 | } else { // status in [FINISHED, ERROR] |
569 | - m_aggregatorTimer.stop(); |
570 | + m_searchProcessingDelayTimer.stop(); |
571 | |
572 | flushUpdates(true); |
573 | |
574 | @@ -229,6 +226,7 @@ |
575 | case scopes::ActivationResponse::UpdateResult: |
576 | m_categories->updateResult(*result, categoryId, response->updated_result()); |
577 | Q_EMIT updateResultRequested(); |
578 | + break; |
579 | case scopes::ActivationResponse::UpdatePreview: |
580 | handlePreviewUpdate(result, response->updated_widgets()); |
581 | break; |
582 | @@ -345,21 +343,29 @@ |
583 | |
584 | void Scope::flushUpdates(bool finalize) |
585 | { |
586 | - if (m_delayedClear) { |
587 | - // TODO: here we could do resultset diffs |
588 | - m_categories->clearAll(); |
589 | - m_delayedClear = false; |
590 | - } |
591 | - |
592 | - if (m_clearTimer.isActive()) { |
593 | - m_clearTimer.stop(); |
594 | + if (m_delayedSearchProcessing) { |
595 | + m_delayedSearchProcessing = false; |
596 | } |
597 | |
598 | if (m_status != Status::Okay) { |
599 | setStatus(Status::Okay); |
600 | } |
601 | + |
602 | + // if no results have been received so far (and we're not in the finalizing step of search), then |
603 | + // don't process the results as this will inevitably make the dash empty. |
604 | + if (m_cachedResults.empty() && !finalize) { |
605 | + return; |
606 | + } |
607 | + |
608 | + qDebug() << "flushUpdates:" << id() << "#results =" << m_cachedResults.count() << "finalize:" << finalize; |
609 | + |
610 | processResultSet(m_cachedResults); // clears the result list |
611 | |
612 | + if (finalize) { |
613 | + m_category_results.clear(); |
614 | + m_categories->purgeResults(); // remove results for categories which were not present in new resultset |
615 | + } |
616 | + |
617 | // process departments |
618 | if (m_rootDepartment && m_rootDepartment != m_lastRootDepartment) { |
619 | // build / append to the tree |
620 | @@ -573,14 +579,15 @@ |
621 | // this will keep the list of categories in order |
622 | QVector<scopes::Category::SCPtr> categories; |
623 | |
624 | - // split the result_set by category_id |
625 | - QMap<std::string, QList<std::shared_ptr<scopes::CategorisedResult>>> category_results; |
626 | + // split the result_set by category_id; note that processResultSet may get called more than once |
627 | + // for single search request, all the contents of m_category_results accumulate until new search |
628 | + // is requested, so that addUpdateResults() can properly update affected models. |
629 | while (!result_set.empty()) { |
630 | auto result = result_set.takeFirst(); |
631 | - if (!category_results.contains(result->category()->id())) { |
632 | + if (!categories.contains(result->category())) { |
633 | categories.append(result->category()); |
634 | } |
635 | - category_results[result->category()->id()].append(std::move(result)); |
636 | + m_category_results[result->category()->id()].append(std::move(result)); |
637 | } |
638 | |
639 | Q_FOREACH(scopes::Category::SCPtr const& category, categories) { |
640 | @@ -588,12 +595,11 @@ |
641 | if (category_model == nullptr) { |
642 | category_model.reset(new ResultsModel(m_categories.data())); |
643 | category_model->setCategoryId(QString::fromStdString(category->id())); |
644 | - category_model->addResults(category_results[category->id()]); |
645 | + category_model->addResults(m_category_results[category->id()]); |
646 | m_categories->registerCategory(category, category_model); |
647 | } else { |
648 | - // FIXME: only update when we know it's necessary |
649 | m_categories->registerCategory(category, QSharedPointer<ResultsModel>()); |
650 | - category_model->addResults(category_results[category->id()]); |
651 | + category_model->addUpdateResults(m_category_results[category->id()]); |
652 | m_categories->updateResultCount(category_model); |
653 | } |
654 | } |
655 | @@ -612,10 +618,11 @@ |
656 | void Scope::invalidateLastSearch() |
657 | { |
658 | m_searchController->invalidate(); |
659 | - if (m_aggregatorTimer.isActive()) { |
660 | - m_aggregatorTimer.stop(); |
661 | + if (m_searchProcessingDelayTimer.isActive()) { |
662 | + m_searchProcessingDelayTimer.stop(); |
663 | } |
664 | m_cachedResults.clear(); |
665 | + m_category_results.clear(); |
666 | } |
667 | |
668 | void Scope::startTtlTimer() |
669 | @@ -695,8 +702,11 @@ |
670 | m_initialQueryDone = true; |
671 | |
672 | invalidateLastSearch(); |
673 | - m_delayedClear = true; |
674 | - m_clearTimer.start(CLEAR_TIMEOUT); |
675 | + m_delayedSearchProcessing = true; |
676 | + m_category_results.clear(); |
677 | + m_categories->markNewSearch(); |
678 | + |
679 | + m_searchProcessingDelayTimer.start(SEARCH_PROCESSING_DELAY); |
680 | /* There are a few objects associated with searches: |
681 | * 1) SearchResultReceiver 2) ResultCollector 3) PushEvent |
682 | * |
683 | |
684 | === modified file 'src/Unity/scope.h' |
685 | --- src/Unity/scope.h 2015-11-02 10:11:24 +0000 |
686 | +++ src/Unity/scope.h 2015-11-02 10:11:24 +0000 |
687 | @@ -231,12 +231,13 @@ |
688 | bool m_isActive; |
689 | bool m_searchInProgress; |
690 | bool m_resultsDirty; |
691 | - bool m_delayedClear; |
692 | + bool m_delayedSearchProcessing; |
693 | bool m_hasNavigation; |
694 | bool m_hasAltNavigation; |
695 | bool m_favorite; |
696 | bool m_initialQueryDone; |
697 | |
698 | + QMap<std::string, QList<std::shared_ptr<unity::scopes::CategorisedResult>>> m_category_results; |
699 | std::unique_ptr<CollectionController> m_searchController; |
700 | std::unique_ptr<CollectionController> m_activationController; |
701 | unity::scopes::ScopeProxy m_proxy; |
702 | @@ -253,8 +254,7 @@ |
703 | QSharedPointer<DepartmentNode> m_departmentTree; |
704 | QSharedPointer<DepartmentNode> m_altNavTree; |
705 | QTimer m_typingTimer; |
706 | - QTimer m_aggregatorTimer; |
707 | - QTimer m_clearTimer; |
708 | + QTimer m_searchProcessingDelayTimer; |
709 | QTimer m_invalidateTimer; |
710 | QList<std::shared_ptr<unity::scopes::CategorisedResult>> m_cachedResults; |
711 | QMultiMap<QString, Department*> m_departmentModels; |
712 | |
713 | === modified file 'tests/data/CMakeLists.txt' |
714 | --- tests/data/CMakeLists.txt 2014-12-01 14:13:38 +0000 |
715 | +++ tests/data/CMakeLists.txt 2015-11-02 10:11:24 +0000 |
716 | @@ -4,6 +4,7 @@ |
717 | add_subdirectory(mock-scope-double-nav) |
718 | add_subdirectory(mock-scope-info) |
719 | add_subdirectory(mock-scope-ttl) |
720 | +add_subdirectory(mock-scope-manyresults) |
721 | add_subdirectory(scopes) |
722 | |
723 | configure_file(Runtime.ini.in Runtime.ini @ONLY) |
724 | |
725 | === added directory 'tests/data/mock-scope-manyresults' |
726 | === added file 'tests/data/mock-scope-manyresults/CMakeLists.txt' |
727 | --- tests/data/mock-scope-manyresults/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
728 | +++ tests/data/mock-scope-manyresults/CMakeLists.txt 2015-11-02 10:11:24 +0000 |
729 | @@ -0,0 +1,6 @@ |
730 | +include_directories(${SCOPESLIB_INCLUDE_DIRS}) |
731 | + |
732 | +add_library(mock-scope-manyresults MODULE mock-scope-manyresults.cpp) |
733 | +target_link_libraries(mock-scope-manyresults ${SCOPESLIB_LDFLAGS}) |
734 | + |
735 | +configure_file(mock-scope-manyresults.ini.in mock-scope-manyresults.ini) |
736 | |
737 | === added file 'tests/data/mock-scope-manyresults/mock-scope-manyresults.cpp' |
738 | --- tests/data/mock-scope-manyresults/mock-scope-manyresults.cpp 1970-01-01 00:00:00 +0000 |
739 | +++ tests/data/mock-scope-manyresults/mock-scope-manyresults.cpp 2015-11-02 10:11:24 +0000 |
740 | @@ -0,0 +1,185 @@ |
741 | +/* |
742 | + * Copyright (C) 2015 Canonical, Ltd. |
743 | + * |
744 | + * This program is free software; you can redistribute it and/or modify |
745 | + * it under the terms of the GNU General Public License as published by |
746 | + * the Free Software Foundation; version 3. |
747 | + * |
748 | + * This program is distributed in the hope that it will be useful, |
749 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
750 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
751 | + * GNU General Public License for more details. |
752 | + * |
753 | + * You should have received a copy of the GNU General Public License |
754 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
755 | + * |
756 | + * Authors: |
757 | + * Pawel Stolowski <pawel.stolowski@canonical.com> |
758 | + */ |
759 | + |
760 | +#include <unity-scopes.h> |
761 | + |
762 | +#include <iostream> |
763 | +#include <thread> |
764 | + |
765 | +#define EXPORT __attribute__ ((visibility ("default"))) |
766 | + |
767 | +using namespace std; |
768 | +using namespace unity::scopes; |
769 | + |
770 | +// Example scope A: replies synchronously to a query. (Replies are returned before returning from the run() method.) |
771 | + |
772 | +class MyQuery : public SearchQueryBase |
773 | +{ |
774 | +public: |
775 | + MyQuery(CannedQuery const& query, SearchMetadata const& metadata, VariantMap const& settings) : |
776 | + SearchQueryBase(query, metadata), |
777 | + query_(query.query_string()), |
778 | + settings_(settings) |
779 | + { |
780 | + } |
781 | + |
782 | + ~MyQuery() noexcept |
783 | + { |
784 | + } |
785 | + |
786 | + virtual void cancelled() override |
787 | + { |
788 | + } |
789 | + |
790 | + virtual void run(SearchReplyProxy const& reply) override |
791 | + { |
792 | + CategoryRenderer meta_rndr(R"({"schema-version": 1, "components": {"title": "title", "art": "art", "subtitle": "subtitle", "emblem": "icon", "mascot": "mascot"}})"); |
793 | + auto cat1 = reply->register_category("cat1", "Category 1", "", meta_rndr); |
794 | + auto cat2 = reply->register_category("cat2", "Category 2", "", meta_rndr); |
795 | + |
796 | + if (query_ == "search1") |
797 | + { |
798 | + // five results with uris 0..4 in a single category cat1 |
799 | + for (int i = 0; i<5; i++) { |
800 | + CategorisedResult res(cat1); |
801 | + res.set_uri("cat1_uri" + std::to_string(i)); |
802 | + res.set_title("result " + std::to_string(i) + " for: \"" + query_ + "\""); |
803 | + reply->push(res); |
804 | + } |
805 | + std::this_thread::sleep_for(std::chrono::seconds(1)); |
806 | + } |
807 | + else if (query_ == "search2") |
808 | + { |
809 | + const int start = 3; |
810 | + // five results with uris 3..7 in categories cat1 and cat2 |
811 | + for (int i = 0; i<5; i++) { |
812 | + { |
813 | + CategorisedResult res(cat1); |
814 | + res.set_uri("cat1_uri" + std::to_string(start + i)); |
815 | + res.set_title("result " + std::to_string(start + i) + " for: \"" + query_ + "\""); |
816 | + reply->push(res); |
817 | + } |
818 | + { |
819 | + CategorisedResult res(cat2); |
820 | + res.set_uri("cat2_uri" + std::to_string(start + i)); |
821 | + res.set_title("result " + std::to_string(start + i) + " for: \"" + query_ + "\""); |
822 | + reply->push(res); |
823 | + } |
824 | + std::this_thread::sleep_for(std::chrono::milliseconds(30)); |
825 | + } |
826 | + } |
827 | + else if (query_ == "search3") |
828 | + { |
829 | + const int start = 3; |
830 | + // five results with uris 7..3 in categories cat1 and cat2 |
831 | + for (int i = 4; i>=0; i--) { |
832 | + { |
833 | + CategorisedResult res(cat1); |
834 | + res.set_uri("cat1_uri" + std::to_string(start + i)); |
835 | + res.set_title("result " + std::to_string(start + i) + " for: \"" + query_ + "\""); |
836 | + reply->push(res); |
837 | + } |
838 | + { |
839 | + CategorisedResult res(cat2); |
840 | + res.set_uri("cat2_uri" + std::to_string(start + i)); |
841 | + res.set_title("result " + std::to_string(start + i) + " for: \"" + query_ + "\""); |
842 | + reply->push(res); |
843 | + } |
844 | + std::this_thread::sleep_for(std::chrono::milliseconds(30)); |
845 | + } |
846 | + } |
847 | + else if (query_ == "search4") |
848 | + { |
849 | + // one result with uri 5 in cat2 |
850 | + { |
851 | + CategorisedResult res(cat2); |
852 | + res.set_uri("cat2_uri5"); |
853 | + res.set_title("result5 for: \"" + query_ + "\""); |
854 | + reply->push(res); |
855 | + } |
856 | + } |
857 | + |
858 | + } |
859 | + |
860 | +private: |
861 | + string query_; |
862 | + VariantMap settings_; |
863 | +}; |
864 | + |
865 | +class MyPreview : public PreviewQueryBase |
866 | +{ |
867 | +public: |
868 | + MyPreview(Result const& result, ActionMetadata const& metadata) : |
869 | + PreviewQueryBase(result, metadata), |
870 | + scope_data_(metadata.scope_data()) |
871 | + { |
872 | + } |
873 | + |
874 | + ~MyPreview() noexcept |
875 | + { |
876 | + } |
877 | + |
878 | + virtual void cancelled() override |
879 | + { |
880 | + } |
881 | + |
882 | + virtual void run(PreviewReplyProxy const&) override |
883 | + { |
884 | + } |
885 | + |
886 | +private: |
887 | + Variant scope_data_; |
888 | +}; |
889 | + |
890 | +class MyScope : public ScopeBase |
891 | +{ |
892 | +public: |
893 | + virtual SearchQueryBase::UPtr search(CannedQuery const& q, SearchMetadata const& metadata) override |
894 | + { |
895 | + SearchQueryBase::UPtr query(new MyQuery(q, metadata, settings())); |
896 | + return query; |
897 | + } |
898 | + |
899 | + virtual PreviewQueryBase::UPtr preview(Result const& result, ActionMetadata const& metadata) override |
900 | + { |
901 | + PreviewQueryBase::UPtr query(new MyPreview(result, metadata)); |
902 | + return query; |
903 | + } |
904 | +}; |
905 | + |
906 | +extern "C" |
907 | +{ |
908 | + |
909 | + EXPORT |
910 | + unity::scopes::ScopeBase* |
911 | + // cppcheck-suppress unusedFunction |
912 | + UNITY_SCOPE_CREATE_FUNCTION() |
913 | + { |
914 | + return new MyScope; |
915 | + } |
916 | + |
917 | + EXPORT |
918 | + void |
919 | + // cppcheck-suppress unusedFunction |
920 | + UNITY_SCOPE_DESTROY_FUNCTION(unity::scopes::ScopeBase* scope_base) |
921 | + { |
922 | + delete scope_base; |
923 | + } |
924 | + |
925 | +} |
926 | |
927 | === added file 'tests/data/mock-scope-manyresults/mock-scope-manyresults.ini.in' |
928 | --- tests/data/mock-scope-manyresults/mock-scope-manyresults.ini.in 1970-01-01 00:00:00 +0000 |
929 | +++ tests/data/mock-scope-manyresults/mock-scope-manyresults.ini.in 2015-11-02 10:11:24 +0000 |
930 | @@ -0,0 +1,14 @@ |
931 | +[ScopeConfig] |
932 | +DisplayName = mock.DisplayName |
933 | +Description = mock.Description |
934 | +Art = /mock.Art |
935 | +Icon = /mock.Icon |
936 | +SearchHint = mock.SearchHint |
937 | +HotKey = mock.HotKey |
938 | +Author = mock.Author |
939 | + |
940 | +[Appearance] |
941 | +PageHeader.Logo = http://assets.ubuntu.com/sites/ubuntu/1110/u/img/logos/logo-ubuntu-orange.svg |
942 | +PageHeader.ForegroundColor = white |
943 | +PageHeader.Background = color://black |
944 | +ShapeImages = false |
945 | |
946 | === modified file 'tests/resultstest.cpp' |
947 | --- tests/resultstest.cpp 2015-11-02 10:11:24 +0000 |
948 | +++ tests/resultstest.cpp 2015-11-02 10:11:24 +0000 |
949 | @@ -101,7 +101,8 @@ |
950 | shr::CustomRegistry::Parameters({ |
951 | TEST_DATA_DIR "mock-scope/mock-scope.ini", |
952 | TEST_DATA_DIR "mock-scope-info/mock-scope-info.ini", |
953 | - TEST_DATA_DIR "mock-scope-ttl/mock-scope-ttl.ini" |
954 | + TEST_DATA_DIR "mock-scope-ttl/mock-scope-ttl.ini", |
955 | + TEST_DATA_DIR "mock-scope-manyresults/mock-scope-manyresults.ini" |
956 | }) |
957 | ); |
958 | } |
959 | @@ -573,41 +574,6 @@ |
960 | ); |
961 | } |
962 | |
963 | -// FIXME Add code to harness to test special categories |
964 | -// void testSpecialCategory() |
965 | -// { |
966 | -// auto resultsView = m_harness->resultsView(); |
967 | -// resultsView->setActiveScope("mock-scope"); |
968 | -// resultsView->setQuery(""); |
969 | -// |
970 | -// auto categories = resultsView->raw_categories(); |
971 | -// QString rawTemplate(R"({"schema-version": 1, "template": {"category-layout": "special"}})"); |
972 | -// CountObject* countObject = new CountObject(categories); |
973 | -// categories->addSpecialCategory("special", "Special", "", rawTemplate, countObject); |
974 | -// |
975 | -// // should have 2 categories now |
976 | -// QCOMPARE(categories->rowCount(), 2); |
977 | -// QCOMPARE(categories->data(categories->index(0), ss::CategoriesInterface::Roles::RoleCount).toInt(), 0); |
978 | -// countObject->setCount(1); |
979 | -// QCOMPARE(categories->data(categories->index(0), ss::CategoriesInterface::Roles::RoleCount).toInt(), 1); |
980 | -// |
981 | -// qRegisterMetaType<QVector<int>>(); |
982 | -// QSignalSpy spy(categories, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
983 | -// |
984 | -// countObject->setCountAsync(13); |
985 | -// QCOMPARE(categories->data(categories->index(0), ss::CategoriesInterface::Roles::RoleCount).toInt(), 1); |
986 | -// QTRY_COMPARE(categories->data(categories->index(0), ss::CategoriesInterface::Roles::RoleCount).toInt(), 13); |
987 | -// |
988 | -// // expecting a few dataChanged signals, count should have changed |
989 | -// bool countChanged = false; |
990 | -// while (!spy.empty() && !countChanged) { |
991 | -// QList<QVariant> arguments = spy.takeFirst(); |
992 | -// auto roles = arguments.at(2).value<QVector<int>>(); |
993 | -// countChanged |= roles.contains(ss::CategoriesInterface::Roles::RoleCount); |
994 | -// } |
995 | -// QCOMPARE(countChanged, true); |
996 | -// } |
997 | - |
998 | void testCategoryWithRating() |
999 | { |
1000 | auto resultsView = m_harness->resultsView(); |
1001 | @@ -1027,6 +993,76 @@ |
1002 | QCOMPARE(resultsView->status(), ss::ScopeInterface::Status::NoInternet); |
1003 | } |
1004 | |
1005 | + void testResultsModelChanges() |
1006 | + { |
1007 | + auto resultsView = m_harness->resultsView(); |
1008 | + resultsView->setActiveScope("mock-scope-manyresults"); |
1009 | + resultsView->setQuery("search1"); |
1010 | + QVERIFY_MATCHRESULT( |
1011 | + shm::CategoryListMatcher() |
1012 | + .hasExactly(1) |
1013 | + .category(shm::CategoryMatcher("cat1") |
1014 | + .result(shm::ResultMatcher("cat1_uri0")) |
1015 | + .result(shm::ResultMatcher("cat1_uri1")) |
1016 | + .result(shm::ResultMatcher("cat1_uri2")) |
1017 | + .result(shm::ResultMatcher("cat1_uri3")) |
1018 | + .result(shm::ResultMatcher("cat1_uri4")) |
1019 | + ) |
1020 | + .match(resultsView->categories()) |
1021 | + ); |
1022 | + |
1023 | + resultsView->setQuery("search2"); |
1024 | + QVERIFY_MATCHRESULT( |
1025 | + shm::CategoryListMatcher() |
1026 | + .hasExactly(2) |
1027 | + .category(shm::CategoryMatcher("cat1") |
1028 | + .result(shm::ResultMatcher("cat1_uri3")) |
1029 | + .result(shm::ResultMatcher("cat1_uri4")) |
1030 | + .result(shm::ResultMatcher("cat1_uri5")) |
1031 | + .result(shm::ResultMatcher("cat1_uri6")) |
1032 | + .result(shm::ResultMatcher("cat1_uri7")) |
1033 | + ) |
1034 | + .category(shm::CategoryMatcher("cat2") |
1035 | + .result(shm::ResultMatcher("cat2_uri3")) |
1036 | + .result(shm::ResultMatcher("cat2_uri4")) |
1037 | + .result(shm::ResultMatcher("cat2_uri5")) |
1038 | + .result(shm::ResultMatcher("cat2_uri6")) |
1039 | + .result(shm::ResultMatcher("cat2_uri7")) |
1040 | + ) |
1041 | + .match(resultsView->categories()) |
1042 | + ); |
1043 | + |
1044 | + resultsView->setQuery("search3"); |
1045 | + QVERIFY_MATCHRESULT( |
1046 | + shm::CategoryListMatcher() |
1047 | + .hasExactly(2) |
1048 | + .category(shm::CategoryMatcher("cat1") |
1049 | + .result(shm::ResultMatcher("cat1_uri7")) |
1050 | + .result(shm::ResultMatcher("cat1_uri6")) |
1051 | + .result(shm::ResultMatcher("cat1_uri5")) |
1052 | + .result(shm::ResultMatcher("cat1_uri4")) |
1053 | + .result(shm::ResultMatcher("cat1_uri3")) |
1054 | + ) |
1055 | + .category(shm::CategoryMatcher("cat2") |
1056 | + .result(shm::ResultMatcher("cat2_uri7")) |
1057 | + .result(shm::ResultMatcher("cat2_uri6")) |
1058 | + .result(shm::ResultMatcher("cat2_uri5")) |
1059 | + .result(shm::ResultMatcher("cat2_uri4")) |
1060 | + .result(shm::ResultMatcher("cat2_uri3")) |
1061 | + ) |
1062 | + .match(resultsView->categories()) |
1063 | + ); |
1064 | + |
1065 | + resultsView->setQuery("search4"); |
1066 | + QVERIFY_MATCHRESULT( |
1067 | + shm::CategoryListMatcher() |
1068 | + .hasExactly(1) |
1069 | + .category(shm::CategoryMatcher("cat2") |
1070 | + .result(shm::ResultMatcher("cat2_uri5")) |
1071 | + ) |
1072 | + .match(resultsView->categories()) |
1073 | + ); |
1074 | + } |
1075 | }; |
1076 | |
1077 | QTEST_GUILESS_MAIN(ResultsTest) |
FAILED: Continuous integration, rev:275 jenkins. qa.ubuntu. com/job/ unity-scopes- shell-ci/ 387/ jenkins. qa.ubuntu. com/job/ unity-scopes- shell-wily- amd64-ci/ 44/console jenkins. qa.ubuntu. com/job/ unity-scopes- shell-wily- armhf-ci/ 44/console jenkins. qa.ubuntu. com/job/ unity-scopes- shell-wily- i386-ci/ 44/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- scopes- shell-ci/ 387/rebuild
http://