Merge lp:~osomon/webbrowser-app/private-downloads into lp:webbrowser-app
- private-downloads
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1552 |
Proposed branch: | lp:~osomon/webbrowser-app/private-downloads |
Merge into: | lp:webbrowser-app |
Diff against target: |
1317 lines (+597/-297) 9 files modified
src/app/Downloader.qml (+3/-11) src/app/WebViewImpl.qml (+2/-2) src/app/webbrowser/Browser.qml (+9/-1) src/app/webbrowser/DownloadDelegate.qml (+151/-149) src/app/webbrowser/DownloadsPage.qml (+10/-2) src/app/webbrowser/downloads-model.cpp (+139/-80) src/app/webbrowser/downloads-model.h (+7/-9) src/app/webbrowser/webbrowser-app.qml (+14/-0) tests/unittests/downloads-model/tst_DownloadsModelTests.cpp (+262/-43) |
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/private-downloads |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Sheldon (community) | Approve | ||
system-apps-ci-bot | continuous-integration | Needs Fixing | |
Review via email: mp+306446@code.launchpad.net |
Commit message
Do not persist references to incognito downloads on disk,
and increase test coverage (to 97.5%) for DownloadsModel.
Description of the change
Do not persist references to incognito downloads on disk,
and increase test coverage (to 97.5%) for DownloadsModel.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
Michael Sheldon (michael-sheldon) wrote : | # |
Looks good to me :)
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:1537
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
- 1538. By Olivier Tilloy
-
Remove dead code.
- 1539. By Olivier Tilloy
-
Do not show downloads initiated from an incognito webview in the indicator, for increased privacy.
Michael Sheldon (michael-sheldon) wrote : | # |
Additional changes look good to me :)
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:1539
https:/
Executed test runs:
SUCCESS: https:/
UNSTABLE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
- 1540. By Olivier Tilloy
-
Merge the latest changes from trunk and resolve conflicts.
- 1541. By Olivier Tilloy
-
Prune incognito entries from the downloads model when the last incognito window is being closed.
- 1542. By Olivier Tilloy
-
Minor optimizations.
- 1543. By Olivier Tilloy
-
Display incognito downloads only in incognito windows.
- 1544. By Olivier Tilloy
-
Display an incognito icon for to easily differentiate incognito downloads.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:1544
https:/
Executed test runs:
SUCCESS: https:/
UNSTABLE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
- 1545. By Olivier Tilloy
-
Use a SlotsLayout for the DownloadDelegate.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:1545
https:/
Executed test runs:
SUCCESS: https:/
UNSTABLE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Michael Sheldon (michael-sheldon) wrote : | # |
Looks good :)
Preview Diff
1 | === modified file 'src/app/Downloader.qml' |
2 | --- src/app/Downloader.qml 2016-01-12 14:50:27 +0000 |
3 | +++ src/app/Downloader.qml 2016-10-06 07:46:34 +0000 |
4 | @@ -34,9 +34,7 @@ |
5 | |
6 | Component { |
7 | id: metadataComponent |
8 | - Metadata { |
9 | - showInIndicator: true |
10 | - } |
11 | + Metadata {} |
12 | } |
13 | |
14 | Component { |
15 | @@ -62,14 +60,8 @@ |
16 | singleDownload.download(url) |
17 | } |
18 | |
19 | - function downloadPicture(url, headers) { |
20 | - var metadata = metadataComponent.createObject(downloadItem) |
21 | - downloadItem.mimeType = "image/*" |
22 | - download(url, ContentType.Pictures, headers, metadata) |
23 | - } |
24 | - |
25 | - function downloadMimeType(url, mimeType, headers, filename) { |
26 | - var metadata = metadataComponent.createObject(downloadItem) |
27 | + function downloadMimeType(url, mimeType, headers, filename, incognito) { |
28 | + var metadata = metadataComponent.createObject(downloadItem, {"showInIndicator": !incognito}) |
29 | var contentType = MimeTypeMapper.mimeTypeToContentType(mimeType) |
30 | if (contentType == ContentType.Unknown && filename) { |
31 | // If we can't determine the content type from the mime-type |
32 | |
33 | === modified file 'src/app/WebViewImpl.qml' |
34 | --- src/app/WebViewImpl.qml 2015-12-15 12:37:34 +0000 |
35 | +++ src/app/WebViewImpl.qml 2016-10-06 07:46:34 +0000 |
36 | @@ -1,5 +1,5 @@ |
37 | /* |
38 | - * Copyright 2013-2015 Canonical Ltd. |
39 | + * Copyright 2013-2016 Canonical Ltd. |
40 | * |
41 | * This file is part of webbrowser-app. |
42 | * |
43 | @@ -76,7 +76,7 @@ |
44 | mimeType = MimeDatabase.filenameToMimeType(filename) |
45 | } |
46 | } |
47 | - downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename) |
48 | + downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename, incognito) |
49 | } else { |
50 | // Desktop form factor case |
51 | Qt.openUrlExternally(request.url) |
52 | |
53 | === modified file 'src/app/webbrowser/Browser.qml' |
54 | --- src/app/webbrowser/Browser.qml 2016-09-28 08:24:06 +0000 |
55 | +++ src/app/webbrowser/Browser.qml 2016-10-06 07:46:34 +0000 |
56 | @@ -968,11 +968,19 @@ |
57 | property: "downloadManager" |
58 | value: downloadHandlerLoader.item |
59 | } |
60 | + |
61 | + Binding { |
62 | + target: downloadsViewLoader.item |
63 | + property: "incognito" |
64 | + value: browser.incognito |
65 | + } |
66 | + |
67 | Binding { |
68 | target: downloadsViewLoader.item |
69 | property: "focus" |
70 | value: true |
71 | } |
72 | + |
73 | Connections { |
74 | target: downloadsViewLoader.item |
75 | onDone: downloadsViewLoader.active = false |
76 | @@ -1375,7 +1383,7 @@ |
77 | } |
78 | |
79 | function startDownload(downloadId, download, mimeType) { |
80 | - DownloadsModel.add(downloadId, download.url, mimeType) |
81 | + DownloadsModel.add(downloadId, download.url, mimeType, incognito) |
82 | download.start() |
83 | downloadsViewLoader.active = true |
84 | } |
85 | |
86 | === modified file 'src/app/webbrowser/DownloadDelegate.qml' |
87 | --- src/app/webbrowser/DownloadDelegate.qml 2016-05-23 02:52:50 +0000 |
88 | +++ src/app/webbrowser/DownloadDelegate.qml 2016-10-06 07:46:34 +0000 |
89 | @@ -25,7 +25,7 @@ |
90 | |
91 | property var downloadManager |
92 | |
93 | - property alias icon: mimeicon.name |
94 | + property string icon |
95 | property alias image: thumbimage.source |
96 | property alias title: title.text |
97 | property alias url: url.text |
98 | @@ -33,15 +33,16 @@ |
99 | property bool incomplete: false |
100 | property string downloadId |
101 | property var download |
102 | - property int progress: download ? download.progress : 0 |
103 | + readonly property int progress: download ? download.progress : 0 |
104 | property bool paused |
105 | + property alias incognito: incognitoIcon.visible |
106 | |
107 | divider.visible: false |
108 | |
109 | signal removed() |
110 | signal cancelled() |
111 | |
112 | - height: visible ? (incomplete ? (paused ? units.gu(13) : units.gu(10)) : units.gu(7)) : 0 |
113 | + height: visible ? layout.height : 0 |
114 | |
115 | QtObject { |
116 | id: internal |
117 | @@ -60,165 +61,167 @@ |
118 | Component.onCompleted: internal.connectToDownloadObject() |
119 | onDownloadManagerChanged: internal.connectToDownloadObject() |
120 | |
121 | - Item { |
122 | - |
123 | - anchors { |
124 | - verticalCenter: parent.verticalCenter |
125 | - left: parent.left |
126 | - leftMargin: units.gu(2) |
127 | - right: parent.right |
128 | - rightMargin: units.gu(2) |
129 | - } |
130 | + SlotsLayout { |
131 | + id: layout |
132 | |
133 | Item { |
134 | - id: iconContainer |
135 | + SlotsLayout.position: SlotsLayout.Leading |
136 | width: units.gu(3) |
137 | - height: width |
138 | - anchors.verticalCenter: parent.verticalCenter |
139 | - anchors.verticalCenterOffset: downloadDelegate.incomplete ? -units.gu(1) : 0 |
140 | + height: units.gu(3) |
141 | |
142 | Image { |
143 | id: thumbimage |
144 | asynchronous: true |
145 | - width: parent.width |
146 | - height: parent.height |
147 | + anchors.fill: parent |
148 | fillMode: Image.PreserveAspectFit |
149 | - sourceSize.width: parent.width |
150 | - sourceSize.height: parent.height |
151 | - anchors.verticalCenter: parent.verticalCenter |
152 | + sourceSize.width: width |
153 | + sourceSize.height: height |
154 | } |
155 | |
156 | Image { |
157 | - id: mimeicon |
158 | asynchronous: true |
159 | anchors.fill: parent |
160 | anchors.margins: units.gu(0.2) |
161 | - source: "image://theme/%1".arg(name != "" ? name : "save") |
162 | + source: "image://theme/%1".arg(downloadDelegate.icon || "save") |
163 | visible: thumbimage.status !== Image.Ready |
164 | cache: true |
165 | - property string name |
166 | - } |
167 | - } |
168 | - |
169 | - Item { |
170 | - anchors.top: iconContainer.top |
171 | - anchors.left: iconContainer.right |
172 | - anchors.leftMargin: units.gu(2) |
173 | - anchors.right: parent.right |
174 | - |
175 | - Column { |
176 | - id: detailsColumn |
177 | - width: parent.width - cancelColumn.width |
178 | - height: parent.height |
179 | - |
180 | - Label { |
181 | - id: title |
182 | - fontSize: "x-small" |
183 | - color: "#5d5d5d" |
184 | - elide: Text.ElideRight |
185 | - width: parent.width |
186 | - } |
187 | - |
188 | - Label { |
189 | - id: url |
190 | - fontSize: "x-small" |
191 | - color: "#5d5d5d" |
192 | - elide: Text.ElideRight |
193 | - width: parent.width |
194 | - } |
195 | - |
196 | - Item { |
197 | - height: error.visible ? units.gu(1) : units.gu(2) |
198 | - width: parent.width |
199 | - visible: downloadDelegate.incomplete |
200 | - } |
201 | - |
202 | - Item { |
203 | - id: error |
204 | - visible: incomplete && download === undefined || errorMessage !== "" |
205 | - height: units.gu(3) |
206 | - width: parent.width |
207 | - |
208 | - Icon { |
209 | - id: errorIcon |
210 | - width: units.gu(2) |
211 | - height: width |
212 | - anchors.verticalCenter: parent.verticalCenter |
213 | - name: "dialog-warning-symbolic" |
214 | - color: theme.palette.normal.negative |
215 | - } |
216 | - |
217 | - Label { |
218 | - width: parent.width - errorIcon.width |
219 | - anchors.left: errorIcon.right |
220 | - anchors.leftMargin: units.gu(1) |
221 | - anchors.verticalCenter: errorIcon.verticalCenter |
222 | - fontSize: "x-small" |
223 | - color: theme.palette.normal.negative |
224 | - text: errorMessage !== "" ? errorMessage |
225 | - : (incomplete && download === undefined) ? i18n.tr("Download failed") |
226 | - : "" |
227 | - elide: Text.ElideRight |
228 | - } |
229 | - } |
230 | - |
231 | - IndeterminateProgressBar { |
232 | - id: progressBar |
233 | - width: parent.width |
234 | - height: units.gu(0.5) |
235 | - visible: downloadDelegate.incomplete && !error.visible |
236 | - progress: downloadDelegate.progress |
237 | - // Work around UDM bug #1450144 |
238 | - indeterminateProgress: downloadDelegate.progress < 0 || downloadDelegate.progress > 100 |
239 | - } |
240 | - } |
241 | - |
242 | - Column { |
243 | - id: cancelColumn |
244 | - spacing: units.gu(1) |
245 | - anchors.top: detailsColumn.top |
246 | - anchors.left: detailsColumn.right |
247 | - anchors.leftMargin: units.gu(2) |
248 | - width: downloadDelegate.incomplete && !error.visible ? cancelButton.width + units.gu(2) : 0 |
249 | - |
250 | - Button { |
251 | - visible: downloadDelegate.incomplete && !error.visible |
252 | - id: cancelButton |
253 | - text: i18n.tr("Cancel") |
254 | - onClicked: { |
255 | - if (download) { |
256 | - download.cancel() |
257 | - cancelled() |
258 | - } |
259 | - } |
260 | - } |
261 | - |
262 | - Label { |
263 | - visible: !progressBar.indeterminateProgress && downloadDelegate.incomplete |
264 | - && !error.visible |
265 | - && !downloadDelegate.paused |
266 | - width: cancelButton.width |
267 | - horizontalAlignment: Text.AlignHCenter |
268 | - fontSize: "x-small" |
269 | - text: progressBar.progress + "%" |
270 | - } |
271 | - |
272 | - Button { |
273 | - visible: downloadDelegate.paused |
274 | - text: i18n.tr("Resume") |
275 | - width: cancelButton.width |
276 | - onClicked: { |
277 | - if (download) { |
278 | - download.resume() |
279 | - } |
280 | - } |
281 | - } |
282 | - } |
283 | - |
284 | - } |
285 | - } |
286 | - |
287 | - leadingActions: error.visible || !downloadDelegate.incomplete ? deleteActionList : null |
288 | + } |
289 | + } |
290 | + |
291 | + mainSlot: Column { |
292 | + Label { |
293 | + id: title |
294 | + fontSize: "x-small" |
295 | + color: "#5d5d5d" |
296 | + elide: Text.ElideRight |
297 | + anchors { |
298 | + left: parent.left |
299 | + right: parent.right |
300 | + } |
301 | + } |
302 | + |
303 | + Label { |
304 | + id: url |
305 | + fontSize: "x-small" |
306 | + color: "#5d5d5d" |
307 | + elide: Text.ElideRight |
308 | + anchors { |
309 | + left: parent.left |
310 | + right: parent.right |
311 | + } |
312 | + } |
313 | + |
314 | + Item { |
315 | + height: error.visible ? units.gu(1) : units.gu(2) |
316 | + anchors { |
317 | + left: parent.left |
318 | + right: parent.right |
319 | + } |
320 | + visible: incomplete |
321 | + } |
322 | + |
323 | + Item { |
324 | + id: error |
325 | + visible: (incomplete && (download === undefined)) || errorMessage |
326 | + height: units.gu(3) |
327 | + anchors { |
328 | + left: parent.left |
329 | + right: parent.right |
330 | + } |
331 | + |
332 | + Icon { |
333 | + id: errorIcon |
334 | + width: units.gu(2) |
335 | + height: units.gu(2) |
336 | + anchors.verticalCenter: parent.verticalCenter |
337 | + name: "dialog-warning-symbolic" |
338 | + color: theme.palette.normal.negative |
339 | + } |
340 | + |
341 | + Label { |
342 | + anchors { |
343 | + left: errorIcon.right |
344 | + leftMargin: units.gu(1) |
345 | + right: parent.right |
346 | + verticalCenter: parent.verticalCenter |
347 | + } |
348 | + fontSize: "x-small" |
349 | + color: theme.palette.normal.negative |
350 | + text: errorMessage || |
351 | + ((incomplete && download === undefined) ? i18n.tr("Download failed") : "") |
352 | + elide: Text.ElideRight |
353 | + } |
354 | + } |
355 | + |
356 | + IndeterminateProgressBar { |
357 | + id: progressBar |
358 | + anchors { |
359 | + left: parent.left |
360 | + right: parent.right |
361 | + } |
362 | + height: units.gu(0.5) |
363 | + visible: incomplete && !error.visible |
364 | + progress: downloadDelegate.progress |
365 | + // Work around UDM bug #1450144 |
366 | + indeterminateProgress: progress < 0 || progress > 100 |
367 | + } |
368 | + } |
369 | + |
370 | + Column { |
371 | + SlotsLayout.position: SlotsLayout.Trailing |
372 | + spacing: units.gu(1) |
373 | + width: (incomplete && !error.visible) ? cancelButton.width : 0 |
374 | + |
375 | + Button { |
376 | + id: cancelButton |
377 | + visible: incomplete && !error.visible |
378 | + text: i18n.tr("Cancel") |
379 | + onClicked: { |
380 | + if (download) { |
381 | + download.cancel() |
382 | + cancelled() |
383 | + } |
384 | + } |
385 | + } |
386 | + |
387 | + Label { |
388 | + visible: !progressBar.indeterminateProgress && incomplete |
389 | + && !error.visible && !paused |
390 | + width: cancelButton.width |
391 | + horizontalAlignment: Text.AlignHCenter |
392 | + fontSize: "x-small" |
393 | + // TRANSLATORS: %1 is the percentage of the download completed so far |
394 | + text: i18n.tr("%1%").arg(progressBar.progress) |
395 | + } |
396 | + |
397 | + Button { |
398 | + visible: paused |
399 | + text: i18n.tr("Resume") |
400 | + width: cancelButton.width |
401 | + onClicked: { |
402 | + if (download) { |
403 | + download.resume() |
404 | + } |
405 | + } |
406 | + } |
407 | + } |
408 | + } |
409 | + |
410 | + Icon { |
411 | + id: incognitoIcon |
412 | + anchors { |
413 | + right: parent.right |
414 | + rightMargin: units.gu(2) |
415 | + bottom: parent.bottom |
416 | + bottomMargin: units.gu(1) |
417 | + } |
418 | + width: units.gu(2) |
419 | + height: units.gu(2) |
420 | + asynchronous: true |
421 | + name: "private-browsing" |
422 | + } |
423 | + |
424 | + leadingActions: error.visible || !incomplete ? deleteActionList : null |
425 | |
426 | ListItemActions { |
427 | id: deleteActionList |
428 | @@ -226,9 +229,8 @@ |
429 | Action { |
430 | objectName: "leadingAction.delete" |
431 | iconName: "delete" |
432 | - enabled: error.visible || !downloadDelegate.incomplete |
433 | - onTriggered: error.visible ? downloadDelegate.cancelled() |
434 | - : downloadDelegate.removed() |
435 | + enabled: error.visible || !incomplete |
436 | + onTriggered: error.visible ? cancelled() : removed() |
437 | } |
438 | ] |
439 | } |
440 | |
441 | === modified file 'src/app/webbrowser/DownloadsPage.qml' |
442 | --- src/app/webbrowser/DownloadsPage.qml 2016-06-06 09:09:47 +0000 |
443 | +++ src/app/webbrowser/DownloadsPage.qml 2016-10-06 07:46:34 +0000 |
444 | @@ -39,6 +39,7 @@ |
445 | property bool pickingMode |
446 | property bool multiSelect |
447 | property alias mimetypeFilter: downloadModelFilter.pattern |
448 | + property bool incognito: false |
449 | |
450 | signal done() |
451 | |
452 | @@ -154,8 +155,14 @@ |
453 | focus: !exportPeerPicker.focus |
454 | |
455 | model: SortFilterModel { |
456 | - model: DownloadsModel |
457 | - filter { |
458 | + model: SortFilterModel { |
459 | + model: DownloadsModel |
460 | + filter { |
461 | + property: "incognito" |
462 | + pattern: RegExp(downloadsItem.incognito ? "" : "^false$") |
463 | + } |
464 | + } |
465 | + filter { |
466 | id: downloadModelFilter |
467 | property: "mimetype" |
468 | } |
469 | @@ -197,6 +204,7 @@ |
470 | visible: !(selectMode && incomplete) |
471 | errorMessage: model.error |
472 | paused: model.paused |
473 | + incognito: model.incognito |
474 | |
475 | onClicked: { |
476 | if (model.complete && !selectMode) { |
477 | |
478 | === modified file 'src/app/webbrowser/downloads-model.cpp' |
479 | --- src/app/webbrowser/downloads-model.cpp 2016-07-06 09:31:57 +0000 |
480 | +++ src/app/webbrowser/downloads-model.cpp 2016-10-06 07:46:34 +0000 |
481 | @@ -104,6 +104,7 @@ |
482 | int count = 0; // size() isn't supported on the sqlite backend |
483 | while (populateQuery.next()) { |
484 | DownloadEntry entry; |
485 | + entry.incognito = false; |
486 | entry.downloadId = populateQuery.value(0).toString(); |
487 | entry.url = populateQuery.value(1).toUrl(); |
488 | entry.path = populateQuery.value(2).toString(); |
489 | @@ -147,6 +148,7 @@ |
490 | roles[Paused] = "paused"; |
491 | roles[Error] = "error"; |
492 | roles[Created] = "created"; |
493 | + roles[Incognito] = "incognito"; |
494 | } |
495 | return roles; |
496 | } |
497 | @@ -182,6 +184,8 @@ |
498 | return entry.error; |
499 | case Created: |
500 | return entry.created; |
501 | + case Incognito: |
502 | + return entry.incognito; |
503 | default: |
504 | return QVariant(); |
505 | } |
506 | @@ -218,7 +222,7 @@ |
507 | Add a download to the database. This should happen as soon as the download |
508 | is started. |
509 | */ |
510 | -void DownloadsModel::add(const QString& downloadId, const QUrl& url, const QString& mimetype) |
511 | +void DownloadsModel::add(const QString& downloadId, const QUrl& url, const QString& mimetype, bool incognito) |
512 | { |
513 | beginInsertRows(QModelIndex(), 0, 0); |
514 | DownloadEntry entry; |
515 | @@ -227,83 +231,111 @@ |
516 | entry.paused = false; |
517 | entry.url = url; |
518 | entry.mimetype = mimetype; |
519 | + entry.incognito = incognito; |
520 | m_orderedEntries.prepend(entry); |
521 | m_numRows++; |
522 | - m_fetchedCount++; |
523 | endInsertRows(); |
524 | - Q_EMIT added(downloadId, url, mimetype); |
525 | - insertNewEntryInDatabase(entry); |
526 | Q_EMIT rowCountChanged(); |
527 | -} |
528 | - |
529 | -void DownloadsModel::setPath(const QString& downloadId, const QString& path) |
530 | -{ |
531 | - QSqlQuery query(m_database); |
532 | - |
533 | - // Override reported mimetype from server with detected mimetype from file once downloaded |
534 | - QMimeDatabase mimeDatabase; |
535 | - QString mimetype = mimeDatabase.mimeTypeForFile(path).name(); |
536 | - |
537 | - static QString updateStatement = QLatin1String("UPDATE downloads SET mimetype = ?, " |
538 | - "path = ? WHERE downloadId = ?"); |
539 | - query.prepare(updateStatement); |
540 | - query.addBindValue(mimetype); |
541 | - query.addBindValue(path); |
542 | - query.addBindValue(downloadId); |
543 | - query.exec(); |
544 | - Q_EMIT pathChanged(downloadId, path); |
545 | + if (!incognito) { |
546 | + insertNewEntryInDatabase(entry); |
547 | + m_fetchedCount++; |
548 | + } |
549 | } |
550 | |
551 | void DownloadsModel::setComplete(const QString& downloadId, const bool complete) |
552 | { |
553 | - QSqlQuery query(m_database); |
554 | - static QString updateStatement = QLatin1String("UPDATE downloads SET complete = ? " |
555 | - "WHERE downloadId = ?"); |
556 | - query.prepare(updateStatement); |
557 | - query.addBindValue(complete); |
558 | - query.addBindValue(downloadId); |
559 | - query.exec(); |
560 | - Q_EMIT completeChanged(downloadId, complete); |
561 | - reload(); |
562 | + int index = getIndexForDownloadId(downloadId); |
563 | + if (index != -1) { |
564 | + DownloadEntry& entry = m_orderedEntries[index]; |
565 | + if (entry.complete == complete) { |
566 | + return; |
567 | + } |
568 | + entry.complete = complete; |
569 | + Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Complete); |
570 | + if (!entry.incognito) { |
571 | + QSqlQuery query(m_database); |
572 | + static QString updateStatement = QLatin1String("UPDATE downloads SET complete=? WHERE downloadId=?;"); |
573 | + query.prepare(updateStatement); |
574 | + query.addBindValue(complete); |
575 | + query.addBindValue(downloadId); |
576 | + query.exec(); |
577 | + } |
578 | + } |
579 | } |
580 | |
581 | void DownloadsModel::setError(const QString& downloadId, const QString& error) |
582 | { |
583 | - QSqlQuery query(m_database); |
584 | - static QString updateStatement = QLatin1String("UPDATE downloads SET error = ? " |
585 | - "WHERE downloadId = ?"); |
586 | - query.prepare(updateStatement); |
587 | - query.addBindValue(error); |
588 | - query.addBindValue(downloadId); |
589 | - query.exec(); |
590 | - Q_EMIT errorChanged(downloadId, error); |
591 | - reload(); |
592 | + int index = getIndexForDownloadId(downloadId); |
593 | + if (index != -1) { |
594 | + DownloadEntry& entry = m_orderedEntries[index]; |
595 | + if (entry.error == error) { |
596 | + return; |
597 | + } |
598 | + entry.error = error; |
599 | + Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Error); |
600 | + if (!entry.incognito) { |
601 | + QSqlQuery query(m_database); |
602 | + static QString updateStatement = QLatin1String("UPDATE downloads SET error=? WHERE downloadId=?;"); |
603 | + query.prepare(updateStatement); |
604 | + query.addBindValue(error); |
605 | + query.addBindValue(downloadId); |
606 | + query.exec(); |
607 | + } |
608 | + } |
609 | } |
610 | |
611 | void DownloadsModel::moveToDownloads(const QString& downloadId, const QString& path) |
612 | { |
613 | + int index = getIndexForDownloadId(downloadId); |
614 | + if (index == -1) { |
615 | + return; |
616 | + } |
617 | QFile file(path); |
618 | if (file.exists()) { |
619 | QFileInfo fi(path); |
620 | - QString suffix = fi.completeSuffix(); |
621 | - QString filename = fi.fileName(); |
622 | - QString filenameWithoutSuffix = filename.left(filename.size() - suffix.size()); |
623 | + DownloadEntry& entry = m_orderedEntries[index]; |
624 | + QVector<int> updatedRoles; |
625 | + |
626 | + // Override reported mimetype from server with detected mimetype from file once downloaded |
627 | + QMimeDatabase mimeDatabase; |
628 | + QString mimetype = mimeDatabase.mimeTypeForFile(fi).name(); |
629 | + if (mimetype != entry.mimetype) { |
630 | + entry.mimetype = mimetype; |
631 | + updatedRoles.append(Mimetype); |
632 | + } |
633 | + |
634 | + // Move file to XDG Downloads folder |
635 | QDir dir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); |
636 | if (!dir.exists()) { |
637 | QDir::root().mkpath(dir.absolutePath()); |
638 | } |
639 | - QString destination = dir.absoluteFilePath(filenameWithoutSuffix + suffix); |
640 | + QString baseName = fi.baseName(); |
641 | + QString suffix = fi.completeSuffix(); |
642 | + QString destination = dir.absoluteFilePath(QString("%1.%2").arg(baseName, suffix)); |
643 | // Avoid filename collision by automatically inserting an incremented |
644 | // number into the filename if the original name already exists. |
645 | int append = 1; |
646 | while (QFile::exists(destination)) { |
647 | - destination = dir.absoluteFilePath(QString("%1%2.%3").arg(filenameWithoutSuffix, QString::number(append++), suffix)); |
648 | + destination = dir.absoluteFilePath(QString("%1.%2.%3").arg(baseName, QString::number(append++), suffix)); |
649 | } |
650 | if (file.rename(destination)) { |
651 | - setPath(downloadId, destination); |
652 | + entry.path = destination; |
653 | + updatedRoles.append(Path); |
654 | } else { |
655 | qWarning() << "Failed moving file from" << path << "to" << destination; |
656 | } |
657 | + |
658 | + Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), updatedRoles); |
659 | + if (!entry.incognito && !updatedRoles.isEmpty()) { |
660 | + QSqlQuery query(m_database); |
661 | + static QString updateStatement = QLatin1String("UPDATE downloads SET mimetype = ?, " |
662 | + "path = ? WHERE downloadId = ?"); |
663 | + query.prepare(updateStatement); |
664 | + query.addBindValue(mimetype); |
665 | + query.addBindValue(destination); |
666 | + query.addBindValue(downloadId); |
667 | + query.exec(); |
668 | + } |
669 | } else { |
670 | qWarning() << "Download not found:" << path; |
671 | } |
672 | @@ -331,15 +363,17 @@ |
673 | int index = 0; |
674 | Q_FOREACH(DownloadEntry entry, m_orderedEntries) { |
675 | if (entry.path == path) { |
676 | + bool incognito = entry.incognito; |
677 | beginRemoveRows(QModelIndex(), index, index); |
678 | m_orderedEntries.removeAt(index); |
679 | endRemoveRows(); |
680 | - Q_EMIT deleted(path); |
681 | - removeExistingEntryFromDatabase(path); |
682 | - m_fetchedCount--; |
683 | m_numRows--; |
684 | Q_EMIT rowCountChanged(); |
685 | QFile::remove(path); |
686 | + if (!incognito) { |
687 | + removeExistingEntryFromDatabase(path); |
688 | + m_fetchedCount--; |
689 | + } |
690 | return; |
691 | } else { |
692 | index++; |
693 | @@ -352,45 +386,69 @@ |
694 | */ |
695 | void DownloadsModel::cancelDownload(const QString& downloadId) |
696 | { |
697 | - int index=0; |
698 | - Q_FOREACH(DownloadEntry entry, m_orderedEntries) { |
699 | - if (entry.downloadId == downloadId) { |
700 | - beginRemoveRows(QModelIndex(), index, index); |
701 | - m_orderedEntries.removeAt(index); |
702 | + int index = getIndexForDownloadId(downloadId); |
703 | + if (index != -1) { |
704 | + const DownloadEntry& entry = m_orderedEntries.at(index); |
705 | + bool incognito = entry.incognito; |
706 | + beginRemoveRows(QModelIndex(), index, index); |
707 | + m_orderedEntries.removeAt(index); |
708 | + endRemoveRows(); |
709 | + m_numRows--; |
710 | + Q_EMIT rowCountChanged(); |
711 | + if (!incognito) { |
712 | QSqlQuery query(m_database); |
713 | static QString deleteStatement = QLatin1String("DELETE FROM downloads WHERE downloadId=?;"); |
714 | query.prepare(deleteStatement); |
715 | query.addBindValue(downloadId); |
716 | query.exec(); |
717 | - endRemoveRows(); |
718 | m_fetchedCount--; |
719 | - m_numRows--; |
720 | - Q_EMIT rowCountChanged(); |
721 | + } |
722 | + } |
723 | +} |
724 | + |
725 | +void DownloadsModel::setPaused(const QString& downloadId, bool paused) |
726 | +{ |
727 | + int index = getIndexForDownloadId(downloadId); |
728 | + if (index != -1) { |
729 | + DownloadEntry& entry = m_orderedEntries[index]; |
730 | + if (entry.paused == paused) { |
731 | return; |
732 | - } else { |
733 | - index++; |
734 | + } |
735 | + entry.paused = paused; |
736 | + Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Paused); |
737 | + if (!entry.incognito) { |
738 | + QSqlQuery query(m_database); |
739 | + static QString pauseStatement = QLatin1String("UPDATE downloads SET paused=? WHERE downloadId=?;"); |
740 | + query.prepare(pauseStatement); |
741 | + query.addBindValue(paused); |
742 | + query.addBindValue(downloadId); |
743 | + query.exec(); |
744 | } |
745 | } |
746 | } |
747 | |
748 | void DownloadsModel::pauseDownload(const QString& downloadId) |
749 | { |
750 | - QSqlQuery query(m_database); |
751 | - static QString pauseStatement = QLatin1String("UPDATE downloads SET paused=1 WHERE downloadId=?;"); |
752 | - query.prepare(pauseStatement); |
753 | - query.addBindValue(downloadId); |
754 | - query.exec(); |
755 | - reload(); |
756 | + setPaused(downloadId, true); |
757 | } |
758 | |
759 | void DownloadsModel::resumeDownload(const QString& downloadId) |
760 | { |
761 | - QSqlQuery query(m_database); |
762 | - static QString resumeStatement = QLatin1String("UPDATE downloads SET paused=0 WHERE downloadId=?;"); |
763 | - query.prepare(resumeStatement); |
764 | - query.addBindValue(downloadId); |
765 | - query.exec(); |
766 | - reload(); |
767 | + setPaused(downloadId, false); |
768 | +} |
769 | + |
770 | +void DownloadsModel::pruneIncognitoDownloads() |
771 | +{ |
772 | + for (int i = m_orderedEntries.size() - 1; i >= 0; --i) { |
773 | + const DownloadEntry& entry = m_orderedEntries.at(i); |
774 | + if (entry.incognito) { |
775 | + beginRemoveRows(QModelIndex(), i, i); |
776 | + m_orderedEntries.removeAt(i); |
777 | + endRemoveRows(); |
778 | + m_numRows--; |
779 | + Q_EMIT rowCountChanged(); |
780 | + } |
781 | + } |
782 | } |
783 | |
784 | void DownloadsModel::removeExistingEntryFromDatabase(const QString& path) |
785 | @@ -409,14 +467,15 @@ |
786 | return m_canFetchMore; |
787 | } |
788 | |
789 | -void DownloadsModel::reload() |
790 | +int DownloadsModel::getIndexForDownloadId(const QString& downloadId) const |
791 | { |
792 | - beginResetModel(); |
793 | - m_orderedEntries.clear(); |
794 | - m_canFetchMore = true; |
795 | - m_fetchedCount = 0; |
796 | - m_numRows = 0; |
797 | - endResetModel(); |
798 | - fetchMore(); |
799 | - Q_EMIT rowCountChanged(); |
800 | + int index = 0; |
801 | + Q_FOREACH(const DownloadEntry& entry, m_orderedEntries) { |
802 | + if (entry.downloadId == downloadId) { |
803 | + return index; |
804 | + } else { |
805 | + ++index; |
806 | + } |
807 | + } |
808 | + return -1; |
809 | } |
810 | |
811 | === modified file 'src/app/webbrowser/downloads-model.h' |
812 | --- src/app/webbrowser/downloads-model.h 2016-01-12 10:37:15 +0000 |
813 | +++ src/app/webbrowser/downloads-model.h 2016-10-06 07:46:34 +0000 |
814 | @@ -49,7 +49,8 @@ |
815 | Complete, |
816 | Paused, |
817 | Error, |
818 | - Created |
819 | + Created, |
820 | + Incognito |
821 | }; |
822 | |
823 | // reimplemented from QAbstractListModel |
824 | @@ -63,23 +64,18 @@ |
825 | void setDatabasePath(const QString& path); |
826 | |
827 | Q_INVOKABLE bool contains(const QString& downloadId) const; |
828 | - Q_INVOKABLE void add(const QString& downloadId, const QUrl& url, const QString& mimetype); |
829 | + Q_INVOKABLE void add(const QString& downloadId, const QUrl& url, const QString& mimetype, bool incognito); |
830 | Q_INVOKABLE void moveToDownloads(const QString& downloadId, const QString& path); |
831 | - Q_INVOKABLE void setPath(const QString& downloadId, const QString& path); |
832 | Q_INVOKABLE void setComplete(const QString& downloadId, const bool complete); |
833 | Q_INVOKABLE void setError(const QString& downloadId, const QString& error); |
834 | Q_INVOKABLE void deleteDownload(const QString& path); |
835 | Q_INVOKABLE void cancelDownload(const QString& downloadId); |
836 | Q_INVOKABLE void pauseDownload(const QString& downloadId); |
837 | Q_INVOKABLE void resumeDownload(const QString& downloadId); |
838 | + Q_INVOKABLE void pruneIncognitoDownloads(); |
839 | |
840 | Q_SIGNALS: |
841 | void databasePathChanged() const; |
842 | - void added(const QString& downloadId, const QUrl& url, const QString& mimetype) const; |
843 | - void pathChanged(const QString& downloadId, const QString& path) const; |
844 | - void completeChanged(const QString& downloadId, const bool complete) const; |
845 | - void errorChanged(const QString& downloadId, const QString& error) const; |
846 | - void deleted(const QString& path) const; |
847 | void rowCountChanged(); |
848 | |
849 | private: |
850 | @@ -98,6 +94,7 @@ |
851 | bool paused; |
852 | QString error; |
853 | QDateTime created; |
854 | + bool incognito; |
855 | }; |
856 | QList<DownloadEntry> m_orderedEntries; |
857 | |
858 | @@ -105,7 +102,8 @@ |
859 | void createOrAlterDatabaseSchema(); |
860 | void insertNewEntryInDatabase(const DownloadEntry& entry); |
861 | void removeExistingEntryFromDatabase(const QString& path); |
862 | - void reload(); |
863 | + void setPaused(const QString& downloadId, bool paused); |
864 | + int getIndexForDownloadId(const QString& downloadId) const; |
865 | }; |
866 | |
867 | #endif // __DOWNLOADS_MODEL_H__ |
868 | |
869 | === modified file 'src/app/webbrowser/webbrowser-app.qml' |
870 | --- src/app/webbrowser/webbrowser-app.qml 2016-09-20 19:55:22 +0000 |
871 | +++ src/app/webbrowser/webbrowser-app.qml 2016-10-06 07:46:34 +0000 |
872 | @@ -128,6 +128,20 @@ |
873 | session.clear() |
874 | } |
875 | } |
876 | + if (incognito && (allWindows.length > 1)) { |
877 | + // If the last incognito window is being closed, |
878 | + // prune incognito entries from the downloads model |
879 | + var incognitoWindows = 0 |
880 | + for (var w in allWindows) { |
881 | + var window = allWindows[w] |
882 | + if ((window !== this) && window.incognito) { |
883 | + ++incognitoWindows |
884 | + } |
885 | + } |
886 | + if (incognitoWindows == 0) { |
887 | + DownloadsModel.pruneIncognitoDownloads() |
888 | + } |
889 | + } |
890 | destroy() |
891 | } |
892 | |
893 | |
894 | === modified file 'tests/unittests/downloads-model/tst_DownloadsModelTests.cpp' |
895 | --- tests/unittests/downloads-model/tst_DownloadsModelTests.cpp 2016-01-12 10:37:15 +0000 |
896 | +++ tests/unittests/downloads-model/tst_DownloadsModelTests.cpp 2016-10-06 07:46:34 +0000 |
897 | @@ -17,6 +17,8 @@ |
898 | */ |
899 | |
900 | #include <QtCore/QDir> |
901 | +#include <QtCore/QFileInfo> |
902 | +#include <QtCore/QTemporaryDir> |
903 | #include <QtCore/QTemporaryFile> |
904 | #include <QtTest/QSignalSpy> |
905 | #include <QtTest/QtTest> |
906 | @@ -27,11 +29,16 @@ |
907 | Q_OBJECT |
908 | |
909 | private: |
910 | + QTemporaryDir homeDir; |
911 | DownloadsModel* model; |
912 | |
913 | private Q_SLOTS: |
914 | void init() |
915 | { |
916 | + // QStandardPaths::setTestModeEnabled() doesn't affect |
917 | + // QStandardPaths::DownloadLocation, so we must override $HOME to |
918 | + // ensure the test won't write data to the user's home directory. |
919 | + qputenv("HOME", homeDir.path().toUtf8()); |
920 | model = new DownloadsModel; |
921 | model->setDatabasePath(":memory:"); |
922 | } |
923 | @@ -39,6 +46,7 @@ |
924 | void cleanup() |
925 | { |
926 | delete model; |
927 | + qunsetenv("HOME"); |
928 | } |
929 | |
930 | void shouldBeInitiallyEmpty() |
931 | @@ -58,90 +66,177 @@ |
932 | QVERIFY(roleNames.contains("paused")); |
933 | QVERIFY(roleNames.contains("error")); |
934 | QVERIFY(roleNames.contains("created")); |
935 | + QVERIFY(roleNames.contains("incognito")); |
936 | } |
937 | |
938 | void shouldContainAddedEntries() |
939 | { |
940 | QVERIFY(!model->contains(QStringLiteral("testid"))); |
941 | - model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/html")); |
942 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/html"), false); |
943 | QVERIFY(model->contains(QStringLiteral("testid"))); |
944 | } |
945 | |
946 | void shouldAddNewEntries() |
947 | { |
948 | - QSignalSpy spy(model, SIGNAL(added(QString, QUrl, QString))); |
949 | + QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); |
950 | |
951 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
952 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
953 | QCOMPARE(model->rowCount(), 1); |
954 | QCOMPARE(spy.count(), 1); |
955 | QVariantList args = spy.takeFirst(); |
956 | - QCOMPARE(args.at(0).toString(), QString("testid")); |
957 | - QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/")); |
958 | - QCOMPARE(args.at(2).toString(), QString("text/plain")); |
959 | + QCOMPARE(args.at(0).toInt(), 0); |
960 | + QCOMPARE(args.at(1).toInt(), 0); |
961 | + QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid")); |
962 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/"))); |
963 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain")); |
964 | |
965 | - model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); |
966 | + model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
967 | QCOMPARE(model->rowCount(), 2); |
968 | QCOMPARE(spy.count(), 1); |
969 | args = spy.takeFirst(); |
970 | - QCOMPARE(args.at(0).toString(), QString("testid2")); |
971 | - QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/pdf")); |
972 | - QCOMPARE(args.at(2).toString(), QString("application/pdf")); |
973 | + QCOMPARE(args.at(0).toInt(), 0); |
974 | + QCOMPARE(args.at(1).toInt(), 0); |
975 | + QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid2")); |
976 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/pdf"))); |
977 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("application/pdf")); |
978 | } |
979 | |
980 | void shouldRemoveCancelled() |
981 | { |
982 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
983 | - model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); |
984 | - model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); |
985 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
986 | + model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
987 | + model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("https://example.org/secure.png")), QStringLiteral("image/png"), false); |
988 | QCOMPARE(model->rowCount(), 3); |
989 | |
990 | - model->cancelDownload("testid2"); |
991 | + model->cancelDownload(QStringLiteral("testid2")); |
992 | QCOMPARE(model->rowCount(), 2); |
993 | |
994 | - model->cancelDownload("invalid"); |
995 | + model->cancelDownload(QStringLiteral("invalid")); |
996 | QCOMPARE(model->rowCount(), 2); |
997 | } |
998 | |
999 | void shouldCompleteDownloads() |
1000 | { |
1001 | - QSignalSpy spy(model, SIGNAL(completeChanged(QString, bool))); |
1002 | - |
1003 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
1004 | - QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
1005 | - model->setComplete("testid", true); |
1006 | - QCOMPARE(spy.count(), 1); |
1007 | - QVariantList args = spy.takeFirst(); |
1008 | - QCOMPARE(args.at(0).toString(), QString("testid")); |
1009 | - QCOMPARE(args.at(1).toBool(), true); |
1010 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1011 | + QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
1012 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1013 | + |
1014 | + model->setComplete(QStringLiteral("testid"), true); |
1015 | + QVERIFY(model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
1016 | + QCOMPARE(spy.count(), 1); |
1017 | + QVariantList args = spy.takeFirst(); |
1018 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1019 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1020 | + QVector<int> roles = args.at(2).value<QVector<int> >(); |
1021 | + QCOMPARE(roles.size(), 1); |
1022 | + QCOMPARE(roles.at(0), (int) DownloadsModel::Complete); |
1023 | + |
1024 | + model->setComplete(QStringLiteral("testid"), true); |
1025 | + QVERIFY(model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
1026 | + QVERIFY(spy.isEmpty()); |
1027 | + |
1028 | + model->setComplete(QStringLiteral("testid"), false); |
1029 | + QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
1030 | + QCOMPARE(spy.count(), 1); |
1031 | + args = spy.takeFirst(); |
1032 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1033 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1034 | + roles = args.at(2).value<QVector<int> >(); |
1035 | + QCOMPARE(roles.size(), 1); |
1036 | + QCOMPARE(roles.at(0), (int) DownloadsModel::Complete); |
1037 | + } |
1038 | + |
1039 | + void shouldSetError() |
1040 | + { |
1041 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1042 | + QVERIFY(model->data(model->index(0, 0), DownloadsModel::Error).toString().isEmpty()); |
1043 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1044 | + |
1045 | + model->setError(QStringLiteral("testid"), QStringLiteral("foo")); |
1046 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("foo")); |
1047 | + QCOMPARE(spy.count(), 1); |
1048 | + QVariantList args = spy.takeFirst(); |
1049 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1050 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1051 | + QVector<int> roles = args.at(2).value<QVector<int> >(); |
1052 | + QCOMPARE(roles.size(), 1); |
1053 | + QCOMPARE(roles.at(0), (int) DownloadsModel::Error); |
1054 | + |
1055 | + model->setError(QStringLiteral("testid"), QStringLiteral("foo")); |
1056 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("foo")); |
1057 | + QVERIFY(spy.isEmpty()); |
1058 | + |
1059 | + model->setError(QStringLiteral("testid"), QString("bar")); |
1060 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("bar")); |
1061 | + QCOMPARE(spy.count(), 1); |
1062 | + args = spy.takeFirst(); |
1063 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1064 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1065 | + roles = args.at(2).value<QVector<int> >(); |
1066 | + QCOMPARE(roles.size(), 1); |
1067 | + QCOMPARE(roles.at(0), (int) DownloadsModel::Error); |
1068 | + } |
1069 | + |
1070 | + void shouldPauseAndResumeDownload() |
1071 | + { |
1072 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1073 | + QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); |
1074 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1075 | + |
1076 | + model->pauseDownload(QStringLiteral("testid")); |
1077 | + QVERIFY(model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); |
1078 | + QCOMPARE(spy.count(), 1); |
1079 | + QVariantList args = spy.takeFirst(); |
1080 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1081 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1082 | + QVector<int> roles = args.at(2).value<QVector<int> >(); |
1083 | + QCOMPARE(roles.size(), 1); |
1084 | + QCOMPARE(roles.at(0), (int) DownloadsModel::Paused); |
1085 | + |
1086 | + model->pauseDownload(QStringLiteral("testid")); |
1087 | + QVERIFY(model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); |
1088 | + QVERIFY(spy.isEmpty()); |
1089 | + |
1090 | + model->resumeDownload(QStringLiteral("testid")); |
1091 | + QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); |
1092 | + QCOMPARE(spy.count(), 1); |
1093 | + args = spy.takeFirst(); |
1094 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1095 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1096 | + roles = args.at(2).value<QVector<int> >(); |
1097 | + QCOMPARE(roles.size(), 1); |
1098 | + QCOMPARE(roles.at(0), (int) DownloadsModel::Paused); |
1099 | } |
1100 | |
1101 | void shouldKeepEntriesSortedChronologically() |
1102 | { |
1103 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
1104 | - model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); |
1105 | - model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); |
1106 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1107 | + model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
1108 | + model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("https://example.org/secure.png")), QStringLiteral("image/png"), false); |
1109 | |
1110 | - QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QString("testid3")); |
1111 | - QCOMPARE(model->data(model->index(1, 0), DownloadsModel::DownloadId).toString(), QString("testid2")); |
1112 | - QCOMPARE(model->data(model->index(2, 0), DownloadsModel::DownloadId).toString(), QString("testid")); |
1113 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid3")); |
1114 | + QCOMPARE(model->data(model->index(1, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid2")); |
1115 | + QCOMPARE(model->data(model->index(2, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid")); |
1116 | } |
1117 | |
1118 | void shouldReturnData() |
1119 | { |
1120 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
1121 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1122 | QVERIFY(!model->data(QModelIndex(), DownloadsModel::DownloadId).isValid()); |
1123 | QVERIFY(!model->data(model->index(-1, 0), DownloadsModel::DownloadId).isValid()); |
1124 | QVERIFY(!model->data(model->index(3, 0), DownloadsModel::DownloadId).isValid()); |
1125 | - QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QString("testid")); |
1126 | - QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Url).toUrl(), QUrl("http://example.org/")); |
1127 | - QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Mimetype).toString(), QString("text/plain")); |
1128 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid")); |
1129 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/"))); |
1130 | + QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain")); |
1131 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Created).toDateTime() <= QDateTime::currentDateTime()); |
1132 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
1133 | + QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Incognito).toBool()); |
1134 | + QVERIFY(!model->data(model->index(0, 0), -1).isValid()); |
1135 | } |
1136 | |
1137 | void shouldReturnDatabasePath() |
1138 | { |
1139 | - QCOMPARE(model->databasePath(), QString(":memory:")); |
1140 | + QCOMPARE(model->databasePath(), QStringLiteral(":memory:")); |
1141 | } |
1142 | |
1143 | void shouldNotifyWhenSettingDatabasePath() |
1144 | @@ -149,14 +244,14 @@ |
1145 | QSignalSpy spyPath(model, SIGNAL(databasePathChanged())); |
1146 | QSignalSpy spyReset(model, SIGNAL(modelReset())); |
1147 | |
1148 | - model->setDatabasePath(":memory:"); |
1149 | + model->setDatabasePath(QStringLiteral(":memory:")); |
1150 | QVERIFY(spyPath.isEmpty()); |
1151 | QVERIFY(spyReset.isEmpty()); |
1152 | |
1153 | - model->setDatabasePath(""); |
1154 | + model->setDatabasePath(QStringLiteral("")); |
1155 | QCOMPARE(spyPath.count(), 1); |
1156 | QCOMPARE(spyReset.count(), 1); |
1157 | - QCOMPARE(model->databasePath(), QString(":memory:")); |
1158 | + QCOMPARE(model->databasePath(), QStringLiteral(":memory:")); |
1159 | } |
1160 | |
1161 | void shouldSerializeOnDisk() |
1162 | @@ -167,8 +262,10 @@ |
1163 | delete model; |
1164 | model = new DownloadsModel; |
1165 | model->setDatabasePath(fileName); |
1166 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
1167 | - model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); |
1168 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1169 | + model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
1170 | + model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("http://example.org/incognito.pdf")), QStringLiteral("application/pdf"), true); |
1171 | + QCOMPARE(model->rowCount(), 3); |
1172 | delete model; |
1173 | model = new DownloadsModel; |
1174 | model->setDatabasePath(fileName); |
1175 | @@ -180,17 +277,139 @@ |
1176 | { |
1177 | QCOMPARE(model->property("count").toInt(), 0); |
1178 | QCOMPARE(model->rowCount(), 0); |
1179 | - model->add("testid", QUrl("http://example.org/"), "text/plain"); |
1180 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1181 | QCOMPARE(model->property("count").toInt(), 1); |
1182 | QCOMPARE(model->rowCount(), 1); |
1183 | - model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); |
1184 | + model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
1185 | QCOMPARE(model->property("count").toInt(), 2); |
1186 | QCOMPARE(model->rowCount(), 2); |
1187 | - model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); |
1188 | + model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("https://example.org/secure.png")), QStringLiteral("image/png"), false); |
1189 | QCOMPARE(model->property("count").toInt(), 3); |
1190 | QCOMPARE(model->rowCount(), 3); |
1191 | } |
1192 | |
1193 | + void shouldPruneIncognitoDownloads() |
1194 | + { |
1195 | + model->add(QStringLiteral("testid1"), QUrl(QStringLiteral("http://example.org/1")), QStringLiteral("text/plain"), false); |
1196 | + model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/2")), QStringLiteral("text/plain"), true); |
1197 | + model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("http://example.org/3")), QStringLiteral("text/plain"), false); |
1198 | + model->add(QStringLiteral("testid4"), QUrl(QStringLiteral("http://example.org/4")), QStringLiteral("text/plain"), true); |
1199 | + QCOMPARE(model->rowCount(), 4); |
1200 | + QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); |
1201 | + QSignalSpy spyRowCountChanged(model, SIGNAL(rowCountChanged())); |
1202 | + model->pruneIncognitoDownloads(); |
1203 | + QCOMPARE(model->rowCount(), 2); |
1204 | + QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid3")); |
1205 | + QCOMPARE(model->data(model->index(1), DownloadsModel::DownloadId).toString(), QStringLiteral("testid1")); |
1206 | + QCOMPARE(spyRowsRemoved.count(), 2); |
1207 | + QCOMPARE(spyRowCountChanged.count(), 2); |
1208 | + } |
1209 | + |
1210 | + void shouldFailToMoveInvalidDownload() |
1211 | + { |
1212 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1213 | + QTemporaryFile tempFile; |
1214 | + tempFile.open(); |
1215 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1216 | + model->moveToDownloads(QStringLiteral("foobar"), tempFile.fileName()); |
1217 | + QVERIFY(spy.isEmpty()); |
1218 | + } |
1219 | + |
1220 | + void shouldFailToMoveNonExistentFile() |
1221 | + { |
1222 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1223 | + QTemporaryFile tempFile; |
1224 | + tempFile.open(); |
1225 | + QString fileName = tempFile.fileName(); |
1226 | + tempFile.remove(); |
1227 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1228 | + QTest::ignoreMessage(QtWarningMsg, QString("Download not found: \"%1\"").arg(fileName).toUtf8().constData()); |
1229 | + model->moveToDownloads(QStringLiteral("testid"), fileName); |
1230 | + QVERIFY(spy.isEmpty()); |
1231 | + } |
1232 | + |
1233 | + void shouldMoveFile() |
1234 | + { |
1235 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("application/pdf"), false); |
1236 | + QTemporaryFile tempFile(QStringLiteral("XXXXXX.txt")); |
1237 | + tempFile.open(); |
1238 | + tempFile.write(QByteArray("foo bar baz")); |
1239 | + tempFile.close(); |
1240 | + QString filePath = tempFile.fileName(); |
1241 | + QString fileName = QFileInfo(filePath).fileName(); |
1242 | + QVERIFY(QFile::exists(filePath)); |
1243 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1244 | + model->moveToDownloads(QStringLiteral("testid"), filePath); |
1245 | + QCOMPARE(spy.count(), 1); |
1246 | + QVariantList args = spy.takeFirst(); |
1247 | + QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1248 | + QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1249 | + QVector<int> roles = args.at(2).value<QVector<int> >(); |
1250 | + QCOMPARE(roles.size(), 2); |
1251 | + QVERIFY(roles.contains(DownloadsModel::Mimetype)); |
1252 | + QVERIFY(roles.contains(DownloadsModel::Path)); |
1253 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain")); |
1254 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Path).toString(), QString("%1/Downloads/%2").arg(homeDir.path(), fileName)); |
1255 | + QVERIFY(!QFile::exists(filePath)); |
1256 | + } |
1257 | + |
1258 | + void shouldRenameFileToAvoidFilenameCollision() |
1259 | + { |
1260 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1261 | + QTemporaryFile tempFile(QStringLiteral("XXXXXX.txt")); |
1262 | + tempFile.open(); |
1263 | + tempFile.write(QByteArray("foo")); |
1264 | + tempFile.close(); |
1265 | + QString filePath = tempFile.fileName(); |
1266 | + QString fileName = QFileInfo(filePath).fileName(); |
1267 | + QVERIFY(QFile::exists(filePath)); |
1268 | + QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1269 | + QString path = QString("%1/Downloads/%2").arg(homeDir.path(), fileName); |
1270 | + QFile file(path); |
1271 | + QVERIFY(file.open(QIODevice::WriteOnly)); |
1272 | + QVERIFY(file.write("bar") != -1); |
1273 | + file.close(); |
1274 | + model->moveToDownloads(QStringLiteral("testid"), filePath); |
1275 | + QString otherPath = QString("%1/Downloads/%2").arg(homeDir.path(), fileName.replace(QStringLiteral("."), QStringLiteral(".1."))); |
1276 | + QCOMPARE(model->data(model->index(0), DownloadsModel::Path).toString(), otherPath); |
1277 | + QVERIFY(!QFile::exists(filePath)); |
1278 | + QVERIFY(QFile::exists(path)); |
1279 | + QVERIFY(QFile::exists(otherPath)); |
1280 | + QVERIFY(file.open(QIODevice::ReadOnly)); |
1281 | + QCOMPARE(file.readAll(), QByteArray("bar")); |
1282 | + file.close(); |
1283 | + QFile file2(otherPath); |
1284 | + QVERIFY(file2.open(QIODevice::ReadOnly)); |
1285 | + QCOMPARE(file2.readAll(), QByteArray("foo")); |
1286 | + file2.close(); |
1287 | + } |
1288 | + |
1289 | + void shouldDeleteDownload() |
1290 | + { |
1291 | + // Need a file saved on disk to allow deleting it |
1292 | + model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
1293 | + QTemporaryFile tempFile(QStringLiteral("XXXXXX.txt")); |
1294 | + tempFile.open(); |
1295 | + tempFile.write(QByteArray("foo bar baz")); |
1296 | + tempFile.close(); |
1297 | + QString filePath = tempFile.fileName(); |
1298 | + QString fileName = QFileInfo(filePath).fileName(); |
1299 | + model->moveToDownloads(QStringLiteral("testid"), filePath); |
1300 | + QString path = model->data(model->index(0), DownloadsModel::Path).toString(); |
1301 | + QVERIFY(QFile::exists(path)); |
1302 | + |
1303 | + QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); |
1304 | + QSignalSpy spyRowCount(model, SIGNAL(rowCountChanged())); |
1305 | + model->deleteDownload(path); |
1306 | + QCOMPARE(spyRowsRemoved.count(), 1); |
1307 | + QVariantList args = spyRowsRemoved.takeFirst(); |
1308 | + QVERIFY(!args.at(0).toModelIndex().isValid()); |
1309 | + QCOMPARE(args.at(1).toInt(), 0); |
1310 | + QCOMPARE(args.at(2).toInt(), 0); |
1311 | + QCOMPARE(spyRowCount.count(), 1); |
1312 | + QCOMPARE(model->rowCount(), 0); |
1313 | + QVERIFY(!QFile::exists(path)); |
1314 | + } |
1315 | }; |
1316 | |
1317 | QTEST_MAIN(DownloadsModelTests) |
FAILED: Continuous integration, rev:1537 /jenkins. canonical. com/system- apps/job/ lp-webbrowser- app-ci/ 654/ /jenkins. canonical. com/system- apps/job/ build/1595/ console /jenkins. canonical. com/system- apps/job/ build-0- fetch/1595 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= yakkety/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= yakkety/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= vivid+overlay/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= vivid+overlay/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 1443 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 1443/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= yakkety/ 1443/console
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/system- apps/job/ lp-webbrowser- app-ci/ 654/rebuild
https:/