Merge lp:~alecu/unity-scope-click/uninstallable-scopes into lp:unity-scope-click/devel

Proposed by Alejandro J. Cura
Status: Merged
Approved by: dobey
Approved revision: 297
Merged at revision: 293
Proposed branch: lp:~alecu/unity-scope-click/uninstallable-scopes
Merge into: lp:unity-scope-click/devel
Prerequisite: lp:~alecu/unity-scope-click/a-few-renames
Diff against target: 851 lines (+419/-85)
10 files modified
libclickscope/click/interface.cpp (+56/-6)
libclickscope/click/interface.h (+9/-0)
libclickscope/click/package.cpp (+5/-4)
libclickscope/click/package.h (+9/-0)
libclickscope/click/preview.cpp (+39/-17)
libclickscope/tests/fake_json.h (+72/-0)
libclickscope/tests/test_interface.cpp (+96/-0)
scope/clickstore/store-query.cpp (+36/-24)
scope/clickstore/store-query.h (+5/-3)
scope/tests/test_query.cpp (+92/-31)
To merge this branch: bzr merge lp:~alecu/unity-scope-click/uninstallable-scopes
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
dobey (community) Approve
Paweł Stołowski (community) Approve
Review via email: mp+223005@code.launchpad.net

Commit message

- label installed packages as such in results
- tweak icon size in surfacing to align with added subtitle
- allow launching installed scopes from package preview
- no longer filter out installed packages in store scope

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
292. By Alejandro J. Cura

Show debugging information when creating uri to open scope

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
293. By Alejandro J. Cura

Add more debugging logs

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Clicking on the "Search" button is not working due to bug #1329890 in unity-scope-shell
Once that's fixed it should work with no changes needed here.

Revision history for this message
dobey (dobey) wrote :

/home/phablet/uninstallable-scopes/libclickscope/click/interface.cpp:406:56: error: converting to ‘std::unordered_set<click::Package>’ from initializer list would use explicit constructor ‘std::unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(std::unordered_set<_Value, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Value = click::Package; _Hash = std::hash<click::Package>; _Pred = std::equal_to<click::Package>; _Alloc = std::allocator<click::Package>; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::size_type = unsigned int; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::hasher = std::hash<click::Package>; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<click::Package>; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<click::Package>]’
                 callback({}, InterfaceError::ParseError);
                                                        ^
/home/phablet/uninstallable-scopes/libclickscope/click/interface.cpp:410:51: error: converting to ‘std::unordered_set<click::Package>’ from initializer list would use explicit constructor ‘std::unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(std::unordered_set<_Value, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Value = click::Package; _Hash = std::hash<click::Package>; _Pred = std::equal_to<click::Package>; _Alloc = std::allocator<click::Package>; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::size_type = unsigned int; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::hasher = std::hash<click::Package>; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<click::Package>; std::unordered_set<_Value, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<click::Package>]’
             callback({}, InterfaceError::CallError);
                                                   ^
libclickscope/click/CMakeFiles/clickscope.dir/build.make:126: recipe for target 'libclickscope/click/CMakeFiles/clickscope.dir/interface.cpp.o' failed

These compilation errors with gcc 4.9 seem to be a result of the changes in this branch. Can we have some plan to fix them before we get upgraded to 4.9 again, even though this compiles ok under 4.8?

review: Needs Information
Revision history for this message
dobey (dobey) :
review: Needs Information
294. By Alejandro J. Cura

Merged from prereq branch

295. By Alejandro J. Cura

Move the hashing inside the Package class

296. By Alejandro J. Cura

Handle multiple apps and scopes in the same package, caps for ✔ INSTALLED label, misc code renamings

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

619 +#include <unordered_set>

This doesn't seem to be required in the context of this file?

240 + if (error == click::InterfaceError::NoError) {
241 + uri = "application:///" + val;
242 + }

Can you log an error here, if any?

13 +#include <streambuf>

Is this needed?

review: Needs Fixing
297. By Alejandro J. Cura

Removed unused includes and added error messages requested in Pawel's review

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Thanks, LGTM!

review: Approve
Revision history for this message
dobey (dobey) wrote :

I still have a concern about the version not being used in the operator== for Package, as in its current state it can result in the store showing "INSTALLED" for a newer version of the package than is installed, which, when the new preview design is implemented, can result in the change log showing disparate information in the preview, resulting in the app running without any new changes, when the update has not been installed. However, this is a new issue, primarily with a design that is not yet implemented, so I don't think we should block this branch on that.

Also, the po/POTFILES.in will need updated, but I will do that in a separate branch.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'libclickscope/click/interface.cpp'
--- libclickscope/click/interface.cpp 2014-06-13 20:44:52 +0000
+++ libclickscope/click/interface.cpp 2014-06-16 14:09:25 +0000
@@ -33,9 +33,11 @@
33#include <QStandardPaths>33#include <QStandardPaths>
34#include <QTimer>34#include <QTimer>
3535
36#include <cstdio>
36#include <list>37#include <list>
37#include <sys/stat.h>38#include <sys/stat.h>
38#include <map>39#include <map>
40#include <sstream>
3941
40#include <boost/locale/collator.hpp>42#include <boost/locale/collator.hpp>
41#include <boost/locale/generator.hpp>43#include <boost/locale/generator.hpp>
@@ -47,7 +49,6 @@
4749
48#include <unity/UnityExceptions.h>50#include <unity/UnityExceptions.h>
49#include <unity/util/IniParser.h>51#include <unity/util/IniParser.h>
50#include <sstream>
5152
52#include "interface.h"53#include "interface.h"
53#include <click/key_file_locator.h>54#include <click/key_file_locator.h>
@@ -368,10 +369,16 @@
368369
369 BOOST_FOREACH(ptree::value_type &sv, pt.get_child("hooks"))370 BOOST_FOREACH(ptree::value_type &sv, pt.get_child("hooks"))
370 {371 {
371 // FIXME: "primary app" for a package is not defined, we just372 // FIXME: "primary app or scope" for a package is not defined,
372 // use first one here:373 // we just use first one here:
373 manifest.first_app_name = sv.first;374 auto app_name = sv.second.get("desktop", "");
374 break;375 if (manifest.first_app_name.empty() && !app_name.empty()) {
376 manifest.first_app_name = sv.first;
377 }
378 auto scope_id = sv.second.get("scope", "");
379 if (manifest.first_scope_id.empty() && !scope_id.empty()) {
380 manifest.first_scope_id = manifest.name; // need to change this for more than one scope per click
381 }
375 }382 }
376 qDebug() << "adding manifest: " << manifest.name.c_str() << manifest.version.c_str() << manifest.first_app_name.c_str();383 qDebug() << "adding manifest: " << manifest.name.c_str() << manifest.version.c_str() << manifest.first_app_name.c_str();
377384
@@ -398,20 +405,63 @@
398 });405 });
399}406}
400407
408PackageSet package_names_from_stdout(const std::string& stdout_data)
409{
410 const char TAB='\t', NEWLINE='\n';
411 std::istringstream iss(stdout_data);
412 PackageSet installed_packages;
413
414 while (iss.peek() != EOF) {
415 Package p;
416 std::getline(iss, p.name, TAB);
417 std::getline(iss, p.version, NEWLINE);
418 if (iss.eof() || p.name.empty() || p.version.empty()) {
419 throw std::runtime_error("Error encountered parsing 'click list' output");
420 }
421 installed_packages.insert(p);
422 }
423
424 return installed_packages;
425}
426
427void Interface::get_installed_packages(std::function<void(PackageSet, InterfaceError)> callback)
428{
429 std::string command = "click list";
430 qDebug() << "Running command:" << command.c_str();
431 run_process(command, [callback](int code, const std::string& stdout_data, const std::string& stderr_data) {
432 if (code == 0) {
433 try {
434 PackageSet package_names = package_names_from_stdout(stdout_data);
435 callback(package_names, InterfaceError::NoError);
436 } catch (...) {
437 qWarning() << "Can't parse 'click list' output: " << QString::fromStdString(stdout_data);
438 callback({}, InterfaceError::ParseError);
439 }
440 } else {
441 qWarning() << "Error" << code << "running 'click list': " << QString::fromStdString(stderr_data);
442 callback({}, InterfaceError::CallError);
443 }
444 });
445}
446
401void Interface::get_manifest_for_app(const std::string &app_id,447void Interface::get_manifest_for_app(const std::string &app_id,
402 std::function<void(Manifest, InterfaceError)> callback)448 std::function<void(Manifest, InterfaceError)> callback)
403{449{
404 std::string command = "click info " + app_id;450 std::string command = "click info " + app_id;
405 qDebug() << "Running command:" << command.c_str();451 qDebug() << "Running command:" << command.c_str();
406 run_process(command, [callback](int code, const std::string& stdout_data, const std::string&) {452 run_process(command, [callback, app_id](int code, const std::string& stdout_data, const std::string& stderr_data) {
407 if (code == 0) {453 if (code == 0) {
408 try {454 try {
409 Manifest manifest = manifest_from_json(stdout_data);455 Manifest manifest = manifest_from_json(stdout_data);
410 callback(manifest, InterfaceError::NoError);456 callback(manifest, InterfaceError::NoError);
411 } catch (...) {457 } catch (...) {
458 qWarning() << "Can't parse 'click info" << QString::fromStdString(app_id)
459 << "' output: " << QString::fromStdString(stdout_data);
412 callback(Manifest(), InterfaceError::ParseError);460 callback(Manifest(), InterfaceError::ParseError);
413 }461 }
414 } else {462 } else {
463 qWarning() << "Error" << code << "running 'click info" << QString::fromStdString(app_id)
464 << "': " << QString::fromStdString(stderr_data);
415 callback(Manifest(), InterfaceError::CallError);465 callback(Manifest(), InterfaceError::CallError);
416 }466 }
417 });467 });
418468
=== modified file 'libclickscope/click/interface.h'
--- libclickscope/click/interface.h 2014-06-13 20:44:52 +0000
+++ libclickscope/click/interface.h 2014-06-16 14:09:25 +0000
@@ -38,6 +38,7 @@
38#include <unordered_set>38#include <unordered_set>
3939
40#include "application.h"40#include "application.h"
41#include "package.h"
4142
42namespace click43namespace click
43{44{
@@ -59,7 +60,14 @@
59 std::string name;60 std::string name;
60 std::string version;61 std::string version;
61 std::string first_app_name;62 std::string first_app_name;
63 std::string first_scope_id;
62 bool removable = false;64 bool removable = false;
65 bool has_any_apps() const {
66 return !first_app_name.empty();
67 }
68 bool has_any_scopes() const {
69 return !first_scope_id.empty();
70 }
63};71};
6472
65enum class InterfaceError {NoError, CallError, ParseError};73enum class InterfaceError {NoError, CallError, ParseError};
@@ -89,6 +97,7 @@
89 static bool is_icon_identifier(const std::string &icon_id);97 static bool is_icon_identifier(const std::string &icon_id);
90 static std::string add_theme_scheme(const std::string &filename);98 static std::string add_theme_scheme(const std::string &filename);
91 virtual void get_manifests(std::function<void(ManifestList, InterfaceError)> callback);99 virtual void get_manifests(std::function<void(ManifestList, InterfaceError)> callback);
100 virtual void get_installed_packages(std::function<void(PackageSet, InterfaceError)> callback);
92 virtual void get_manifest_for_app(const std::string &app_id, std::function<void(Manifest, InterfaceError)> callback);101 virtual void get_manifest_for_app(const std::string &app_id, std::function<void(Manifest, InterfaceError)> callback);
93 virtual void get_dotdesktop_filename(const std::string &app_id,102 virtual void get_dotdesktop_filename(const std::string &app_id,
94 std::function<void(std::string filename, InterfaceError)> callback);103 std::function<void(std::string filename, InterfaceError)> callback);
95104
=== modified file 'libclickscope/click/package.cpp'
--- libclickscope/click/package.cpp 2014-06-12 21:05:25 +0000
+++ libclickscope/click/package.cpp 2014-06-16 14:09:25 +0000
@@ -41,10 +41,11 @@
41}41}
4242
43bool operator==(const Package& lhs, const Package& rhs) {43bool operator==(const Package& lhs, const Package& rhs) {
44 return lhs.name == rhs.name &&44 // We can't include the version in the comparison here, because this
45 lhs.title == rhs.title &&45 // comparison is used by the sorted_set, that we use to compare a package
46 lhs.price == rhs.price &&46 // installed locally on the device with a (possibly updated) package available in the store.
47 lhs.icon_url == rhs.icon_url;47 return lhs.name == rhs.name;
48
48}49}
4950
50bool operator==(const PackageDetails& lhs, const PackageDetails& rhs) {51bool operator==(const PackageDetails& lhs, const PackageDetails& rhs) {
5152
=== modified file 'libclickscope/click/package.h'
--- libclickscope/click/package.h 2014-06-13 19:58:45 +0000
+++ libclickscope/click/package.h 2014-06-16 14:09:25 +0000
@@ -92,9 +92,18 @@
92 std::string url;92 std::string url;
93 std::string version;93 std::string version;
94 void matches (std::string query, std::function<bool> callback);94 void matches (std::string query, std::function<bool> callback);
95
96 struct hash_name {
97 public :
98 size_t operator()(const Package &package ) const
99 {
100 return std::hash<std::string>()(package.name);
101 }
102 };
95};103};
96104
97typedef std::vector<Package> Packages;105typedef std::vector<Package> Packages;
106typedef std::unordered_set<Package, Package::hash_name> PackageSet;
98107
99Package package_from_json_node(const Json::Value& item);108Package package_from_json_node(const Json::Value& item);
100Packages package_list_from_json(const std::string& json);109Packages package_list_from_json(const std::string& json);
101110
=== modified file 'libclickscope/click/preview.cpp'
--- libclickscope/click/preview.cpp 2014-06-12 21:05:25 +0000
+++ libclickscope/click/preview.cpp 2014-06-16 14:09:25 +0000
@@ -36,6 +36,7 @@
36#include <boost/algorithm/string/replace.hpp>36#include <boost/algorithm/string/replace.hpp>
3737
38#include <unity/UnityExceptions.h>38#include <unity/UnityExceptions.h>
39#include <unity/scopes/CannedQuery.h>
39#include <unity/scopes/PreviewReply.h>40#include <unity/scopes/PreviewReply.h>
40#include <unity/scopes/Variant.h>41#include <unity/scopes/Variant.h>
41#include <unity/scopes/VariantBuilder.h>42#include <unity/scopes/VariantBuilder.h>
@@ -500,14 +501,23 @@
500 scopes::PreviewWidgetList widgets;501 scopes::PreviewWidgetList widgets;
501 scopes::PreviewWidget buttons("buttons", "actions");502 scopes::PreviewWidget buttons("buttons", "actions");
502 scopes::VariantBuilder builder;503 scopes::VariantBuilder builder;
504
505 std::string open_label = _("Open");
506
507 if (!manifest.has_any_apps() && manifest.has_any_scopes()) {
508 open_label = _("Search");
509 }
510
503 if (!uri.empty())511 if (!uri.empty())
504 {512 {
505 builder.add_tuple(513 builder.add_tuple(
506 {514 {
507 {"id", scopes::Variant(click::Preview::Actions::OPEN_CLICK)},515 {"id", scopes::Variant(click::Preview::Actions::OPEN_CLICK)},
508 {"label", scopes::Variant(_("Open"))},516 {"label", scopes::Variant(open_label)},
509 {"uri", scopes::Variant(uri)}517 {"uri", scopes::Variant(uri)}
510 });518 });
519 qDebug() << "Adding button" << QString::fromStdString(open_label) << "-"
520 << QString::fromStdString(uri);
511 }521 }
512 if (manifest.removable)522 if (manifest.removable)
513 {523 {
@@ -523,30 +533,42 @@
523 return widgets;533 return widgets;
524}534}
525535
526void InstalledPreview::getApplicationUri(const Manifest& /*manifest*/, std::function<void(const std::string&)> callback)536void InstalledPreview::getApplicationUri(const Manifest& manifest, std::function<void(const std::string&)> callback)
527{537{
528 std::string uri;
529 QString app_url = QString::fromStdString(result.uri());538 QString app_url = QString::fromStdString(result.uri());
530539
531 // asynchronously get application uri based on app name, if the uri is not application://.540 // asynchronously get application uri based on app name, if the uri is not application://.
532 // this can happen if the app was just installed and we have its http uri from the Result.541 // this can happen if the app was just installed and we have its http uri from the Result.
533 if (!app_url.startsWith("application:///")) {542 if (!app_url.startsWith("application:///")) {
534 const std::string name = result["name"].get_string();543 const std::string name = result["name"].get_string();
535 qt::core::world::enter_with_task([this, name, callback] ()544
536 {545 if (manifest.has_any_apps()) {
537 click::Interface().get_dotdesktop_filename(name,546 qt::core::world::enter_with_task([this, name, callback] ()
538 [callback] (std::string val, click::InterfaceError error) {547 {
539 std::string uri;548 click::Interface().get_dotdesktop_filename(name,
540 if (error == click::InterfaceError::NoError) {549 [callback, name] (std::string val, click::InterfaceError error) {
541 uri = "application:///" + val;550 std::string uri;
542 }551 if (error == click::InterfaceError::NoError) {
543 callback(uri);552 uri = "application:///" + val;
544 }553 } else {
545 );554 qWarning() << "Can't get .desktop filename for"
546 });555 << QString::fromStdString(name);
556 }
557 callback(uri);
558 }
559 );
560 });
561 } else {
562 if (manifest.has_any_scopes()) {
563 unity::scopes::CannedQuery cq(manifest.first_scope_id);
564 auto scope_uri = cq.to_uri();
565 qDebug() << "Found uri for scope" << QString::fromStdString(manifest.first_scope_id)
566 << "-" << QString::fromStdString(scope_uri);
567 callback(scope_uri);
568 }
569 }
547 } else {570 } else {
548 uri = app_url.toStdString();571 callback(result.uri());
549 callback(uri);
550 }572 }
551}573}
552574
553575
=== modified file 'libclickscope/tests/fake_json.h'
--- libclickscope/tests/fake_json.h 2014-05-26 14:27:31 +0000
+++ libclickscope/tests/fake_json.h 2014-06-16 14:09:25 +0000
@@ -176,4 +176,76 @@
176 }176 }
177)foo";177)foo";
178178
179const std::string FAKE_JSON_MANIFEST_ONE_APP = R"foo(
180 {
181 "_removable": 1,
182 "name": "com.example.fake-app",
183 "version": "0.1",
184 "hooks": {
185 "fake-app": {
186 "apparmor": "fake-app.json",
187 "desktop": "fake-app.desktop"
188 }
189 }
190 }
191)foo";
192
193const std::string FAKE_JSON_MANIFEST_ONE_SCOPE = R"foo(
194 {
195 "_removable": 1,
196 "name": "com.example.fake-scope",
197 "version": "0.1",
198 "hooks": {
199 "fake-scope": {
200 "apparmor": "scope-security.json",
201 "scope": "fake-scope"
202 }
203 }
204 }
205)foo";
206
207const std::string FAKE_JSON_MANIFEST_ONE_APP_ONE_SCOPE = R"foo(
208 {
209 "_removable": 1,
210 "name": "com.example.fake-1app-1scope",
211 "version": "0.1",
212 "hooks": {
213 "fake-app": {
214 "apparmor": "fake-app.json",
215 "desktop": "fake-app.desktop"
216 },
217 "fake-scope": {
218 "apparmor": "scope-security.json",
219 "scope": "fake-scope"
220 }
221 }
222 }
223)foo";
224
225const std::string FAKE_JSON_MANIFEST_TWO_APPS_TWO_SCOPES = R"foo(
226 {
227 "_removable": 1,
228 "name": "com.example.fake-2apps-2scopes",
229 "version": "0.1",
230 "hooks": {
231 "fake-app1": {
232 "apparmor": "fake-app1.json",
233 "desktop": "fake-app1.desktop"
234 },
235 "fake-app2": {
236 "apparmor": "fake-app2.json",
237 "desktop": "fake-app2.desktop"
238 },
239 "fake-scope1": {
240 "apparmor": "scope-security1.json",
241 "scope": "fake-scope1"
242 },
243 "fake-scope2": {
244 "apparmor": "scope-security1.json",
245 "scope": "fake-scope2"
246 }
247 }
248 }
249)foo";
250
179#endif // FAKE_JSON_H251#endif // FAKE_JSON_H
180252
=== modified file 'libclickscope/tests/test_interface.cpp'
--- libclickscope/tests/test_interface.cpp 2014-06-13 20:44:52 +0000
+++ libclickscope/tests/test_interface.cpp 2014-06-16 14:09:25 +0000
@@ -119,6 +119,7 @@
119public:119public:
120 MOCK_METHOD2(manifest_callback, void(Manifest, InterfaceError));120 MOCK_METHOD2(manifest_callback, void(Manifest, InterfaceError));
121 MOCK_METHOD2(manifests_callback, void(ManifestList, InterfaceError));121 MOCK_METHOD2(manifests_callback, void(ManifestList, InterfaceError));
122 MOCK_METHOD2(installed_callback, void(PackageSet, InterfaceError));
122};123};
123124
124}125}
@@ -447,6 +448,40 @@
447 EXPECT_FALSE(iface.show_desktop_apps());448 EXPECT_FALSE(iface.show_desktop_apps());
448}449}
449450
451TEST(ClickInterface, testManifestFromJsonOneApp)
452{
453 Manifest m = manifest_from_json(FAKE_JSON_MANIFEST_ONE_APP);
454 ASSERT_EQ(m.first_app_name, "fake-app");
455 ASSERT_TRUE(m.has_any_apps());
456 ASSERT_FALSE(m.has_any_scopes());
457}
458
459TEST(ClickInterface, testManifestFromJsonOneScope)
460{
461 Manifest m = manifest_from_json(FAKE_JSON_MANIFEST_ONE_SCOPE);
462 ASSERT_EQ(m.first_scope_id, "com.example.fake-scope");
463 ASSERT_FALSE(m.has_any_apps());
464 ASSERT_TRUE(m.has_any_scopes());
465}
466
467TEST(ClickInterface, testManifestFromJsonOneAppOneScope)
468{
469 Manifest m = manifest_from_json(FAKE_JSON_MANIFEST_ONE_APP_ONE_SCOPE);
470 ASSERT_EQ(m.first_app_name, "fake-app");
471 ASSERT_EQ(m.first_scope_id, "com.example.fake-1app-1scope");
472 ASSERT_TRUE(m.has_any_apps());
473 ASSERT_TRUE(m.has_any_scopes());
474}
475
476TEST(ClickInterface, testManifestFromJsonTwoAppsTwoScopes)
477{
478 Manifest m = manifest_from_json(FAKE_JSON_MANIFEST_TWO_APPS_TWO_SCOPES);
479 ASSERT_EQ(m.first_app_name, "fake-app1");
480 ASSERT_EQ(m.first_scope_id, "com.example.fake-2apps-2scopes");
481 ASSERT_TRUE(m.has_any_apps());
482 ASSERT_TRUE(m.has_any_scopes());
483}
484
450TEST(ClickInterface, testGetManifestForAppCorrectCommand)485TEST(ClickInterface, testGetManifestForAppCorrectCommand)
451{486{
452 FakeClickInterface iface;487 FakeClickInterface iface;
@@ -584,3 +619,64 @@
584 ASSERT_TRUE(manifests.size() == expected.size());619 ASSERT_TRUE(manifests.size() == expected.size());
585 });620 });
586}621}
622
623TEST(ClickInterface, testGetInstalledPackagesCorrectCommand)
624{
625 FakeClickInterface iface;
626 std::string command = "click list";
627 EXPECT_CALL(iface, run_process(command, _)).
628 Times(1);
629 iface.get_installed_packages([](PackageSet, InterfaceError){});
630}
631
632TEST_F(ClickInterfaceTest, testGetInstalledPackagesParseError)
633{
634 FakeClickInterface iface;
635 EXPECT_CALL(iface, run_process(_, _)).
636 Times(1).
637 WillOnce(Invoke([&](const std::string&,
638 std::function<void(int, const std::string&,
639 const std::string&)> callback){
640 callback(0, "valid.package\t1.0\nINVALID LINE", "");
641 }));
642 EXPECT_CALL(*this, installed_callback(_, InterfaceError::ParseError));
643 iface.get_installed_packages([this](PackageSet package_names, InterfaceError error){
644 installed_callback(package_names, error);
645 });
646}
647
648TEST_F(ClickInterfaceTest, testGetInstalledPackagesCommandFailed)
649{
650 FakeClickInterface iface;
651 EXPECT_CALL(iface, run_process(_, _)).
652 Times(1).
653 WillOnce(Invoke([&](const std::string&,
654 std::function<void(int, const std::string&,
655 const std::string&)> callback){
656 callback(-1, "", "CRITICAL: FAIL");
657 }));
658 EXPECT_CALL(*this, installed_callback(_, InterfaceError::CallError));
659 iface.get_installed_packages([this](PackageSet package_names, InterfaceError error){
660 installed_callback(package_names, error);
661 });
662}
663
664TEST_F(ClickInterfaceTest, testGetInstalledPackagesParsed)
665{
666 FakeClickInterface iface;
667 std::string sample_stdout = "ABC\t0.1\nDEF\t0.2\n";
668 PackageSet expected{{"ABC", "0.1"}, {"DEF", "0.2"}};
669
670 EXPECT_CALL(iface, run_process(_, _)).
671 Times(1).
672 WillOnce(Invoke([&](const std::string&,
673 std::function<void(int, const std::string&,
674 const std::string&)> callback){
675 callback(0, sample_stdout, "");
676 }));
677 iface.get_installed_packages([expected](PackageSet package_names, InterfaceError error){
678 ASSERT_EQ(error, InterfaceError::NoError);
679 ASSERT_EQ(package_names, expected);
680 });
681}
682
587683
=== modified file 'scope/clickstore/store-query.cpp'
--- scope/clickstore/store-query.cpp 2014-06-12 21:05:25 +0000
+++ scope/clickstore/store-query.cpp 2014-06-16 14:09:25 +0000
@@ -48,6 +48,8 @@
4848
49#include <click/click-i18n.h>49#include <click/click-i18n.h>
5050
51using namespace click;
52
51namespace53namespace
52{54{
5355
@@ -60,10 +62,10 @@
60 },62 },
61 "components" : {63 "components" : {
62 "title" : "title",64 "title" : "title",
65 "subtitle": "subtitle",
63 "art" : {66 "art" : {
64 "field": "art",67 "field": "art",
65 "aspect-ratio": 1.6,68 "aspect-ratio": 1.13
66 "fill-mode": "fit"
67 }69 }
68 }70 }
69 }71 }
@@ -82,7 +84,7 @@
82 "mascot" : {84 "mascot" : {
83 "field": "art"85 "field": "art"
84 },86 },
85 "subtitle": "publisher"87 "subtitle": "subtitle"
86 }88 }
87 }89 }
88)";90)";
@@ -119,9 +121,7 @@
119 impl->search_operation.cancel();121 impl->search_operation.cancel();
120}122}
121123
122namespace124click::Interface& click::Query::clickInterfaceInstance()
123{
124click::Interface& clickInterfaceInstance()
125{125{
126 static QSharedPointer<click::KeyFileLocator> keyFileLocator(new click::KeyFileLocator());126 static QSharedPointer<click::KeyFileLocator> keyFileLocator(new click::KeyFileLocator());
127 static click::Interface iface(keyFileLocator);127 static click::Interface iface(keyFileLocator);
@@ -129,8 +129,6 @@
129 return iface;129 return iface;
130}130}
131131
132}
133
134bool click::Query::push_result(scopes::SearchReplyProxy const& searchReply, const scopes::CategorisedResult &res)132bool click::Query::push_result(scopes::SearchReplyProxy const& searchReply, const scopes::CategorisedResult &res)
135{133{
136 return searchReply->push(res);134 return searchReply->push(res);
@@ -158,7 +156,7 @@
158}156}
159157
160void click::Query::add_available_apps(scopes::SearchReplyProxy const& searchReply,158void click::Query::add_available_apps(scopes::SearchReplyProxy const& searchReply,
161 const std::set<std::string>& locallyInstalledApps,159 const PackageSet& installedPackages,
162 const std::string& categoryTemplate)160 const std::string& categoryTemplate)
163{161{
164 scopes::CategoryRenderer categoryRenderer(categoryTemplate);162 scopes::CategoryRenderer categoryRenderer(categoryTemplate);
@@ -166,7 +164,7 @@
166164
167 run_under_qt([=]()165 run_under_qt([=]()
168 {166 {
169 auto search_cb = [this, searchReply, category, locallyInstalledApps](click::Packages packages) {167 auto search_cb = [this, searchReply, category, installedPackages](Packages packages) {
170 qDebug("search callback");168 qDebug("search callback");
171169
172 // handle packages data170 // handle packages data
@@ -174,16 +172,20 @@
174 qDebug() << "pushing result" << QString::fromStdString(p.name);172 qDebug() << "pushing result" << QString::fromStdString(p.name);
175 try {173 try {
176 scopes::CategorisedResult res(category);174 scopes::CategorisedResult res(category);
177 // TODO: mark as installed
178 if (locallyInstalledApps.count(p.name) > 0) {
179 qDebug() << "already installed" << QString::fromStdString(p.name);
180 continue;
181 }
182 res.set_title(p.title);175 res.set_title(p.title);
183 res.set_art(p.icon_url);176 res.set_art(p.icon_url);
184 res.set_uri(p.url);177 res.set_uri(p.url);
185 res[click::Query::ResultKeys::NAME] = p.name;178 res[click::Query::ResultKeys::NAME] = p.name;
186 res[click::Query::ResultKeys::INSTALLED] = false;179 auto installed = installedPackages.find(p);
180 if (installed != installedPackages.end()) {
181 res[click::Query::ResultKeys::INSTALLED] = true;
182 res["subtitle"] = _("✔ INSTALLED");
183 res[click::Query::ResultKeys::VERSION] = installed->version;
184 } else {
185 res[click::Query::ResultKeys::INSTALLED] = false;
186 // TODO: get the real price from the webservice (upcoming branch)
187 res["subtitle"] = _("FREE");
188 }
187189
188 this->push_result(searchReply, res);190 this->push_result(searchReply, res);
189 } catch(const std::exception& e){191 } catch(const std::exception& e){
@@ -201,6 +203,23 @@
201 });203 });
202}204}
203205
206PackageSet click::Query::get_installed_packages()
207{
208 std::promise<PackageSet> installed_promise;
209 std::future<PackageSet> installed_future = installed_promise.get_future();
210
211 run_under_qt([&]()
212 {
213 clickInterfaceInstance().get_installed_packages(
214 [&installed_promise](PackageSet installedPackages, InterfaceError){
215 installed_promise.set_value(installedPackages);
216 });
217 });
218
219 return installed_future.get();
220}
221
222
204void click::Query::run(scopes::SearchReplyProxy const& searchReply)223void click::Query::run(scopes::SearchReplyProxy const& searchReply)
205{224{
206 auto query = impl->query.query_string();225 auto query = impl->query.query_string();
@@ -208,12 +227,6 @@
208 if (query.empty()) {227 if (query.empty()) {
209 categoryTemplate = CATEGORY_APPS_DISPLAY;228 categoryTemplate = CATEGORY_APPS_DISPLAY;
210 }229 }
211 auto localResults = clickInterfaceInstance().find_installed_apps(query);
212
213 std::set<std::string> locallyInstalledApps;
214 for(const auto& app : localResults) {
215 locallyInstalledApps.insert(app.name);
216 }
217230
218 static const std::string no_net_hint("no-internet");231 static const std::string no_net_hint("no-internet");
219 if (impl->meta.contains_hint(no_net_hint))232 if (impl->meta.contains_hint(no_net_hint))
@@ -223,8 +236,7 @@
223 {236 {
224 return;237 return;
225 }238 }
226
227 }239 }
228240
229 add_available_apps(searchReply, locallyInstalledApps, categoryTemplate);241 add_available_apps(searchReply, get_installed_packages(), categoryTemplate);
230}242}
231243
=== modified file 'scope/clickstore/store-query.h'
--- scope/clickstore/store-query.h 2014-05-27 06:57:52 +0000
+++ scope/clickstore/store-query.h 2014-06-16 14:09:25 +0000
@@ -36,8 +36,7 @@
36namespace scopes = unity::scopes;36namespace scopes = unity::scopes;
3737
38#include <QSharedPointer>38#include <QSharedPointer>
39#include <set>39#include <click/interface.h>
40
4140
42namespace click41namespace click
43{42{
@@ -78,7 +77,10 @@
78 virtual void run(scopes::SearchReplyProxy const& reply) override;77 virtual void run(scopes::SearchReplyProxy const& reply) override;
7978
80protected:79protected:
81 virtual void add_available_apps(const scopes::SearchReplyProxy &searchReply, const std::set<std::string> &locallyInstalledApps, const std::string &category);80 virtual void add_available_apps(const scopes::SearchReplyProxy &searchReply,
81 const PackageSet &installedPackages, const std::string &category);
82 virtual click::Interface& clickInterfaceInstance();
83 virtual PackageSet get_installed_packages();
82 virtual bool push_result(const scopes::SearchReplyProxy &searchReply, scopes::CategorisedResult const& res);84 virtual bool push_result(const scopes::SearchReplyProxy &searchReply, scopes::CategorisedResult const& res);
83 virtual void finished(const scopes::SearchReplyProxy &searchReply);85 virtual void finished(const scopes::SearchReplyProxy &searchReply);
84 virtual scopes::Category::SCPtr register_category(scopes::SearchReplyProxy const& searchReply,86 virtual scopes::Category::SCPtr register_category(scopes::SearchReplyProxy const& searchReply,
8587
=== modified file 'scope/tests/test_query.cpp'
--- scope/tests/test_query.cpp 2014-06-12 21:05:25 +0000
+++ scope/tests/test_query.cpp 2014-06-16 14:09:25 +0000
@@ -47,6 +47,7 @@
47#include <unity/scopes/SearchReply.h>47#include <unity/scopes/SearchReply.h>
4848
49using namespace ::testing;49using namespace ::testing;
50using namespace click;
5051
51namespace52namespace
52{53{
@@ -98,18 +99,20 @@
9899
99 }100 }
100 void wrap_add_available_apps(const scopes::SearchReplyProxy &searchReply,101 void wrap_add_available_apps(const scopes::SearchReplyProxy &searchReply,
101 const std::set<std::string> &locallyInstalledApps,102 const PackageSet &installedPackages,
102 const std::string& categoryTemplate)103 const std::string& categoryTemplate)
103 {104 {
104 add_available_apps(searchReply, locallyInstalledApps, categoryTemplate);105 add_available_apps(searchReply, installedPackages, categoryTemplate);
105 }106 }
106 MOCK_METHOD2(push_result, bool(scopes::SearchReplyProxy const&, scopes::CategorisedResult const&));107 MOCK_METHOD2(push_result, bool(scopes::SearchReplyProxy const&, scopes::CategorisedResult const&));
108 MOCK_METHOD0(clickInterfaceInstance, click::Interface&());
107 MOCK_METHOD1(finished, void(scopes::SearchReplyProxy const&));109 MOCK_METHOD1(finished, void(scopes::SearchReplyProxy const&));
108 MOCK_METHOD5(register_category, scopes::Category::SCPtr(const scopes::SearchReplyProxy &searchReply,110 MOCK_METHOD5(register_category, scopes::Category::SCPtr(const scopes::SearchReplyProxy &searchReply,
109 const std::string &id,111 const std::string &id,
110 const std::string &title,112 const std::string &title,
111 const std::string &icon,113 const std::string &icon,
112 const scopes::CategoryRenderer &renderer_template));114 const scopes::CategoryRenderer &renderer_template));
115 using click::Query::get_installed_packages; // allow tests to access protected method
113};116};
114117
115class MockQueryRun : public MockQueryBase {118class MockQueryRun : public MockQueryBase {
@@ -121,11 +124,12 @@
121 }124 }
122 MOCK_METHOD3(add_available_apps,125 MOCK_METHOD3(add_available_apps,
123 void(scopes::SearchReplyProxy const&searchReply,126 void(scopes::SearchReplyProxy const&searchReply,
124 const std::set<std::string> &locallyInstalledApps,127 const PackageSet &locallyInstalledApps,
125 const std::string& categoryTemplate));128 const std::string& categoryTemplate));
126 MOCK_METHOD3(push_local_results, void(scopes::SearchReplyProxy const &replyProxy,129 MOCK_METHOD3(push_local_results, void(scopes::SearchReplyProxy const &replyProxy,
127 std::vector<click::Application> const &apps,130 std::vector<click::Application> const &apps,
128 std::string& categoryTemplate));131 std::string& categoryTemplate));
132 MOCK_METHOD0(get_installed_packages, PackageSet());
129};133};
130134
131class FakeCategory : public scopes::Category135class FakeCategory : public scopes::Category
@@ -144,7 +148,7 @@
144{148{
145 MockIndex mock_index;149 MockIndex mock_index;
146 scopes::SearchMetadata metadata("en_EN", "phone");150 scopes::SearchMetadata metadata("en_EN", "phone");
147 std::set<std::string> no_installed_packages;151 PackageSet no_installed_packages;
148 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");152 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
149 MockQuery q(query, mock_index, metadata);153 MockQuery q(query, mock_index, metadata);
150 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _)).Times(1);154 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _)).Times(1);
@@ -163,7 +167,7 @@
163 };167 };
164 MockIndex mock_index(packages);168 MockIndex mock_index(packages);
165 scopes::SearchMetadata metadata("en_EN", "phone");169 scopes::SearchMetadata metadata("en_EN", "phone");
166 std::set<std::string> no_installed_packages;170 PackageSet no_installed_packages;
167 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");171 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
168 MockQuery q(query, mock_index, metadata);172 MockQuery q(query, mock_index, metadata);
169 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));173 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
@@ -185,7 +189,7 @@
185 };189 };
186 MockIndex mock_index(packages);190 MockIndex mock_index(packages);
187 scopes::SearchMetadata metadata("en_EN", "phone");191 scopes::SearchMetadata metadata("en_EN", "phone");
188 std::set<std::string> no_installed_packages;192 PackageSet no_installed_packages;
189 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");193 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
190 MockQuery q(query, mock_index, metadata);194 MockQuery q(query, mock_index, metadata);
191 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));195 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
@@ -206,37 +210,94 @@
206 };210 };
207 MockIndex mock_index(packages);211 MockIndex mock_index(packages);
208 scopes::SearchMetadata metadata("en_EN", "phone");212 scopes::SearchMetadata metadata("en_EN", "phone");
213 PackageSet no_installed_packages;
209 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");214 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
210 MockQueryRun q(query, mock_index, metadata);215 MockQueryRun q(query, mock_index, metadata);
211 auto reply = scopes::SearchReplyProxy();216 auto reply = scopes::SearchReplyProxy();
212 EXPECT_CALL(q, add_available_apps(reply, _, _));217 EXPECT_CALL(q, get_installed_packages()).WillOnce(Return(no_installed_packages));
218 EXPECT_CALL(q, add_available_apps(reply, no_installed_packages, _));
213219
214 q.run(reply);220 q.run(reply);
215}221}
216222
217MATCHER_P(HasPackageName, n, "") { return arg[click::Query::ResultKeys::NAME].get_string() == n; }223MATCHER_P(HasPackageName, n, "") { return arg[click::Query::ResultKeys::NAME].get_string() == n; }
218224MATCHER_P(IsInstalled, b, "") { return arg[click::Query::ResultKeys::INSTALLED].get_bool() == b; }
219TEST(QueryTest, testDuplicatesFilteredOnPackageName)225
220{226TEST(QueryTest, testDuplicatesNotFilteredAnymore)
221 click::Packages packages {227{
222 {"org.example.app1", "app title1", 0.0, "icon", "uri"},228 click::Packages packages {
223 {"org.example.app2", "app title2", 0.0, "icon", "uri"}229 {"org.example.app1", "app title1", 0.0, "icon", "uri"},
224 };230 {"org.example.app2", "app title2", 0.0, "icon", "uri"}
225 MockIndex mock_index(packages);231 };
226 scopes::SearchMetadata metadata("en_EN", "phone");232 MockIndex mock_index(packages);
227 std::set<std::string> one_installed_package {233 scopes::SearchMetadata metadata("en_EN", "phone");
228 "org.example.app2"234 PackageSet one_installed_package {
229 };235 {"org.example.app2", "0.2"}
230 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");236 };
231 MockQuery q(query, mock_index, metadata);237 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
232 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));238 MockQuery q(query, mock_index, metadata);
233239 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
234 scopes::CategoryRenderer renderer("{}");240
235 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);241 scopes::CategoryRenderer renderer("{}");
236 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));242 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
237243 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));
238 scopes::SearchReplyProxy reply;244
239 auto expected_name = packages.front().name;245 scopes::SearchReplyProxy reply;
240 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name)));246 auto expected_name1 = packages.front().name;
241 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);247 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name1)));
248 auto expected_name2 = packages.back().name;
249 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name2)));
250 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);
251}
252
253TEST(QueryTest, testInstalledPackagesFlaggedAsSuch)
254{
255 click::Packages packages {
256 {"org.example.app1", "app title1", 0.0, "icon", "uri"},
257 {"org.example.app2", "app title2", 0.0, "icon", "uri"}
258 };
259 MockIndex mock_index(packages);
260 scopes::SearchMetadata metadata("en_EN", "phone");
261 PackageSet one_installed_package {
262 {"org.example.app2", "0.2"}
263 };
264 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
265 MockQuery q(query, mock_index, metadata);
266 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
267
268 scopes::CategoryRenderer renderer("{}");
269 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
270 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));
271
272 scopes::SearchReplyProxy reply;
273 EXPECT_CALL(q, push_result(_, IsInstalled(true)));
274 EXPECT_CALL(q, push_result(_, IsInstalled(false)));
275 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);
276}
277
278class FakeInterface : public click::Interface
279{
280public:
281 MOCK_METHOD1(get_installed_packages, void(std::function<void(PackageSet, click::InterfaceError)> callback));
282};
283
284TEST(QueryTest, testGetInstalledPackages)
285{
286 click::Packages uninstalled_packages {
287 {"name", "title", 0.0, "icon", "uri"}
288 };
289 MockIndex mock_index(uninstalled_packages);
290 scopes::SearchMetadata metadata("en_EN", "phone");
291 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
292 MockQuery q(query, mock_index, metadata);
293 PackageSet installed_packages{{"package_1", "0.1"}};
294
295 FakeInterface fake_interface;
296 EXPECT_CALL(q, clickInterfaceInstance()).WillOnce(ReturnRef(fake_interface));
297 EXPECT_CALL(fake_interface, get_installed_packages(_)).WillOnce(Invoke(
298 [&](std::function<void(PackageSet, click::InterfaceError)> callback){
299 callback(installed_packages, click::InterfaceError::NoError);
300 }));
301
302 ASSERT_EQ(q.get_installed_packages(), installed_packages);
242}303}

Subscribers

People subscribed via source and target branches

to all changes: