1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2010 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import QDir, QFile, QFileInfo, QIODevice, QUrl
46from PyQt5.QtWidgets import (QApplication, QDialog, QDialogButtonBox,
47        QHBoxLayout, QLabel, QLineEdit, QMessageBox, QProgressDialog,
48        QPushButton, QVBoxLayout)
49from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
50
51
52class HttpWindow(QDialog):
53    def __init__(self, parent=None):
54        super(HttpWindow, self).__init__(parent)
55
56        self.url = QUrl()
57        self.qnam = QNetworkAccessManager()
58        self.reply = None
59        self.outFile = None
60        self.httpGetId = 0
61        self.httpRequestAborted = False
62
63        self.urlLineEdit = QLineEdit('https://www.qt.io')
64
65        urlLabel = QLabel("&URL:")
66        urlLabel.setBuddy(self.urlLineEdit)
67        self.statusLabel = QLabel(
68                "Please enter the URL of a file you want to download.")
69        self.statusLabel.setWordWrap(True)
70
71        self.downloadButton = QPushButton("Download")
72        self.downloadButton.setDefault(True)
73        self.quitButton = QPushButton("Quit")
74        self.quitButton.setAutoDefault(False)
75
76        buttonBox = QDialogButtonBox()
77        buttonBox.addButton(self.downloadButton, QDialogButtonBox.ActionRole)
78        buttonBox.addButton(self.quitButton, QDialogButtonBox.RejectRole)
79
80        self.progressDialog = QProgressDialog(self)
81
82        self.urlLineEdit.textChanged.connect(self.enableDownloadButton)
83        self.qnam.authenticationRequired.connect(
84                self.slotAuthenticationRequired)
85        self.qnam.sslErrors.connect(self.sslErrors)
86        self.progressDialog.canceled.connect(self.cancelDownload)
87        self.downloadButton.clicked.connect(self.downloadFile)
88        self.quitButton.clicked.connect(self.close)
89
90        topLayout = QHBoxLayout()
91        topLayout.addWidget(urlLabel)
92        topLayout.addWidget(self.urlLineEdit)
93
94        mainLayout = QVBoxLayout()
95        mainLayout.addLayout(topLayout)
96        mainLayout.addWidget(self.statusLabel)
97        mainLayout.addWidget(buttonBox)
98        self.setLayout(mainLayout)
99
100        self.setWindowTitle("HTTP")
101        self.urlLineEdit.setFocus()
102
103    def startRequest(self, url):
104        self.reply = self.qnam.get(QNetworkRequest(url))
105        self.reply.finished.connect(self.httpFinished)
106        self.reply.readyRead.connect(self.httpReadyRead)
107        self.reply.downloadProgress.connect(self.updateDataReadProgress)
108
109    def downloadFile(self):
110        self.url = QUrl(self.urlLineEdit.text())
111        fileInfo = QFileInfo(self.url.path())
112        fileName = fileInfo.fileName()
113
114        if not fileName:
115            fileName = 'index.html'
116
117        if QFile.exists(fileName):
118            ret = QMessageBox.question(self, "HTTP",
119                    "There already exists a file called %s in the current "
120                    "directory. Overwrite?" % fileName,
121                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
122
123            if ret == QMessageBox.No:
124                return
125
126            QFile.remove(fileName)
127
128        self.outFile = QFile(fileName)
129        if not self.outFile.open(QIODevice.WriteOnly):
130            QMessageBox.information(self, "HTTP",
131                    "Unable to save the file %s: %s." % (fileName, self.outFile.errorString()))
132            self.outFile = None
133            return
134
135        self.progressDialog.setWindowTitle("HTTP")
136        self.progressDialog.setLabelText("Downloading %s." % fileName)
137        self.downloadButton.setEnabled(False)
138
139        self.httpRequestAborted = False
140        self.startRequest(self.url)
141
142    def cancelDownload(self):
143        self.statusLabel.setText("Download canceled.")
144        self.httpRequestAborted = True
145        if self.reply is not None:
146            self.reply.abort()
147        self.downloadButton.setEnabled(True)
148
149    def httpFinished(self):
150        if self.httpRequestAborted:
151            if self.outFile is not None:
152                self.outFile.close()
153                self.outFile.remove()
154                self.outFile = None
155
156            self.reply.deleteLater()
157            self.reply = None
158            self.progressDialog.hide()
159            return
160
161        self.progressDialog.hide()
162        self.outFile.flush()
163        self.outFile.close()
164
165        redirectionTarget = self.reply.attribute(QNetworkRequest.RedirectionTargetAttribute)
166
167        if self.reply.error():
168            self.outFile.remove()
169            QMessageBox.information(self, "HTTP",
170                    "Download failed: %s." % self.reply.errorString())
171            self.downloadButton.setEnabled(True)
172        elif redirectionTarget is not None:
173            newUrl = self.url.resolved(redirectionTarget)
174
175            ret = QMessageBox.question(self, "HTTP",
176                    "Redirect to %s?" % newUrl.toString(),
177                    QMessageBox.Yes | QMessageBox.No)
178
179            if ret == QMessageBox.Yes:
180                self.url = newUrl
181                self.reply.deleteLater()
182                self.reply = None
183                self.outFile.open(QIODevice.WriteOnly)
184                self.outFile.resize(0)
185                self.startRequest(self.url)
186                return
187        else:
188            fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName()
189            self.statusLabel.setText("Downloaded %s to %s." % (fileName, QDir.currentPath()))
190
191            self.downloadButton.setEnabled(True)
192
193        self.reply.deleteLater()
194        self.reply = None
195        self.outFile = None
196
197    def httpReadyRead(self):
198        if self.outFile is not None:
199            self.outFile.write(self.reply.readAll())
200
201    def updateDataReadProgress(self, bytesRead, totalBytes):
202        if self.httpRequestAborted:
203            return
204
205        self.progressDialog.setMaximum(totalBytes)
206        self.progressDialog.setValue(bytesRead)
207
208    def enableDownloadButton(self):
209        self.downloadButton.setEnabled(self.urlLineEdit.text() != '')
210
211    def slotAuthenticationRequired(self, authenticator):
212        import os
213        from PyQt5 import uic
214
215        ui = os.path.join(os.path.dirname(__file__), 'authenticationdialog.ui')
216        dlg = uic.loadUi(ui)
217        dlg.adjustSize()
218        dlg.siteDescription.setText("%s at %s" % (authenticator.realm(), self.url.host()))
219
220        dlg.userEdit.setText(self.url.userName())
221        dlg.passwordEdit.setText(self.url.password())
222
223        if dlg.exec_() == QDialog.Accepted:
224            authenticator.setUser(dlg.userEdit.text())
225            authenticator.setPassword(dlg.passwordEdit.text())
226
227    def sslErrors(self, reply, errors):
228        errorString = ", ".join([str(error.errorString()) for error in errors])
229
230        ret = QMessageBox.warning(self, "HTTP Example",
231                "One or more SSL errors has occurred: %s" % errorString,
232                QMessageBox.Ignore | QMessageBox.Abort)
233
234        if ret == QMessageBox.Ignore:
235            self.reply.ignoreSslErrors()
236
237
238if __name__ == '__main__':
239
240    import sys
241
242    app = QApplication(sys.argv)
243    httpWin = HttpWindow()
244    httpWin.show()
245    sys.exit(httpWin.exec_())
246