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