--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Helpviewer/DownloadDialog.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,420 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2008 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the download dialog. +""" + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from PyQt4.QtNetwork import QNetworkReply, QNetworkAccessManager, QNetworkRequest + +import Preferences + +import Helpviewer.HelpWindow + +from Ui_DownloadDialog import Ui_DownloadDialog + +class DownloadDialog(QWidget, Ui_DownloadDialog): + """ + Class implementing the download dialog. + + @signal done() emitted just before the dialog is closed + """ + done = pyqtSignal() + + def __init__(self, reply = None, requestFilename = False, webPage = None, + download = False, parent = None): + """ + Constructor + + @param reply reference to the network reply object (QNetworkReply) + @param requestFilename flag indicating to ask the user for a filename (boolean) + @param webPage reference to the web page object the download originated + from (QWebPage) + @param download flag indicating a download operation (boolean) + @param parent reference to the parent widget (QWidget) + """ + QWidget.__init__(self, parent) + self.setupUi(self) + self.setAttribute(Qt.WA_DeleteOnClose) + + self.__tryAgainButton = \ + self.buttonBox.addButton(self.trUtf8("Try Again"), + QDialogButtonBox.ActionRole) + self.__stopButton = \ + self.buttonBox.addButton(self.trUtf8("Stop"), QDialogButtonBox.ActionRole) + self.__openButton = self.buttonBox.button(QDialogButtonBox.Open) + self.__closeButton = self.buttonBox.button(QDialogButtonBox.Close) + + self.__tryAgainButton.setEnabled(False) + self.__closeButton.setEnabled(False) + self.__openButton.setEnabled(False) + self.keepOpenCheckBox.setChecked(True) + + icon = self.style().standardIcon(QStyle.SP_FileIcon) + self.fileIcon.setPixmap(icon.pixmap(48, 48)) + + self.__reply = reply + self.__requestFilename = requestFilename + self.__page = webPage + self.__toDownload = download + self.__bytesReceived = 0 + self.__downloadTime = QTime() + self.__output = QFile() + + self.__initialize() + + def __initialize(self): + """ + Private method to (re)initialize the dialog. + """ + if self.__reply is None: + return + + self.__startedSaving = False + self.__downloadFinished = False + + self.__url = self.__reply.url() + self.__reply.setParent(self) + self.connect(self.__reply, SIGNAL("readyRead()"), self.__readyRead) + self.connect(self.__reply, SIGNAL("error(QNetworkReply::NetworkError)"), + self.__networkError) + self.connect(self.__reply, SIGNAL("downloadProgress(qint64, qint64)"), + self.__downloadProgress) + self.connect(self.__reply, SIGNAL("metaDataChanged()"), + self.__metaDataChanged) + self.connect(self.__reply, SIGNAL("finished()"), self.__finished) + + # reset info + self.infoLabel.clear() + self.progressBar.setValue(0) + self.__getFileName() + + # start timer for the download estimation + self.__downloadTime.start() + + if self.__reply.error() != QNetworkReply.NoError: + self.__networkError() + self.__finished() + + def __getFileName(self): + """ + Private method to get the filename to save to from the user. + """ + downloadDirectory = Preferences.getUI("DownloadPath") + if not downloadDirectory: + downloadDirectory = \ + QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation) + if downloadDirectory: + downloadDirectory += '/' + + defaultFileName = self.__saveFileName(downloadDirectory) + fileName = defaultFileName + baseName = QFileInfo(fileName).completeBaseName() + self.__autoOpen = False + if not self.__toDownload: + res = QMessageBox.question(None, + self.trUtf8("Downloading"), + self.trUtf8("""<p>You are about to download the file <b>{0}</b>.</p>""" + """<p>What do you want to do?</p>""").format(baseName), + QMessageBox.StandardButtons(\ + QMessageBox.Open | \ + QMessageBox.Save)) + self.__autoOpen = res == QMessageBox.Open + fileName = QDesktopServices.storageLocation(QDesktopServices.TempLocation) + \ + '/' + baseName + + if not self.__autoOpen and self.__requestFilename: + fileName = QFileDialog.getSaveFileName( + None, + self.trUtf8("Save File"), + defaultFileName, + "") + if not fileName: + self.__reply.close() + if not self.keepOpenCheckBox.isChecked(): + self.close() + else: + self.filenameLabel.setText(self.trUtf8("Download canceled: {0}")\ + .format(QFileInfo(defaultFileName).fileName())) + self.__stop() + return + + self.__output.setFileName(fileName) + self.filenameLabel.setText(QFileInfo(self.__output.fileName()).fileName()) + if self.__requestFilename: + self.__readyRead() + + def __saveFileName(self, directory): + """ + Private method to calculate a name for the file to download. + + @param directory name of the directory to store the file into (string) + @return proposed filename (string) + """ + path = "" + if self.__reply.hasRawHeader("Content-Disposition"): + header = unicode(self.__reply.rawHeader("Content-Disposition")) + if header: + pos = header.find("filename=") + if pos != -1: + path = header[pos + 9:] + if path.startswith('"') and path.endswith('"'): + path = path[1:-1] + if not path: + path = self.__url.path() + + info = QFileInfo(path) + baseName = info.completeBaseName() + endName = info.suffix() + + if not baseName: + baseName = "unnamed_download" + + name = directory + baseName + if endName: + name += '.' + endName + i = 1 + while QFile.exists(name): + # file exists already, don't overwrite + name = directory + baseName + ('-%d' % i) + if endName: + name += '.' + endName + i += 1 + return name + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked (QAbstractButton) + """ + if button == self.__closeButton: + self.close() + elif button == self.__openButton: + self.__open() + elif button == self.__stopButton: + self.__stop() + elif button == self.__tryAgainButton: + self.__tryAgain() + + def __stop(self): + """ + Private slot to stop the download. + """ + self.__stopButton.setEnabled(False) + self.__closeButton.setEnabled(True) + self.__tryAgainButton.setEnabled(True) + self.__reply.abort() + + def __open(self): + """ + Private slot to open the downloaded file. + """ + info = QFileInfo(self.__output) + url = QUrl.fromLocalFile(info.absoluteFilePath()) + QDesktopServices.openUrl(url) + + def __tryAgain(self): + """ + Private slot to retry the download. + """ + self.__tryAgainButton.setEnabled(False) + self.__closeButton.setEnabled(False) + self.__stopButton.setEnabled(True) + + if self.__page: + nam = self.__page.networkAccessManager() + else: + nam = QNetworkAccessManager() + reply = nam.get(QNetworkRequest(self.__url)) + if self.__reply: + self.__reply.deleteLater() + if self.__output.exists(): + self.__output.remove() + self.__reply = reply + self.__initialize() + + def __readyRead(self): + """ + Private slot to read the available data. + """ + if self.__requestFilename and not self.__output.fileName(): + return + + if not self.__output.isOpen(): + # in case someone else has already put a file there + if not self.__requestFilename: + self.__getFileName() + if not self.__output.open(QIODevice.WriteOnly): + self.infoLabel.setText(self.trUtf8("Error opening save file: {0}")\ + .format(self.__output.errorString())) + self.__stopButton.click() + return + + bytesWritten = self.__output.write(self.__reply.readAll()) + if bytesWritten == -1: + self.infoLabel.setText(self.trUtf8("Error saving: {0}")\ + .format(self.__output.errorString())) + self.__stopButton.click() + else: + size = self.__reply.header(QNetworkRequest.ContentLengthHeader).toInt()[0] + if size == bytesWritten: + self.__downloadProgress(size, size) + self.__downloadFinished = True + self.__startedSaving = True + if self.__downloadFinished: + self.__finished() + + def __networkError(self): + """ + Private slot to handle a network error. + """ + if self.__reply.error() != QNetworkReply.OperationCanceledError: + self.infoLabel.setText(self.trUtf8("Network Error: {0}")\ + .format(self.__reply.errorString())) + self.__tryAgainButton.setEnabled(True) + self.__closeButton.setEnabled(True) + self.__openButton.setEnabled(False) + + def __metaDataChanged(self): + """ + Private slot to handle a change of the meta data. + """ + locationHeader = self.__reply.header(QNetworkRequest.LocationHeader) + if locationHeader.isValid(): + self.__url = locationHeader.toUrl() + self.__reply.deleteLater() + self.__reply = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().get( + QNetworkRequest(self.__url)) + self.__initialize() + + def __downloadProgress(self, received, total): + """ + Private method show the download progress. + + @param received number of bytes received (integer) + @param total number of total bytes (integer) + """ + self.__bytesReceived = received + if total == -1: + self.progressBar.setValue(0) + self.progressBar.setMaximum(0) + else: + self.progressBar.setValue(received) + self.progressBar.setMaximum(total) + self.__updateInfoLabel() + + def __updateInfoLabel(self): + """ + Private method to update the info label. + """ + if self.__reply.error() != QNetworkReply.NoError and \ + self.__reply.error() != QNetworkReply.OperationCanceledError: + return + + bytesTotal = self.progressBar.maximum() + running = not self.__downloadedSuccessfully() + + info = "" + if self.__downloading(): + remaining = "" + speed = self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed() + if bytesTotal != 0: + timeRemaining = int((bytesTotal - self.__bytesReceived) / speed) + + if timeRemaining > 60: + minutes = int(timeRemaining / 60) + seconds = int(timeRemaining % 60) + remaining = self.trUtf8("- {0}:{1:02} minutes remaining")\ + .format(minutes, seconds) + else: + # when downloading, the eta should never be 0 + if timeRemaining == 0: + timeRemaining = 1 + + remaining = self.trUtf8("- {0} seconds remaining")\ + .format(timeRemaining) + info = self.trUtf8("{0} of {1} ({2}/sec) {3}")\ + .format( + self.__dataString(self.__bytesReceived), + bytesTotal == 0 and self.trUtf8("?") \ + or self.__dataString(bytesTotal), + self.__dataString(int(speed)), + remaining) + else: + if self.__bytesReceived == bytesTotal: + info = self.trUtf8("{0} downloaded")\ + .format(self.__dataString(self.__output.size())) + else: + info = self.trUtf8("{0} of {1} - Stopped")\ + .format(self.__dataString(self.__bytesReceived), + self.__dataString(bytesTotal)) + self.infoLabel.setText(info) + + def __dataString(self, size): + """ + Private method to generate a formatted data string. + + @param size size to be formatted (integer) + @return formatted data string (QString) + """ + unit = "" + if size < 1024: + unit = self.trUtf8("bytes") + elif size < 1024 * 1024: + size /= 1024 + unit = self.trUtf8("kB") + else: + size /= 1024 * 1024 + unit = self.trUtf8("MB") + return "{0} {1}".format(size, unit) + + def __downloading(self): + """ + Private method to determine, if a download is in progress. + + @return flag indicating a download is in progress (boolean) + """ + return self.__stopButton.isEnabled() + + def __downloadedSuccessfully(self): + """ + Private method to determine the download status. + + @return download status (boolean) + """ + return (not self.__stopButton.isEnabled() and \ + not self.__tryAgainButton.isEnabled()) + + def __finished(self): + """ + Private slot to handle the download finished. + """ + self.__downloadFinished = True + if not self.__startedSaving: + return + + self.__stopButton.setEnabled(False) + self.__closeButton.setEnabled(True) + self.__openButton.setEnabled(True) + self.__output.close() + self.__updateInfoLabel() + + if not self.keepOpenCheckBox.isChecked() and \ + self.__reply.error() == QNetworkReply.NoError: + self.close() + + if self.__autoOpen: + self.__open() + + def closeEvent(self, evt): + """ + Protected method called when the dialog is closed. + """ + self.__output.close() + self.done.emit()