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
=== modified file 'src/app/Downloader.qml'
--- src/app/Downloader.qml 2016-01-12 14:50:27 +0000
+++ src/app/Downloader.qml 2016-10-06 07:46:34 +0000
@@ -34,9 +34,7 @@
3434
35 Component {35 Component {
36 id: metadataComponent36 id: metadataComponent
37 Metadata {37 Metadata {}
38 showInIndicator: true
39 }
40 }38 }
4139
42 Component {40 Component {
@@ -62,14 +60,8 @@
62 singleDownload.download(url)60 singleDownload.download(url)
63 }61 }
6462
65 function downloadPicture(url, headers) {63 function downloadMimeType(url, mimeType, headers, filename, incognito) {
66 var metadata = metadataComponent.createObject(downloadItem)64 var metadata = metadataComponent.createObject(downloadItem, {"showInIndicator": !incognito})
67 downloadItem.mimeType = "image/*"
68 download(url, ContentType.Pictures, headers, metadata)
69 }
70
71 function downloadMimeType(url, mimeType, headers, filename) {
72 var metadata = metadataComponent.createObject(downloadItem)
73 var contentType = MimeTypeMapper.mimeTypeToContentType(mimeType)65 var contentType = MimeTypeMapper.mimeTypeToContentType(mimeType)
74 if (contentType == ContentType.Unknown && filename) {66 if (contentType == ContentType.Unknown && filename) {
75 // If we can't determine the content type from the mime-type67 // If we can't determine the content type from the mime-type
7668
=== modified file 'src/app/WebViewImpl.qml'
--- src/app/WebViewImpl.qml 2015-12-15 12:37:34 +0000
+++ src/app/WebViewImpl.qml 2016-10-06 07:46:34 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2013-2015 Canonical Ltd.2 * Copyright 2013-2016 Canonical Ltd.
3 *3 *
4 * This file is part of webbrowser-app.4 * This file is part of webbrowser-app.
5 *5 *
@@ -76,7 +76,7 @@
76 mimeType = MimeDatabase.filenameToMimeType(filename)76 mimeType = MimeDatabase.filenameToMimeType(filename)
77 }77 }
78 }78 }
79 downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename)79 downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename, incognito)
80 } else {80 } else {
81 // Desktop form factor case81 // Desktop form factor case
82 Qt.openUrlExternally(request.url)82 Qt.openUrlExternally(request.url)
8383
=== modified file 'src/app/webbrowser/Browser.qml'
--- src/app/webbrowser/Browser.qml 2016-09-28 08:24:06 +0000
+++ src/app/webbrowser/Browser.qml 2016-10-06 07:46:34 +0000
@@ -968,11 +968,19 @@
968 property: "downloadManager"968 property: "downloadManager"
969 value: downloadHandlerLoader.item969 value: downloadHandlerLoader.item
970 }970 }
971
972 Binding {
973 target: downloadsViewLoader.item
974 property: "incognito"
975 value: browser.incognito
976 }
977
971 Binding {978 Binding {
972 target: downloadsViewLoader.item979 target: downloadsViewLoader.item
973 property: "focus"980 property: "focus"
974 value: true981 value: true
975 }982 }
983
976 Connections {984 Connections {
977 target: downloadsViewLoader.item985 target: downloadsViewLoader.item
978 onDone: downloadsViewLoader.active = false986 onDone: downloadsViewLoader.active = false
@@ -1375,7 +1383,7 @@
1375 }1383 }
13761384
1377 function startDownload(downloadId, download, mimeType) {1385 function startDownload(downloadId, download, mimeType) {
1378 DownloadsModel.add(downloadId, download.url, mimeType)1386 DownloadsModel.add(downloadId, download.url, mimeType, incognito)
1379 download.start()1387 download.start()
1380 downloadsViewLoader.active = true1388 downloadsViewLoader.active = true
1381 }1389 }
13821390
=== modified file 'src/app/webbrowser/DownloadDelegate.qml'
--- src/app/webbrowser/DownloadDelegate.qml 2016-05-23 02:52:50 +0000
+++ src/app/webbrowser/DownloadDelegate.qml 2016-10-06 07:46:34 +0000
@@ -25,7 +25,7 @@
2525
26 property var downloadManager26 property var downloadManager
2727
28 property alias icon: mimeicon.name28 property string icon
29 property alias image: thumbimage.source29 property alias image: thumbimage.source
30 property alias title: title.text30 property alias title: title.text
31 property alias url: url.text31 property alias url: url.text
@@ -33,15 +33,16 @@
33 property bool incomplete: false33 property bool incomplete: false
34 property string downloadId34 property string downloadId
35 property var download35 property var download
36 property int progress: download ? download.progress : 036 readonly property int progress: download ? download.progress : 0
37 property bool paused37 property bool paused
38 property alias incognito: incognitoIcon.visible
3839
39 divider.visible: false40 divider.visible: false
4041
41 signal removed()42 signal removed()
42 signal cancelled()43 signal cancelled()
4344
44 height: visible ? (incomplete ? (paused ? units.gu(13) : units.gu(10)) : units.gu(7)) : 045 height: visible ? layout.height : 0
4546
46 QtObject {47 QtObject {
47 id: internal48 id: internal
@@ -60,165 +61,167 @@
60 Component.onCompleted: internal.connectToDownloadObject()61 Component.onCompleted: internal.connectToDownloadObject()
61 onDownloadManagerChanged: internal.connectToDownloadObject()62 onDownloadManagerChanged: internal.connectToDownloadObject()
6263
63 Item {64 SlotsLayout {
64 65 id: layout
65 anchors {
66 verticalCenter: parent.verticalCenter
67 left: parent.left
68 leftMargin: units.gu(2)
69 right: parent.right
70 rightMargin: units.gu(2)
71 }
7266
73 Item {67 Item {
74 id: iconContainer68 SlotsLayout.position: SlotsLayout.Leading
75 width: units.gu(3)69 width: units.gu(3)
76 height: width70 height: units.gu(3)
77 anchors.verticalCenter: parent.verticalCenter
78 anchors.verticalCenterOffset: downloadDelegate.incomplete ? -units.gu(1) : 0
7971
80 Image {72 Image {
81 id: thumbimage73 id: thumbimage
82 asynchronous: true74 asynchronous: true
83 width: parent.width75 anchors.fill: parent
84 height: parent.height
85 fillMode: Image.PreserveAspectFit76 fillMode: Image.PreserveAspectFit
86 sourceSize.width: parent.width77 sourceSize.width: width
87 sourceSize.height: parent.height78 sourceSize.height: height
88 anchors.verticalCenter: parent.verticalCenter
89 }79 }
9080
91 Image {81 Image {
92 id: mimeicon
93 asynchronous: true82 asynchronous: true
94 anchors.fill: parent83 anchors.fill: parent
95 anchors.margins: units.gu(0.2)84 anchors.margins: units.gu(0.2)
96 source: "image://theme/%1".arg(name != "" ? name : "save")85 source: "image://theme/%1".arg(downloadDelegate.icon || "save")
97 visible: thumbimage.status !== Image.Ready86 visible: thumbimage.status !== Image.Ready
98 cache: true87 cache: true
99 property string name88 }
100 }89 }
101 }90
10291 mainSlot: Column {
103 Item {92 Label {
104 anchors.top: iconContainer.top93 id: title
105 anchors.left: iconContainer.right94 fontSize: "x-small"
106 anchors.leftMargin: units.gu(2)95 color: "#5d5d5d"
107 anchors.right: parent.right96 elide: Text.ElideRight
10897 anchors {
109 Column {98 left: parent.left
110 id: detailsColumn99 right: parent.right
111 width: parent.width - cancelColumn.width100 }
112 height: parent.height101 }
113102
114 Label {103 Label {
115 id: title104 id: url
116 fontSize: "x-small"105 fontSize: "x-small"
117 color: "#5d5d5d"106 color: "#5d5d5d"
118 elide: Text.ElideRight107 elide: Text.ElideRight
119 width: parent.width108 anchors {
120 }109 left: parent.left
121110 right: parent.right
122 Label {111 }
123 id: url112 }
124 fontSize: "x-small"113
125 color: "#5d5d5d"114 Item {
126 elide: Text.ElideRight115 height: error.visible ? units.gu(1) : units.gu(2)
127 width: parent.width116 anchors {
128 }117 left: parent.left
129118 right: parent.right
130 Item {119 }
131 height: error.visible ? units.gu(1) : units.gu(2)120 visible: incomplete
132 width: parent.width121 }
133 visible: downloadDelegate.incomplete122
134 }123 Item {
135124 id: error
136 Item {125 visible: (incomplete && (download === undefined)) || errorMessage
137 id: error126 height: units.gu(3)
138 visible: incomplete && download === undefined || errorMessage !== ""127 anchors {
139 height: units.gu(3)128 left: parent.left
140 width: parent.width129 right: parent.right
141130 }
142 Icon {131
143 id: errorIcon132 Icon {
144 width: units.gu(2)133 id: errorIcon
145 height: width134 width: units.gu(2)
146 anchors.verticalCenter: parent.verticalCenter135 height: units.gu(2)
147 name: "dialog-warning-symbolic"136 anchors.verticalCenter: parent.verticalCenter
148 color: theme.palette.normal.negative137 name: "dialog-warning-symbolic"
149 }138 color: theme.palette.normal.negative
150139 }
151 Label {140
152 width: parent.width - errorIcon.width141 Label {
153 anchors.left: errorIcon.right142 anchors {
154 anchors.leftMargin: units.gu(1)143 left: errorIcon.right
155 anchors.verticalCenter: errorIcon.verticalCenter144 leftMargin: units.gu(1)
156 fontSize: "x-small"145 right: parent.right
157 color: theme.palette.normal.negative146 verticalCenter: parent.verticalCenter
158 text: errorMessage !== "" ? errorMessage 147 }
159 : (incomplete && download === undefined) ? i18n.tr("Download failed") 148 fontSize: "x-small"
160 : ""149 color: theme.palette.normal.negative
161 elide: Text.ElideRight150 text: errorMessage ||
162 }151 ((incomplete && download === undefined) ? i18n.tr("Download failed") : "")
163 }152 elide: Text.ElideRight
164153 }
165 IndeterminateProgressBar {154 }
166 id: progressBar155
167 width: parent.width156 IndeterminateProgressBar {
168 height: units.gu(0.5)157 id: progressBar
169 visible: downloadDelegate.incomplete && !error.visible158 anchors {
170 progress: downloadDelegate.progress159 left: parent.left
171 // Work around UDM bug #1450144160 right: parent.right
172 indeterminateProgress: downloadDelegate.progress < 0 || downloadDelegate.progress > 100161 }
173 }162 height: units.gu(0.5)
174 }163 visible: incomplete && !error.visible
175164 progress: downloadDelegate.progress
176 Column {165 // Work around UDM bug #1450144
177 id: cancelColumn166 indeterminateProgress: progress < 0 || progress > 100
178 spacing: units.gu(1)167 }
179 anchors.top: detailsColumn.top168 }
180 anchors.left: detailsColumn.right169
181 anchors.leftMargin: units.gu(2)170 Column {
182 width: downloadDelegate.incomplete && !error.visible ? cancelButton.width + units.gu(2) : 0171 SlotsLayout.position: SlotsLayout.Trailing
183172 spacing: units.gu(1)
184 Button {173 width: (incomplete && !error.visible) ? cancelButton.width : 0
185 visible: downloadDelegate.incomplete && !error.visible174
186 id: cancelButton175 Button {
187 text: i18n.tr("Cancel")176 id: cancelButton
188 onClicked: {177 visible: incomplete && !error.visible
189 if (download) {178 text: i18n.tr("Cancel")
190 download.cancel()179 onClicked: {
191 cancelled()180 if (download) {
192 }181 download.cancel()
193 }182 cancelled()
194 }183 }
195184 }
196 Label {185 }
197 visible: !progressBar.indeterminateProgress && downloadDelegate.incomplete186
198 && !error.visible 187 Label {
199 && !downloadDelegate.paused188 visible: !progressBar.indeterminateProgress && incomplete
200 width: cancelButton.width189 && !error.visible && !paused
201 horizontalAlignment: Text.AlignHCenter190 width: cancelButton.width
202 fontSize: "x-small"191 horizontalAlignment: Text.AlignHCenter
203 text: progressBar.progress + "%"192 fontSize: "x-small"
204 }193 // TRANSLATORS: %1 is the percentage of the download completed so far
205194 text: i18n.tr("%1%").arg(progressBar.progress)
206 Button {195 }
207 visible: downloadDelegate.paused196
208 text: i18n.tr("Resume")197 Button {
209 width: cancelButton.width198 visible: paused
210 onClicked: {199 text: i18n.tr("Resume")
211 if (download) {200 width: cancelButton.width
212 download.resume()201 onClicked: {
213 }202 if (download) {
214 }203 download.resume()
215 }204 }
216 }205 }
217206 }
218 }207 }
219 }208 }
220209
221 leadingActions: error.visible || !downloadDelegate.incomplete ? deleteActionList : null210 Icon {
211 id: incognitoIcon
212 anchors {
213 right: parent.right
214 rightMargin: units.gu(2)
215 bottom: parent.bottom
216 bottomMargin: units.gu(1)
217 }
218 width: units.gu(2)
219 height: units.gu(2)
220 asynchronous: true
221 name: "private-browsing"
222 }
223
224 leadingActions: error.visible || !incomplete ? deleteActionList : null
222225
223 ListItemActions {226 ListItemActions {
224 id: deleteActionList227 id: deleteActionList
@@ -226,9 +229,8 @@
226 Action {229 Action {
227 objectName: "leadingAction.delete"230 objectName: "leadingAction.delete"
228 iconName: "delete"231 iconName: "delete"
229 enabled: error.visible || !downloadDelegate.incomplete232 enabled: error.visible || !incomplete
230 onTriggered: error.visible ? downloadDelegate.cancelled() 233 onTriggered: error.visible ? cancelled() : removed()
231 : downloadDelegate.removed()
232 }234 }
233 ]235 ]
234 }236 }
235237
=== modified file 'src/app/webbrowser/DownloadsPage.qml'
--- src/app/webbrowser/DownloadsPage.qml 2016-06-06 09:09:47 +0000
+++ src/app/webbrowser/DownloadsPage.qml 2016-10-06 07:46:34 +0000
@@ -39,6 +39,7 @@
39 property bool pickingMode39 property bool pickingMode
40 property bool multiSelect40 property bool multiSelect
41 property alias mimetypeFilter: downloadModelFilter.pattern41 property alias mimetypeFilter: downloadModelFilter.pattern
42 property bool incognito: false
4243
43 signal done()44 signal done()
4445
@@ -154,8 +155,14 @@
154 focus: !exportPeerPicker.focus155 focus: !exportPeerPicker.focus
155156
156 model: SortFilterModel {157 model: SortFilterModel {
157 model: DownloadsModel158 model: SortFilterModel {
158 filter { 159 model: DownloadsModel
160 filter {
161 property: "incognito"
162 pattern: RegExp(downloadsItem.incognito ? "" : "^false$")
163 }
164 }
165 filter {
159 id: downloadModelFilter166 id: downloadModelFilter
160 property: "mimetype"167 property: "mimetype"
161 }168 }
@@ -197,6 +204,7 @@
197 visible: !(selectMode && incomplete)204 visible: !(selectMode && incomplete)
198 errorMessage: model.error205 errorMessage: model.error
199 paused: model.paused206 paused: model.paused
207 incognito: model.incognito
200208
201 onClicked: {209 onClicked: {
202 if (model.complete && !selectMode) {210 if (model.complete && !selectMode) {
203211
=== modified file 'src/app/webbrowser/downloads-model.cpp'
--- src/app/webbrowser/downloads-model.cpp 2016-07-06 09:31:57 +0000
+++ src/app/webbrowser/downloads-model.cpp 2016-10-06 07:46:34 +0000
@@ -104,6 +104,7 @@
104 int count = 0; // size() isn't supported on the sqlite backend104 int count = 0; // size() isn't supported on the sqlite backend
105 while (populateQuery.next()) {105 while (populateQuery.next()) {
106 DownloadEntry entry;106 DownloadEntry entry;
107 entry.incognito = false;
107 entry.downloadId = populateQuery.value(0).toString();108 entry.downloadId = populateQuery.value(0).toString();
108 entry.url = populateQuery.value(1).toUrl();109 entry.url = populateQuery.value(1).toUrl();
109 entry.path = populateQuery.value(2).toString();110 entry.path = populateQuery.value(2).toString();
@@ -147,6 +148,7 @@
147 roles[Paused] = "paused";148 roles[Paused] = "paused";
148 roles[Error] = "error";149 roles[Error] = "error";
149 roles[Created] = "created";150 roles[Created] = "created";
151 roles[Incognito] = "incognito";
150 }152 }
151 return roles;153 return roles;
152}154}
@@ -182,6 +184,8 @@
182 return entry.error;184 return entry.error;
183 case Created:185 case Created:
184 return entry.created;186 return entry.created;
187 case Incognito:
188 return entry.incognito;
185 default:189 default:
186 return QVariant();190 return QVariant();
187 }191 }
@@ -218,7 +222,7 @@
218 Add a download to the database. This should happen as soon as the download222 Add a download to the database. This should happen as soon as the download
219 is started.223 is started.
220*/224*/
221void DownloadsModel::add(const QString& downloadId, const QUrl& url, const QString& mimetype)225void DownloadsModel::add(const QString& downloadId, const QUrl& url, const QString& mimetype, bool incognito)
222{226{
223 beginInsertRows(QModelIndex(), 0, 0);227 beginInsertRows(QModelIndex(), 0, 0);
224 DownloadEntry entry;228 DownloadEntry entry;
@@ -227,83 +231,111 @@
227 entry.paused = false;231 entry.paused = false;
228 entry.url = url;232 entry.url = url;
229 entry.mimetype = mimetype;233 entry.mimetype = mimetype;
234 entry.incognito = incognito;
230 m_orderedEntries.prepend(entry);235 m_orderedEntries.prepend(entry);
231 m_numRows++;236 m_numRows++;
232 m_fetchedCount++;
233 endInsertRows();237 endInsertRows();
234 Q_EMIT added(downloadId, url, mimetype);
235 insertNewEntryInDatabase(entry);
236 Q_EMIT rowCountChanged();238 Q_EMIT rowCountChanged();
237}239 if (!incognito) {
238240 insertNewEntryInDatabase(entry);
239void DownloadsModel::setPath(const QString& downloadId, const QString& path)241 m_fetchedCount++;
240{242 }
241 QSqlQuery query(m_database);
242
243 // Override reported mimetype from server with detected mimetype from file once downloaded
244 QMimeDatabase mimeDatabase;
245 QString mimetype = mimeDatabase.mimeTypeForFile(path).name();
246
247 static QString updateStatement = QLatin1String("UPDATE downloads SET mimetype = ?, "
248 "path = ? WHERE downloadId = ?");
249 query.prepare(updateStatement);
250 query.addBindValue(mimetype);
251 query.addBindValue(path);
252 query.addBindValue(downloadId);
253 query.exec();
254 Q_EMIT pathChanged(downloadId, path);
255}243}
256244
257void DownloadsModel::setComplete(const QString& downloadId, const bool complete)245void DownloadsModel::setComplete(const QString& downloadId, const bool complete)
258{246{
259 QSqlQuery query(m_database);247 int index = getIndexForDownloadId(downloadId);
260 static QString updateStatement = QLatin1String("UPDATE downloads SET complete = ? "248 if (index != -1) {
261 "WHERE downloadId = ?");249 DownloadEntry& entry = m_orderedEntries[index];
262 query.prepare(updateStatement);250 if (entry.complete == complete) {
263 query.addBindValue(complete);251 return;
264 query.addBindValue(downloadId);252 }
265 query.exec();253 entry.complete = complete;
266 Q_EMIT completeChanged(downloadId, complete);254 Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Complete);
267 reload();255 if (!entry.incognito) {
256 QSqlQuery query(m_database);
257 static QString updateStatement = QLatin1String("UPDATE downloads SET complete=? WHERE downloadId=?;");
258 query.prepare(updateStatement);
259 query.addBindValue(complete);
260 query.addBindValue(downloadId);
261 query.exec();
262 }
263 }
268}264}
269265
270void DownloadsModel::setError(const QString& downloadId, const QString& error)266void DownloadsModel::setError(const QString& downloadId, const QString& error)
271{267{
272 QSqlQuery query(m_database);268 int index = getIndexForDownloadId(downloadId);
273 static QString updateStatement = QLatin1String("UPDATE downloads SET error = ? "269 if (index != -1) {
274 "WHERE downloadId = ?");270 DownloadEntry& entry = m_orderedEntries[index];
275 query.prepare(updateStatement);271 if (entry.error == error) {
276 query.addBindValue(error);272 return;
277 query.addBindValue(downloadId);273 }
278 query.exec();274 entry.error = error;
279 Q_EMIT errorChanged(downloadId, error);275 Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Error);
280 reload();276 if (!entry.incognito) {
277 QSqlQuery query(m_database);
278 static QString updateStatement = QLatin1String("UPDATE downloads SET error=? WHERE downloadId=?;");
279 query.prepare(updateStatement);
280 query.addBindValue(error);
281 query.addBindValue(downloadId);
282 query.exec();
283 }
284 }
281}285}
282286
283void DownloadsModel::moveToDownloads(const QString& downloadId, const QString& path)287void DownloadsModel::moveToDownloads(const QString& downloadId, const QString& path)
284{288{
289 int index = getIndexForDownloadId(downloadId);
290 if (index == -1) {
291 return;
292 }
285 QFile file(path);293 QFile file(path);
286 if (file.exists()) {294 if (file.exists()) {
287 QFileInfo fi(path);295 QFileInfo fi(path);
288 QString suffix = fi.completeSuffix();296 DownloadEntry& entry = m_orderedEntries[index];
289 QString filename = fi.fileName();297 QVector<int> updatedRoles;
290 QString filenameWithoutSuffix = filename.left(filename.size() - suffix.size());298
299 // Override reported mimetype from server with detected mimetype from file once downloaded
300 QMimeDatabase mimeDatabase;
301 QString mimetype = mimeDatabase.mimeTypeForFile(fi).name();
302 if (mimetype != entry.mimetype) {
303 entry.mimetype = mimetype;
304 updatedRoles.append(Mimetype);
305 }
306
307 // Move file to XDG Downloads folder
291 QDir dir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));308 QDir dir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
292 if (!dir.exists()) {309 if (!dir.exists()) {
293 QDir::root().mkpath(dir.absolutePath());310 QDir::root().mkpath(dir.absolutePath());
294 }311 }
295 QString destination = dir.absoluteFilePath(filenameWithoutSuffix + suffix);312 QString baseName = fi.baseName();
313 QString suffix = fi.completeSuffix();
314 QString destination = dir.absoluteFilePath(QString("%1.%2").arg(baseName, suffix));
296 // Avoid filename collision by automatically inserting an incremented315 // Avoid filename collision by automatically inserting an incremented
297 // number into the filename if the original name already exists.316 // number into the filename if the original name already exists.
298 int append = 1;317 int append = 1;
299 while (QFile::exists(destination)) {318 while (QFile::exists(destination)) {
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));
301 }320 }
302 if (file.rename(destination)) {321 if (file.rename(destination)) {
303 setPath(downloadId, destination);322 entry.path = destination;
323 updatedRoles.append(Path);
304 } else {324 } else {
305 qWarning() << "Failed moving file from" << path << "to" << destination;325 qWarning() << "Failed moving file from" << path << "to" << destination;
306 }326 }
327
328 Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), updatedRoles);
329 if (!entry.incognito && !updatedRoles.isEmpty()) {
330 QSqlQuery query(m_database);
331 static QString updateStatement = QLatin1String("UPDATE downloads SET mimetype = ?, "
332 "path = ? WHERE downloadId = ?");
333 query.prepare(updateStatement);
334 query.addBindValue(mimetype);
335 query.addBindValue(destination);
336 query.addBindValue(downloadId);
337 query.exec();
338 }
307 } else {339 } else {
308 qWarning() << "Download not found:" << path;340 qWarning() << "Download not found:" << path;
309 }341 }
@@ -331,15 +363,17 @@
331 int index = 0;363 int index = 0;
332 Q_FOREACH(DownloadEntry entry, m_orderedEntries) {364 Q_FOREACH(DownloadEntry entry, m_orderedEntries) {
333 if (entry.path == path) {365 if (entry.path == path) {
366 bool incognito = entry.incognito;
334 beginRemoveRows(QModelIndex(), index, index);367 beginRemoveRows(QModelIndex(), index, index);
335 m_orderedEntries.removeAt(index);368 m_orderedEntries.removeAt(index);
336 endRemoveRows();369 endRemoveRows();
337 Q_EMIT deleted(path);
338 removeExistingEntryFromDatabase(path);
339 m_fetchedCount--;
340 m_numRows--;370 m_numRows--;
341 Q_EMIT rowCountChanged();371 Q_EMIT rowCountChanged();
342 QFile::remove(path);372 QFile::remove(path);
373 if (!incognito) {
374 removeExistingEntryFromDatabase(path);
375 m_fetchedCount--;
376 }
343 return;377 return;
344 } else {378 } else {
345 index++;379 index++;
@@ -352,45 +386,69 @@
352*/386*/
353void DownloadsModel::cancelDownload(const QString& downloadId)387void DownloadsModel::cancelDownload(const QString& downloadId)
354{388{
355 int index=0;389 int index = getIndexForDownloadId(downloadId);
356 Q_FOREACH(DownloadEntry entry, m_orderedEntries) {390 if (index != -1) {
357 if (entry.downloadId == downloadId) {391 const DownloadEntry& entry = m_orderedEntries.at(index);
358 beginRemoveRows(QModelIndex(), index, index);392 bool incognito = entry.incognito;
359 m_orderedEntries.removeAt(index);393 beginRemoveRows(QModelIndex(), index, index);
394 m_orderedEntries.removeAt(index);
395 endRemoveRows();
396 m_numRows--;
397 Q_EMIT rowCountChanged();
398 if (!incognito) {
360 QSqlQuery query(m_database);399 QSqlQuery query(m_database);
361 static QString deleteStatement = QLatin1String("DELETE FROM downloads WHERE downloadId=?;");400 static QString deleteStatement = QLatin1String("DELETE FROM downloads WHERE downloadId=?;");
362 query.prepare(deleteStatement);401 query.prepare(deleteStatement);
363 query.addBindValue(downloadId);402 query.addBindValue(downloadId);
364 query.exec();403 query.exec();
365 endRemoveRows();
366 m_fetchedCount--;404 m_fetchedCount--;
367 m_numRows--;405 }
368 Q_EMIT rowCountChanged();406 }
407}
408
409void DownloadsModel::setPaused(const QString& downloadId, bool paused)
410{
411 int index = getIndexForDownloadId(downloadId);
412 if (index != -1) {
413 DownloadEntry& entry = m_orderedEntries[index];
414 if (entry.paused == paused) {
369 return;415 return;
370 } else {416 }
371 index++;417 entry.paused = paused;
418 Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Paused);
419 if (!entry.incognito) {
420 QSqlQuery query(m_database);
421 static QString pauseStatement = QLatin1String("UPDATE downloads SET paused=? WHERE downloadId=?;");
422 query.prepare(pauseStatement);
423 query.addBindValue(paused);
424 query.addBindValue(downloadId);
425 query.exec();
372 }426 }
373 }427 }
374}428}
375429
376void DownloadsModel::pauseDownload(const QString& downloadId)430void DownloadsModel::pauseDownload(const QString& downloadId)
377{431{
378 QSqlQuery query(m_database);432 setPaused(downloadId, true);
379 static QString pauseStatement = QLatin1String("UPDATE downloads SET paused=1 WHERE downloadId=?;");
380 query.prepare(pauseStatement);
381 query.addBindValue(downloadId);
382 query.exec();
383 reload();
384}433}
385434
386void DownloadsModel::resumeDownload(const QString& downloadId)435void DownloadsModel::resumeDownload(const QString& downloadId)
387{436{
388 QSqlQuery query(m_database);437 setPaused(downloadId, false);
389 static QString resumeStatement = QLatin1String("UPDATE downloads SET paused=0 WHERE downloadId=?;");438}
390 query.prepare(resumeStatement);439
391 query.addBindValue(downloadId);440void DownloadsModel::pruneIncognitoDownloads()
392 query.exec();441{
393 reload();442 for (int i = m_orderedEntries.size() - 1; i >= 0; --i) {
443 const DownloadEntry& entry = m_orderedEntries.at(i);
444 if (entry.incognito) {
445 beginRemoveRows(QModelIndex(), i, i);
446 m_orderedEntries.removeAt(i);
447 endRemoveRows();
448 m_numRows--;
449 Q_EMIT rowCountChanged();
450 }
451 }
394}452}
395453
396void DownloadsModel::removeExistingEntryFromDatabase(const QString& path)454void DownloadsModel::removeExistingEntryFromDatabase(const QString& path)
@@ -409,14 +467,15 @@
409 return m_canFetchMore;467 return m_canFetchMore;
410}468}
411469
412void DownloadsModel::reload()470int DownloadsModel::getIndexForDownloadId(const QString& downloadId) const
413{471{
414 beginResetModel();472 int index = 0;
415 m_orderedEntries.clear();473 Q_FOREACH(const DownloadEntry& entry, m_orderedEntries) {
416 m_canFetchMore = true;474 if (entry.downloadId == downloadId) {
417 m_fetchedCount = 0;475 return index;
418 m_numRows = 0;476 } else {
419 endResetModel();477 ++index;
420 fetchMore();478 }
421 Q_EMIT rowCountChanged();479 }
480 return -1;
422}481}
423482
=== modified file 'src/app/webbrowser/downloads-model.h'
--- src/app/webbrowser/downloads-model.h 2016-01-12 10:37:15 +0000
+++ src/app/webbrowser/downloads-model.h 2016-10-06 07:46:34 +0000
@@ -49,7 +49,8 @@
49 Complete,49 Complete,
50 Paused,50 Paused,
51 Error,51 Error,
52 Created52 Created,
53 Incognito
53 };54 };
5455
55 // reimplemented from QAbstractListModel56 // reimplemented from QAbstractListModel
@@ -63,23 +64,18 @@
63 void setDatabasePath(const QString& path);64 void setDatabasePath(const QString& path);
6465
65 Q_INVOKABLE bool contains(const QString& downloadId) const;66 Q_INVOKABLE bool contains(const QString& downloadId) const;
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);
67 Q_INVOKABLE void moveToDownloads(const QString& downloadId, const QString& path);68 Q_INVOKABLE void moveToDownloads(const QString& downloadId, const QString& path);
68 Q_INVOKABLE void setPath(const QString& downloadId, const QString& path);
69 Q_INVOKABLE void setComplete(const QString& downloadId, const bool complete);69 Q_INVOKABLE void setComplete(const QString& downloadId, const bool complete);
70 Q_INVOKABLE void setError(const QString& downloadId, const QString& error);70 Q_INVOKABLE void setError(const QString& downloadId, const QString& error);
71 Q_INVOKABLE void deleteDownload(const QString& path);71 Q_INVOKABLE void deleteDownload(const QString& path);
72 Q_INVOKABLE void cancelDownload(const QString& downloadId);72 Q_INVOKABLE void cancelDownload(const QString& downloadId);
73 Q_INVOKABLE void pauseDownload(const QString& downloadId);73 Q_INVOKABLE void pauseDownload(const QString& downloadId);
74 Q_INVOKABLE void resumeDownload(const QString& downloadId);74 Q_INVOKABLE void resumeDownload(const QString& downloadId);
75 Q_INVOKABLE void pruneIncognitoDownloads();
7576
76Q_SIGNALS:77Q_SIGNALS:
77 void databasePathChanged() const;78 void databasePathChanged() const;
78 void added(const QString& downloadId, const QUrl& url, const QString& mimetype) const;
79 void pathChanged(const QString& downloadId, const QString& path) const;
80 void completeChanged(const QString& downloadId, const bool complete) const;
81 void errorChanged(const QString& downloadId, const QString& error) const;
82 void deleted(const QString& path) const;
83 void rowCountChanged();79 void rowCountChanged();
8480
85private:81private:
@@ -98,6 +94,7 @@
98 bool paused;94 bool paused;
99 QString error;95 QString error;
100 QDateTime created;96 QDateTime created;
97 bool incognito;
101 };98 };
102 QList<DownloadEntry> m_orderedEntries;99 QList<DownloadEntry> m_orderedEntries;
103100
@@ -105,7 +102,8 @@
105 void createOrAlterDatabaseSchema();102 void createOrAlterDatabaseSchema();
106 void insertNewEntryInDatabase(const DownloadEntry& entry);103 void insertNewEntryInDatabase(const DownloadEntry& entry);
107 void removeExistingEntryFromDatabase(const QString& path);104 void removeExistingEntryFromDatabase(const QString& path);
108 void reload();105 void setPaused(const QString& downloadId, bool paused);
106 int getIndexForDownloadId(const QString& downloadId) const;
109};107};
110108
111#endif // __DOWNLOADS_MODEL_H__109#endif // __DOWNLOADS_MODEL_H__
112110
=== modified file 'src/app/webbrowser/webbrowser-app.qml'
--- src/app/webbrowser/webbrowser-app.qml 2016-09-20 19:55:22 +0000
+++ src/app/webbrowser/webbrowser-app.qml 2016-10-06 07:46:34 +0000
@@ -128,6 +128,20 @@
128 session.clear()128 session.clear()
129 }129 }
130 }130 }
131 if (incognito && (allWindows.length > 1)) {
132 // If the last incognito window is being closed,
133 // prune incognito entries from the downloads model
134 var incognitoWindows = 0
135 for (var w in allWindows) {
136 var window = allWindows[w]
137 if ((window !== this) && window.incognito) {
138 ++incognitoWindows
139 }
140 }
141 if (incognitoWindows == 0) {
142 DownloadsModel.pruneIncognitoDownloads()
143 }
144 }
131 destroy()145 destroy()
132 }146 }
133147
134148
=== modified file 'tests/unittests/downloads-model/tst_DownloadsModelTests.cpp'
--- tests/unittests/downloads-model/tst_DownloadsModelTests.cpp 2016-01-12 10:37:15 +0000
+++ tests/unittests/downloads-model/tst_DownloadsModelTests.cpp 2016-10-06 07:46:34 +0000
@@ -17,6 +17,8 @@
17 */17 */
1818
19#include <QtCore/QDir>19#include <QtCore/QDir>
20#include <QtCore/QFileInfo>
21#include <QtCore/QTemporaryDir>
20#include <QtCore/QTemporaryFile>22#include <QtCore/QTemporaryFile>
21#include <QtTest/QSignalSpy>23#include <QtTest/QSignalSpy>
22#include <QtTest/QtTest>24#include <QtTest/QtTest>
@@ -27,11 +29,16 @@
27 Q_OBJECT29 Q_OBJECT
2830
29private:31private:
32 QTemporaryDir homeDir;
30 DownloadsModel* model;33 DownloadsModel* model;
3134
32private Q_SLOTS:35private Q_SLOTS:
33 void init()36 void init()
34 {37 {
38 // QStandardPaths::setTestModeEnabled() doesn't affect
39 // QStandardPaths::DownloadLocation, so we must override $HOME to
40 // ensure the test won't write data to the user's home directory.
41 qputenv("HOME", homeDir.path().toUtf8());
35 model = new DownloadsModel;42 model = new DownloadsModel;
36 model->setDatabasePath(":memory:");43 model->setDatabasePath(":memory:");
37 }44 }
@@ -39,6 +46,7 @@
39 void cleanup()46 void cleanup()
40 {47 {
41 delete model;48 delete model;
49 qunsetenv("HOME");
42 }50 }
4351
44 void shouldBeInitiallyEmpty()52 void shouldBeInitiallyEmpty()
@@ -58,90 +66,177 @@
58 QVERIFY(roleNames.contains("paused"));66 QVERIFY(roleNames.contains("paused"));
59 QVERIFY(roleNames.contains("error"));67 QVERIFY(roleNames.contains("error"));
60 QVERIFY(roleNames.contains("created"));68 QVERIFY(roleNames.contains("created"));
69 QVERIFY(roleNames.contains("incognito"));
61 }70 }
6271
63 void shouldContainAddedEntries()72 void shouldContainAddedEntries()
64 {73 {
65 QVERIFY(!model->contains(QStringLiteral("testid")));74 QVERIFY(!model->contains(QStringLiteral("testid")));
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);
67 QVERIFY(model->contains(QStringLiteral("testid")));76 QVERIFY(model->contains(QStringLiteral("testid")));
68 }77 }
6978
70 void shouldAddNewEntries()79 void shouldAddNewEntries()
71 {80 {
72 QSignalSpy spy(model, SIGNAL(added(QString, QUrl, QString)));81 QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
7382
74 model->add("testid", QUrl("http://example.org/"), "text/plain");83 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
75 QCOMPARE(model->rowCount(), 1);84 QCOMPARE(model->rowCount(), 1);
76 QCOMPARE(spy.count(), 1);85 QCOMPARE(spy.count(), 1);
77 QVariantList args = spy.takeFirst();86 QVariantList args = spy.takeFirst();
78 QCOMPARE(args.at(0).toString(), QString("testid"));87 QCOMPARE(args.at(0).toInt(), 0);
79 QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/"));88 QCOMPARE(args.at(1).toInt(), 0);
80 QCOMPARE(args.at(2).toString(), QString("text/plain"));89 QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid"));
90 QCOMPARE(model->data(model->index(0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/")));
91 QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain"));
8192
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);
83 QCOMPARE(model->rowCount(), 2);94 QCOMPARE(model->rowCount(), 2);
84 QCOMPARE(spy.count(), 1);95 QCOMPARE(spy.count(), 1);
85 args = spy.takeFirst();96 args = spy.takeFirst();
86 QCOMPARE(args.at(0).toString(), QString("testid2"));97 QCOMPARE(args.at(0).toInt(), 0);
87 QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/pdf"));98 QCOMPARE(args.at(1).toInt(), 0);
88 QCOMPARE(args.at(2).toString(), QString("application/pdf"));99 QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid2"));
100 QCOMPARE(model->data(model->index(0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/pdf")));
101 QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("application/pdf"));
89 }102 }
90103
91 void shouldRemoveCancelled()104 void shouldRemoveCancelled()
92 {105 {
93 model->add("testid", QUrl("http://example.org/"), "text/plain");106 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
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);
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);
96 QCOMPARE(model->rowCount(), 3);109 QCOMPARE(model->rowCount(), 3);
97110
98 model->cancelDownload("testid2");111 model->cancelDownload(QStringLiteral("testid2"));
99 QCOMPARE(model->rowCount(), 2);112 QCOMPARE(model->rowCount(), 2);
100113
101 model->cancelDownload("invalid");114 model->cancelDownload(QStringLiteral("invalid"));
102 QCOMPARE(model->rowCount(), 2);115 QCOMPARE(model->rowCount(), 2);
103 }116 }
104117
105 void shouldCompleteDownloads()118 void shouldCompleteDownloads()
106 {119 {
107 QSignalSpy spy(model, SIGNAL(completeChanged(QString, bool)));120 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
108121 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool());
109 model->add("testid", QUrl("http://example.org/"), "text/plain");122 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
110 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool());123
111 model->setComplete("testid", true);124 model->setComplete(QStringLiteral("testid"), true);
112 QCOMPARE(spy.count(), 1);125 QVERIFY(model->data(model->index(0, 0), DownloadsModel::Complete).toBool());
113 QVariantList args = spy.takeFirst();126 QCOMPARE(spy.count(), 1);
114 QCOMPARE(args.at(0).toString(), QString("testid"));127 QVariantList args = spy.takeFirst();
115 QCOMPARE(args.at(1).toBool(), true);128 QCOMPARE(args.at(0).toModelIndex().row(), 0);
129 QCOMPARE(args.at(1).toModelIndex().row(), 0);
130 QVector<int> roles = args.at(2).value<QVector<int> >();
131 QCOMPARE(roles.size(), 1);
132 QCOMPARE(roles.at(0), (int) DownloadsModel::Complete);
133
134 model->setComplete(QStringLiteral("testid"), true);
135 QVERIFY(model->data(model->index(0, 0), DownloadsModel::Complete).toBool());
136 QVERIFY(spy.isEmpty());
137
138 model->setComplete(QStringLiteral("testid"), false);
139 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool());
140 QCOMPARE(spy.count(), 1);
141 args = spy.takeFirst();
142 QCOMPARE(args.at(0).toModelIndex().row(), 0);
143 QCOMPARE(args.at(1).toModelIndex().row(), 0);
144 roles = args.at(2).value<QVector<int> >();
145 QCOMPARE(roles.size(), 1);
146 QCOMPARE(roles.at(0), (int) DownloadsModel::Complete);
147 }
148
149 void shouldSetError()
150 {
151 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
152 QVERIFY(model->data(model->index(0, 0), DownloadsModel::Error).toString().isEmpty());
153 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
154
155 model->setError(QStringLiteral("testid"), QStringLiteral("foo"));
156 QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("foo"));
157 QCOMPARE(spy.count(), 1);
158 QVariantList args = spy.takeFirst();
159 QCOMPARE(args.at(0).toModelIndex().row(), 0);
160 QCOMPARE(args.at(1).toModelIndex().row(), 0);
161 QVector<int> roles = args.at(2).value<QVector<int> >();
162 QCOMPARE(roles.size(), 1);
163 QCOMPARE(roles.at(0), (int) DownloadsModel::Error);
164
165 model->setError(QStringLiteral("testid"), QStringLiteral("foo"));
166 QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("foo"));
167 QVERIFY(spy.isEmpty());
168
169 model->setError(QStringLiteral("testid"), QString("bar"));
170 QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("bar"));
171 QCOMPARE(spy.count(), 1);
172 args = spy.takeFirst();
173 QCOMPARE(args.at(0).toModelIndex().row(), 0);
174 QCOMPARE(args.at(1).toModelIndex().row(), 0);
175 roles = args.at(2).value<QVector<int> >();
176 QCOMPARE(roles.size(), 1);
177 QCOMPARE(roles.at(0), (int) DownloadsModel::Error);
178 }
179
180 void shouldPauseAndResumeDownload()
181 {
182 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
183 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Paused).toBool());
184 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
185
186 model->pauseDownload(QStringLiteral("testid"));
187 QVERIFY(model->data(model->index(0, 0), DownloadsModel::Paused).toBool());
188 QCOMPARE(spy.count(), 1);
189 QVariantList args = spy.takeFirst();
190 QCOMPARE(args.at(0).toModelIndex().row(), 0);
191 QCOMPARE(args.at(1).toModelIndex().row(), 0);
192 QVector<int> roles = args.at(2).value<QVector<int> >();
193 QCOMPARE(roles.size(), 1);
194 QCOMPARE(roles.at(0), (int) DownloadsModel::Paused);
195
196 model->pauseDownload(QStringLiteral("testid"));
197 QVERIFY(model->data(model->index(0, 0), DownloadsModel::Paused).toBool());
198 QVERIFY(spy.isEmpty());
199
200 model->resumeDownload(QStringLiteral("testid"));
201 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Paused).toBool());
202 QCOMPARE(spy.count(), 1);
203 args = spy.takeFirst();
204 QCOMPARE(args.at(0).toModelIndex().row(), 0);
205 QCOMPARE(args.at(1).toModelIndex().row(), 0);
206 roles = args.at(2).value<QVector<int> >();
207 QCOMPARE(roles.size(), 1);
208 QCOMPARE(roles.at(0), (int) DownloadsModel::Paused);
116 }209 }
117210
118 void shouldKeepEntriesSortedChronologically()211 void shouldKeepEntriesSortedChronologically()
119 {212 {
120 model->add("testid", QUrl("http://example.org/"), "text/plain");213 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
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);
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);
123216
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"));
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"));
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"));
127 }220 }
128221
129 void shouldReturnData()222 void shouldReturnData()
130 {223 {
131 model->add("testid", QUrl("http://example.org/"), "text/plain");224 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
132 QVERIFY(!model->data(QModelIndex(), DownloadsModel::DownloadId).isValid());225 QVERIFY(!model->data(QModelIndex(), DownloadsModel::DownloadId).isValid());
133 QVERIFY(!model->data(model->index(-1, 0), DownloadsModel::DownloadId).isValid());226 QVERIFY(!model->data(model->index(-1, 0), DownloadsModel::DownloadId).isValid());
134 QVERIFY(!model->data(model->index(3, 0), DownloadsModel::DownloadId).isValid());227 QVERIFY(!model->data(model->index(3, 0), DownloadsModel::DownloadId).isValid());
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"));
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/")));
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"));
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());
139 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool());232 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool());
233 QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Incognito).toBool());
234 QVERIFY(!model->data(model->index(0, 0), -1).isValid());
140 }235 }
141236
142 void shouldReturnDatabasePath()237 void shouldReturnDatabasePath()
143 {238 {
144 QCOMPARE(model->databasePath(), QString(":memory:"));239 QCOMPARE(model->databasePath(), QStringLiteral(":memory:"));
145 }240 }
146241
147 void shouldNotifyWhenSettingDatabasePath()242 void shouldNotifyWhenSettingDatabasePath()
@@ -149,14 +244,14 @@
149 QSignalSpy spyPath(model, SIGNAL(databasePathChanged()));244 QSignalSpy spyPath(model, SIGNAL(databasePathChanged()));
150 QSignalSpy spyReset(model, SIGNAL(modelReset()));245 QSignalSpy spyReset(model, SIGNAL(modelReset()));
151246
152 model->setDatabasePath(":memory:");247 model->setDatabasePath(QStringLiteral(":memory:"));
153 QVERIFY(spyPath.isEmpty());248 QVERIFY(spyPath.isEmpty());
154 QVERIFY(spyReset.isEmpty());249 QVERIFY(spyReset.isEmpty());
155250
156 model->setDatabasePath("");251 model->setDatabasePath(QStringLiteral(""));
157 QCOMPARE(spyPath.count(), 1);252 QCOMPARE(spyPath.count(), 1);
158 QCOMPARE(spyReset.count(), 1);253 QCOMPARE(spyReset.count(), 1);
159 QCOMPARE(model->databasePath(), QString(":memory:"));254 QCOMPARE(model->databasePath(), QStringLiteral(":memory:"));
160 }255 }
161256
162 void shouldSerializeOnDisk()257 void shouldSerializeOnDisk()
@@ -167,8 +262,10 @@
167 delete model;262 delete model;
168 model = new DownloadsModel;263 model = new DownloadsModel;
169 model->setDatabasePath(fileName);264 model->setDatabasePath(fileName);
170 model->add("testid", QUrl("http://example.org/"), "text/plain");265 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
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);
267 model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("http://example.org/incognito.pdf")), QStringLiteral("application/pdf"), true);
268 QCOMPARE(model->rowCount(), 3);
172 delete model;269 delete model;
173 model = new DownloadsModel;270 model = new DownloadsModel;
174 model->setDatabasePath(fileName);271 model->setDatabasePath(fileName);
@@ -180,17 +277,139 @@
180 {277 {
181 QCOMPARE(model->property("count").toInt(), 0);278 QCOMPARE(model->property("count").toInt(), 0);
182 QCOMPARE(model->rowCount(), 0);279 QCOMPARE(model->rowCount(), 0);
183 model->add("testid", QUrl("http://example.org/"), "text/plain");280 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
184 QCOMPARE(model->property("count").toInt(), 1);281 QCOMPARE(model->property("count").toInt(), 1);
185 QCOMPARE(model->rowCount(), 1);282 QCOMPARE(model->rowCount(), 1);
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);
187 QCOMPARE(model->property("count").toInt(), 2);284 QCOMPARE(model->property("count").toInt(), 2);
188 QCOMPARE(model->rowCount(), 2);285 QCOMPARE(model->rowCount(), 2);
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);
190 QCOMPARE(model->property("count").toInt(), 3);287 QCOMPARE(model->property("count").toInt(), 3);
191 QCOMPARE(model->rowCount(), 3);288 QCOMPARE(model->rowCount(), 3);
192 }289 }
193290
291 void shouldPruneIncognitoDownloads()
292 {
293 model->add(QStringLiteral("testid1"), QUrl(QStringLiteral("http://example.org/1")), QStringLiteral("text/plain"), false);
294 model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/2")), QStringLiteral("text/plain"), true);
295 model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("http://example.org/3")), QStringLiteral("text/plain"), false);
296 model->add(QStringLiteral("testid4"), QUrl(QStringLiteral("http://example.org/4")), QStringLiteral("text/plain"), true);
297 QCOMPARE(model->rowCount(), 4);
298 QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
299 QSignalSpy spyRowCountChanged(model, SIGNAL(rowCountChanged()));
300 model->pruneIncognitoDownloads();
301 QCOMPARE(model->rowCount(), 2);
302 QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid3"));
303 QCOMPARE(model->data(model->index(1), DownloadsModel::DownloadId).toString(), QStringLiteral("testid1"));
304 QCOMPARE(spyRowsRemoved.count(), 2);
305 QCOMPARE(spyRowCountChanged.count(), 2);
306 }
307
308 void shouldFailToMoveInvalidDownload()
309 {
310 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
311 QTemporaryFile tempFile;
312 tempFile.open();
313 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
314 model->moveToDownloads(QStringLiteral("foobar"), tempFile.fileName());
315 QVERIFY(spy.isEmpty());
316 }
317
318 void shouldFailToMoveNonExistentFile()
319 {
320 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
321 QTemporaryFile tempFile;
322 tempFile.open();
323 QString fileName = tempFile.fileName();
324 tempFile.remove();
325 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
326 QTest::ignoreMessage(QtWarningMsg, QString("Download not found: \"%1\"").arg(fileName).toUtf8().constData());
327 model->moveToDownloads(QStringLiteral("testid"), fileName);
328 QVERIFY(spy.isEmpty());
329 }
330
331 void shouldMoveFile()
332 {
333 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("application/pdf"), false);
334 QTemporaryFile tempFile(QStringLiteral("XXXXXX.txt"));
335 tempFile.open();
336 tempFile.write(QByteArray("foo bar baz"));
337 tempFile.close();
338 QString filePath = tempFile.fileName();
339 QString fileName = QFileInfo(filePath).fileName();
340 QVERIFY(QFile::exists(filePath));
341 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
342 model->moveToDownloads(QStringLiteral("testid"), filePath);
343 QCOMPARE(spy.count(), 1);
344 QVariantList args = spy.takeFirst();
345 QCOMPARE(args.at(0).toModelIndex().row(), 0);
346 QCOMPARE(args.at(1).toModelIndex().row(), 0);
347 QVector<int> roles = args.at(2).value<QVector<int> >();
348 QCOMPARE(roles.size(), 2);
349 QVERIFY(roles.contains(DownloadsModel::Mimetype));
350 QVERIFY(roles.contains(DownloadsModel::Path));
351 QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain"));
352 QCOMPARE(model->data(model->index(0), DownloadsModel::Path).toString(), QString("%1/Downloads/%2").arg(homeDir.path(), fileName));
353 QVERIFY(!QFile::exists(filePath));
354 }
355
356 void shouldRenameFileToAvoidFilenameCollision()
357 {
358 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
359 QTemporaryFile tempFile(QStringLiteral("XXXXXX.txt"));
360 tempFile.open();
361 tempFile.write(QByteArray("foo"));
362 tempFile.close();
363 QString filePath = tempFile.fileName();
364 QString fileName = QFileInfo(filePath).fileName();
365 QVERIFY(QFile::exists(filePath));
366 QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
367 QString path = QString("%1/Downloads/%2").arg(homeDir.path(), fileName);
368 QFile file(path);
369 QVERIFY(file.open(QIODevice::WriteOnly));
370 QVERIFY(file.write("bar") != -1);
371 file.close();
372 model->moveToDownloads(QStringLiteral("testid"), filePath);
373 QString otherPath = QString("%1/Downloads/%2").arg(homeDir.path(), fileName.replace(QStringLiteral("."), QStringLiteral(".1.")));
374 QCOMPARE(model->data(model->index(0), DownloadsModel::Path).toString(), otherPath);
375 QVERIFY(!QFile::exists(filePath));
376 QVERIFY(QFile::exists(path));
377 QVERIFY(QFile::exists(otherPath));
378 QVERIFY(file.open(QIODevice::ReadOnly));
379 QCOMPARE(file.readAll(), QByteArray("bar"));
380 file.close();
381 QFile file2(otherPath);
382 QVERIFY(file2.open(QIODevice::ReadOnly));
383 QCOMPARE(file2.readAll(), QByteArray("foo"));
384 file2.close();
385 }
386
387 void shouldDeleteDownload()
388 {
389 // Need a file saved on disk to allow deleting it
390 model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false);
391 QTemporaryFile tempFile(QStringLiteral("XXXXXX.txt"));
392 tempFile.open();
393 tempFile.write(QByteArray("foo bar baz"));
394 tempFile.close();
395 QString filePath = tempFile.fileName();
396 QString fileName = QFileInfo(filePath).fileName();
397 model->moveToDownloads(QStringLiteral("testid"), filePath);
398 QString path = model->data(model->index(0), DownloadsModel::Path).toString();
399 QVERIFY(QFile::exists(path));
400
401 QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
402 QSignalSpy spyRowCount(model, SIGNAL(rowCountChanged()));
403 model->deleteDownload(path);
404 QCOMPARE(spyRowsRemoved.count(), 1);
405 QVariantList args = spyRowsRemoved.takeFirst();
406 QVERIFY(!args.at(0).toModelIndex().isValid());
407 QCOMPARE(args.at(1).toInt(), 0);
408 QCOMPARE(args.at(2).toInt(), 0);
409 QCOMPARE(spyRowCount.count(), 1);
410 QCOMPARE(model->rowCount(), 0);
411 QVERIFY(!QFile::exists(path));
412 }
194};413};
195414
196QTEST_MAIN(DownloadsModelTests)415QTEST_MAIN(DownloadsModelTests)

Subscribers

People subscribed via source and target branches

to status/vote changes: