Merge lp:~osomon/webbrowser-app/private-downloads into lp:webbrowser-app

Proposed by Olivier Tilloy
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
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.

To post a comment you must log in.
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1537
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/654/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/1595/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1595
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1443/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1443
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1443/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1443/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/654/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Looks good to me :)

review: Approve
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1537
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/655/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/1596/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1596
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1444/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1444
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1444/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1444
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1444/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1444/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1444
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1444/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1444
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1444/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1444/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1444
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1444/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1444
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1444/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/655/rebuild

review: Needs Fixing (continuous-integration)
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.

Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Additional changes look good to me :)

review: Approve
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1539
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/656/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1599
    UNSTABLE: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/382
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1599
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1447/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1447
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1447/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/656/rebuild

review: Needs Fixing (continuous-integration)
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.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1544
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/678/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1745
    UNSTABLE: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/422
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1745
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1590/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1590
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1590/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/678/rebuild

review: Needs Fixing (continuous-integration)
1545. By Olivier Tilloy

Use a SlotsLayout for the DownloadDelegate.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1545
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/684/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1762
    UNSTABLE: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/429
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1762
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1607/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1607
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1607/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/684/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Sheldon (michael-sheldon) wrote :

Looks good :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '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)

Subscribers

People subscribed via source and target branches

to status/vote changes: