Merge lp:~mikemc/unity-scope-click/startdownload-error-handling into lp:unity-scope-click

Proposed by Mike McCracken
Status: Merged
Approved by: Alejandro J. Cura
Approved revision: 153
Merged at revision: 162
Proposed branch: lp:~mikemc/unity-scope-click/startdownload-error-handling
Merge into: lp:unity-scope-click
Prerequisite: lp:~diegosarmentero/unity-scope-click/fix-progress
Diff against target: 413 lines (+147/-38)
7 files modified
scope/click/download-manager.cpp (+23/-8)
scope/click/download-manager.h (+5/-1)
scope/click/preview.cpp (+51/-11)
scope/click/preview.h (+15/-0)
scope/click/scope.cpp (+13/-2)
scope/tests/download_manager_tool/download_manager_tool.cpp (+9/-2)
scope/tests/test_download_manager.cpp (+31/-14)
To merge this branch: bzr merge lp:~mikemc/unity-scope-click/startdownload-error-handling
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Approve
Diego Sarmentero (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+207356@code.launchpad.net

Commit message

- Surface errors in the download and install process via ErrorPreview

Description of the change

- Surface errors in the download and install process via ErrorPreview

Shows user error messages when
- creds are not found
- any error is returned from download manager, including network errors or failed install

Other changes:
- fix tests to account for separate error signals for creds vs. downloading/installing.
- fix a problem in the test matcher for the pkcon command
- use constants instead of strings in some places
- cleanup whitespace

TO TEST:

The download manager tests are currently disabled because they're flaky on ARM.
To run all the tests, which should pass on x86, do:
% GTEST_ALSO_RUN_DISABLED_TESTS=1 U1_DEBUG=1 make test

TO TEST IRL:
using unity-scope-tool or a device, try installing an app with:

- no credentials - you should get a specific error message for this
- no network - you should get a network error or a download/install error, depending on when you cut the net.
- install error - not sure how to trigger this on the device. maybe you could mess with permissions of the click directory temporarily. on desktop, it's easy, since pkcon doesn't work with clicks on desktop, according to the packagekit-plugin-click README:
       http://bazaar.launchpad.net/~click-hackers/click/trunk/view/head:/pk-plugin/README

In each case you should get a serviceable (untranslated, un-designed for wording) error message.

To post a comment you must log in.
153. By Mike McCracken

remove unused boost/optional header

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

+1

review: Approve
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'scope/click/download-manager.cpp'
2--- scope/click/download-manager.cpp 2014-02-18 11:26:15 +0000
3+++ scope/click/download-manager.cpp 2014-02-20 06:27:03 +0000
4@@ -215,7 +215,7 @@
5 void click::DownloadManager::handleCredentialsNotFound()
6 {
7 qDebug() << "No credentials were found.";
8- emit clickTokenFetchError(QString("No creds found"));
9+ emit credentialsNotFound();
10 }
11
12 void click::DownloadManager::handleNetworkFinished()
13@@ -295,7 +295,8 @@
14 // TODO, unimplemented. see https://bugs.launchpad.net/ubuntu-download-manager/+bug/1277814
15 }
16
17-void click::Downloader::startDownload(std::string url, std::string package_name, const std::function<void (std::string)>& callback)
18+void click::Downloader::startDownload(std::string url, std::string package_name,
19+ const std::function<void (std::pair<std::string, click::InstallError >)>& callback)
20 {
21 qt::core::world::enter_with_task([this, callback, url, package_name] (qt::core::world::Environment& /*env*/)
22 {
23@@ -304,16 +305,30 @@
24 QObject::connect(&dm, &click::DownloadManager::downloadStarted,
25 [callback](QString downloadId)
26 {
27- std::cout << "Download started: " << downloadId.toStdString() << std::endl;
28- callback(downloadId.toUtf8().data());
29- });
30+ qDebug() << "Download started, id: " << downloadId;
31+ auto ret = std::pair<std::string, click::InstallError>(downloadId.toUtf8().data(),
32+ click::InstallError::NoError);
33+ callback(ret);
34+ });
35+
36+ QObject::connect(&dm, &click::DownloadManager::credentialsNotFound,
37+ [callback]()
38+ {
39+ qDebug() << "Credentials not found:";
40+ auto ret = std::pair<std::string, click::InstallError>(std::string(),
41+ click::InstallError::CredentialsError);
42+ callback(ret);
43+ });
44+
45 QObject::connect(&dm, &click::DownloadManager::downloadError,
46- [](QString errorMessage)
47+ [callback](QString errorMessage)
48 {
49- // TODO: unclear how to handle errors
50 qDebug() << "Error creating download:" << errorMessage;
51+ auto ret = std::pair<std::string, click::InstallError>(errorMessage.toStdString(),
52+ click::InstallError::DownloadInstallError);
53+ callback(ret);
54 });
55- std::cout << "About to call start download" << std::endl;
56+
57 dm.startDownload(QString::fromStdString(url),
58 QString::fromStdString(package_name));
59 });
60
61=== modified file 'scope/click/download-manager.h'
62--- scope/click/download-manager.h 2014-02-18 11:26:15 +0000
63+++ scope/click/download-manager.h 2014-02-20 06:27:03 +0000
64@@ -67,6 +67,8 @@
65 virtual void fetchClickToken(const QString& downloadUrl);
66
67 signals:
68+
69+ void credentialsNotFound();
70 void downloadStarted(const QString& downloadObjectPath);
71 void downloadError(const QString& errorMessage);
72 void clickTokenFetched(const QString& clickToken);
73@@ -86,13 +88,15 @@
74 QScopedPointer<Private> impl;
75 };
76
77+enum class InstallError {NoError, CredentialsError, DownloadInstallError};
78
79 class Downloader
80 {
81 public:
82 Downloader(const QSharedPointer<click::network::AccessManager>& networkAccessManager);
83 void get_download_progress(std::string package_name, const std::function<void (std::string)>& callback);
84- void startDownload(std::string url, std::string package_name, const std::function<void (std::string)>& callback);
85+ void startDownload(std::string url, std::string package_name,
86+ const std::function<void (std::pair<std::string, InstallError>)>& callback);
87 private:
88 QSharedPointer<click::network::AccessManager> networkAccessManager;
89 };
90
91=== modified file 'scope/click/preview.cpp'
92--- scope/click/preview.cpp 2014-02-20 06:27:03 +0000
93+++ scope/click/preview.cpp 2014-02-20 06:27:03 +0000
94@@ -41,6 +41,8 @@
95
96 #include <QDebug>
97
98+#include <boost/optional.hpp>
99+
100 #include <sstream>
101
102 namespace
103@@ -125,12 +127,14 @@
104 reply->push(widgets);
105 }
106
107-void buildErrorPreview(scopes::PreviewReplyProxy const& reply)
108+void buildErrorPreview(scopes::PreviewReplyProxy const& reply,
109+ const std::string& error_message)
110 {
111 scopes::PreviewWidgetList widgets;
112
113 scopes::PreviewWidget header("hdr", "header");
114 header.add_attribute("title", scopes::Variant("Error"));
115+ header.add_attribute("subtitle", scopes::Variant(error_message));
116 widgets.push_back(header);
117
118 scopes::PreviewWidget buttons("buttons", "actions");
119@@ -151,6 +155,7 @@
120
121 scopes::PreviewWidget header("hdr", "header");
122 header.add_attribute("title", scopes::Variant("Login Error"));
123+ header.add_attribute("subtitle", scopes::Variant("Please log in to your Ubuntu One account."));
124 widgets.push_back(header);
125
126 scopes::PreviewWidget buttons("buttons", "actions");
127@@ -312,9 +317,6 @@
128 case Type::UNINSTALLED:
129 buildUninstalledPreview(reply, details);
130 break;
131- case Type::ERROR:
132- buildErrorPreview(reply);
133- break;
134 case Type::LOGIN:
135 buildLoginErrorPreview(reply);
136 break;
137@@ -328,8 +330,12 @@
138 buildPurchasingPreview(reply, details);
139 break;
140 case Type::DEFAULT:
141+ case Type::ERROR:
142+ // Don't showPreview() with errors. always use the error string.
143 default:
144- buildDefaultPreview(reply, details);
145+ qDebug() << "reached default preview type, returning internal error preview";
146+ buildErrorPreview(reply,
147+ std::string("Internal Error, please close and try again."));
148 break;
149 };
150 }
151@@ -339,6 +345,30 @@
152 this->type = type;
153 }
154
155+
156+// ErrorPreview
157+
158+ErrorPreview::ErrorPreview(const std::string& error_message,
159+ const QSharedPointer<click::Index>& index,
160+ const unity::scopes::Result &result)
161+ : Preview(result.uri(), index, result), error_message(error_message)
162+{
163+ qDebug() << "in ErrorPreview constructor, error_message is " << QString::fromStdString(error_message);
164+}
165+
166+ErrorPreview::~ErrorPreview()
167+{
168+
169+}
170+
171+void ErrorPreview::run(const unity::scopes::PreviewReplyProxy &reply)
172+{
173+ buildErrorPreview(reply, error_message);
174+}
175+
176+
177+// InstallPreview
178+
179 InstallPreview::InstallPreview(const std::string &download_url, const QSharedPointer<Index> &index,
180 const unity::scopes::Result &result,
181 const QSharedPointer<click::network::AccessManager> &nam)
182@@ -356,12 +386,22 @@
183 void InstallPreview::run(const unity::scopes::PreviewReplyProxy &reply)
184 {
185 qDebug() << "about to call startDownload in run()";
186- downloader->startDownload(download_url, result["name"].get_string(),[this, reply](std::string obj_path) {
187- qDebug() << "got object path: " << QString::fromStdString(obj_path);
188- index->get_details(result["name"].get_string(), [this, reply, obj_path](const click::PackageDetails& details)
189- {
190- buildInstallingPreview(reply, details, obj_path);
191- });
192+ downloader->startDownload(download_url, result["name"].get_string(),[this, reply](std::pair<std::string, click::InstallError> pair) {
193+
194+ auto error = pair.second;
195+
196+ if (error == InstallError::NoError) {
197+ auto obj_path = pair.first;
198+ qDebug() << "got object path: " << QString::fromStdString(obj_path);
199+ index->get_details(result["name"].get_string(), [this, reply, obj_path](const click::PackageDetails& details)
200+ {
201+ buildInstallingPreview(reply, details, obj_path);
202+ });
203+ } else if (error == InstallError::CredentialsError) {
204+ buildLoginErrorPreview(reply);
205+ } else {
206+ buildErrorPreview(reply, pair.first);
207+ }
208 });
209 qDebug() << "after startDownload in run()";
210 }
211
212=== modified file 'scope/click/preview.h'
213--- scope/click/preview.h 2014-02-18 12:44:49 +0000
214+++ scope/click/preview.h 2014-02-20 06:27:03 +0000
215@@ -108,6 +108,21 @@
216 const click::PackageDetails& details);
217 };
218
219+class ErrorPreview : public Preview
220+{
221+protected:
222+ std::string error_message;
223+public:
224+ ErrorPreview(const std::string& error_message,
225+ const QSharedPointer<click::Index>& index,
226+ const unity::scopes::Result& result);
227+
228+ virtual ~ErrorPreview();
229+
230+ void run(unity::scopes::PreviewReplyProxy const& reply) override;
231+
232+};
233+
234 class InstallPreview : public Preview
235 {
236 protected:
237
238=== modified file 'scope/click/scope.cpp'
239--- scope/click/scope.cpp 2014-02-20 06:27:03 +0000
240+++ scope/click/scope.cpp 2014-02-20 06:27:03 +0000
241@@ -101,10 +101,16 @@
242
243 if (metadata.scope_data().which() != scopes::Variant::Type::Null) {
244 auto metadict = metadata.scope_data().get_dict();
245- if (metadict.count("download_completed") != 0) {
246+
247+ if(metadict.count(click::Preview::Actions::DOWNLOAD_FAILED) != 0) {
248+ return scopes::QueryBase::UPtr{new ErrorPreview(std::string("Download or install failed. Please try again."),
249+ index, result)};
250+
251+ } else if (metadict.count(click::Preview::Actions::DOWNLOAD_COMPLETED) != 0) {
252 Preview* prev = new Preview(result.uri(), index, result);
253 prev->setPreview(click::Preview::Type::INSTALLED);
254 return scopes::QueryBase::UPtr{prev};
255+
256 } else if (metadict.count("action_id") != 0 &&
257 metadict.count("download_url") != 0) {
258 action_id = metadict["action_id"].get_string();
259@@ -132,8 +138,13 @@
260 activation->setHint("action_id", unity::scopes::Variant(action_id));
261 qDebug() << "returning ShowPreview";
262 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
263+
264+ } else if (action_id == click::Preview::Actions::DOWNLOAD_FAILED) {
265+ activation->setHint(click::Preview::Actions::DOWNLOAD_FAILED, unity::scopes::Variant(true));
266+ activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
267+
268 } else if (action_id == click::Preview::Actions::DOWNLOAD_COMPLETED) {
269- activation->setHint("download_completed", unity::scopes::Variant(true));
270+ activation->setHint(click::Preview::Actions::DOWNLOAD_COMPLETED, unity::scopes::Variant(true));
271 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
272 }
273 return scopes::ActivationBase::UPtr(activation);
274
275=== modified file 'scope/tests/download_manager_tool/download_manager_tool.cpp'
276--- scope/tests/download_manager_tool/download_manager_tool.cpp 2014-02-18 07:55:54 +0000
277+++ scope/tests/download_manager_tool/download_manager_tool.cpp 2014-02-20 06:27:03 +0000
278@@ -35,6 +35,8 @@
279
280 #include <iostream>
281
282+#include <boost/optional.hpp>
283+
284 #include <download_manager_tool.h>
285
286 DownloadManagerTool::DownloadManagerTool(QObject *parent):
287@@ -91,8 +93,13 @@
288
289 QObject::connect(&timer, &QTimer::timeout, [&]() {
290 downloader.startDownload(std::string(argv[1]), std::string(argv[2]),
291- [&a] (std::string download_id){
292- std::cout << " Success, got download ID:" << download_id << std::endl;
293+ [&a] (std::pair<std::string, click::InstallError> arg){
294+ auto error = arg.second;
295+ if (error == click::InstallError::NoError) {
296+ std::cout << " Success, got download ID:" << arg.first << std::endl;
297+ } else {
298+ std::cout << " Error:" << arg.first << std::endl;
299+ }
300 a.quit();
301 });
302
303
304=== modified file 'scope/tests/test_download_manager.cpp'
305--- scope/tests/test_download_manager.cpp 2014-02-17 21:32:33 +0000
306+++ scope/tests/test_download_manager.cpp 2014-02-20 06:27:03 +0000
307@@ -184,11 +184,11 @@
308 });
309 signalTimer.start(10);
310 }
311-
312 };
313
314 struct DownloadManagerMockClient
315 {
316+ MOCK_METHOD0(onCredentialsNotFoundEmitted, void());
317 MOCK_METHOD1(onClickTokenFetchedEmitted, void(QString clickToken));
318 MOCK_METHOD1(onClickTokenFetchErrorEmitted, void(QString errorMessage));
319 MOCK_METHOD1(onDownloadStartedEmitted, void(QString id));
320@@ -256,6 +256,12 @@
321
322 DownloadManagerMockClient mockDownloadManagerClient;
323
324+ QObject::connect(&dm, &click::DownloadManager::credentialsNotFound,
325+ [&mockDownloadManagerClient]()
326+ {
327+ mockDownloadManagerClient.onCredentialsNotFoundEmitted();
328+ });
329+
330 QObject::connect(&dm, &click::DownloadManager::clickTokenFetchError,
331 [&mockDownloadManagerClient](const QString& error)
332 {
333@@ -282,12 +288,23 @@
334
335 } else {
336
337- EXPECT_CALL(mockDownloadManagerClient, onClickTokenFetchErrorEmitted(_))
338- .Times(1)
339- .WillOnce(
340- InvokeWithoutArgs(
341- this,
342- &DISABLED_DownloadManagerCredsNetworkTest::Quit));
343+ if (p.credsFound) {
344+
345+ EXPECT_CALL(mockDownloadManagerClient, onClickTokenFetchErrorEmitted(_))
346+ .Times(1)
347+ .WillOnce(
348+ InvokeWithoutArgs(
349+ this,
350+ &DISABLED_DownloadManagerCredsNetworkTest::Quit));
351+ } else {
352+
353+ EXPECT_CALL(mockDownloadManagerClient, onCredentialsNotFoundEmitted())
354+ .Times(1)
355+ .WillOnce(
356+ InvokeWithoutArgs(
357+ this,
358+ &DISABLED_DownloadManagerCredsNetworkTest::Quit));
359+ }
360
361 EXPECT_CALL(mockDownloadManagerClient, onClickTokenFetchedEmitted(_)).Times(0);
362
363@@ -326,12 +343,12 @@
364 {
365 auto commandList = arg.getMetadata()["post-download-command"].toStringList();
366 return arg.getUrl() == TEST_URL
367- && arg.getHash() == ""
368+ && arg.getHash() == ""
369 && arg.getAlgorithm() == ""
370 && arg.getMetadata()["app_id"] == QVariant(TEST_APP_ID)
371 && commandList[0] == "/bin/sh"
372 && commandList[1] == "-c"
373- && commandList[3] == "$files"
374+ && commandList[3] == "$file"
375 && arg.getHeaders()["X-Click-Token"] == TEST_CLICK_TOKEN_VALUE;
376 }
377
378@@ -385,14 +402,14 @@
379 });
380 }
381
382- EXPECT_CALL(*mockSystemDownloadManager,
383+ EXPECT_CALL(*mockSystemDownloadManager,
384 createDownload(DownloadStructIsValid())).Times(1).WillOnce(InvokeWithoutArgs(downloadCreatedSignalFunc));
385- }
386+ }
387
388 EXPECT_CALL(*mockCredentialsService, getCredentials())
389 .Times(1).WillOnce(InvokeWithoutArgs(clickTokenSignalFunc));
390
391-
392+
393 DownloadManagerMockClient mockDownloadManagerClient;
394
395 QObject::connect(&dm, &click::DownloadManager::downloadError,
396@@ -418,7 +435,7 @@
397 &DISABLED_DownloadManagerStartDownloadTest::Quit));
398
399 EXPECT_CALL(mockDownloadManagerClient, onDownloadErrorEmitted(_)).Times(0);
400-
401+
402 // when https://bugs.launchpad.net/ubuntu-download-manager/+bug/1278789
403 // is fixed, we can add this assertion:
404 // EXPECT_CALL(successfulDownload, start()).Times(1);
405@@ -435,7 +452,7 @@
406 EXPECT_CALL(mockDownloadManagerClient, onDownloadStartedEmitted(_)).Times(0);
407
408 }
409-
410+
411 QTimer timer;
412 timer.setSingleShot(true);
413 QObject::connect(&timer, &QTimer::timeout, [&dm]() {

Subscribers

People subscribed via source and target branches

to all changes: