diff -r eaf2cf171f3a -r 5cf645f6daab eric6/Helpviewer/Download/DownloadItem.py --- a/eric6/Helpviewer/Download/DownloadItem.py Sat Sep 07 14:45:27 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,764 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing a widget controlling a download. -""" - -from __future__ import unicode_literals -try: - str = unicode -except NameError: - pass - -from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QTime, QFile, QFileInfo, \ - QUrl, QIODevice, QCryptographicHash, PYQT_VERSION_STR, QDateTime -from PyQt5.QtGui import QPalette, QDesktopServices -from PyQt5.QtWidgets import QWidget, QStyle, QDialog -from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply - -from E5Gui import E5FileDialog - -from .Ui_DownloadItem import Ui_DownloadItem - -from .DownloadUtilities import timeString, dataString -from ..HelpUtilities import parseContentDisposition - -import UI.PixmapCache -import Preferences - - -class DownloadItem(QWidget, Ui_DownloadItem): - """ - Class implementing a widget controlling a download. - - @signal statusChanged() emitted upon a status change of a download - @signal downloadFinished() emitted when a download finished - @signal progress(int, int) emitted to signal the download progress - """ - statusChanged = pyqtSignal() - downloadFinished = pyqtSignal() - progress = pyqtSignal(int, int) - - Downloading = 0 - DownloadSuccessful = 1 - DownloadCancelled = 2 - - def __init__(self, reply=None, requestFilename=False, webPage=None, - download=False, parent=None, mainWindow=None): - """ - Constructor - - @keyparam reply reference to the network reply object (QNetworkReply) - @keyparam requestFilename flag indicating to ask the user for a - filename (boolean) - @keyparam webPage reference to the web page object the download - originated from (QWebPage) - @keyparam download flag indicating a download operation (boolean) - @keyparam parent reference to the parent widget (QWidget) - @keyparam mainWindow reference to the main window (HelpWindow) - """ - super(DownloadItem, self).__init__(parent) - self.setupUi(self) - - p = self.infoLabel.palette() - p.setColor(QPalette.Text, Qt.darkGray) - self.infoLabel.setPalette(p) - - self.progressBar.setMaximum(0) - - self.__isFtpDownload = reply is not None and \ - reply.url().scheme() == "ftp" - - self.tryAgainButton.setIcon(UI.PixmapCache.getIcon("restart.png")) - self.tryAgainButton.setEnabled(False) - self.tryAgainButton.setVisible(False) - self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading.png")) - self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause.png")) - self.openButton.setIcon(UI.PixmapCache.getIcon("open.png")) - self.openButton.setEnabled(False) - self.openButton.setVisible(False) - if self.__isFtpDownload: - self.stopButton.setEnabled(False) - self.stopButton.setVisible(False) - self.pauseButton.setEnabled(False) - self.pauseButton.setVisible(False) - - self.__state = DownloadItem.Downloading - - icon = self.style().standardIcon(QStyle.SP_FileIcon) - self.fileIcon.setPixmap(icon.pixmap(48, 48)) - - self.__mainWindow = mainWindow - self.__reply = reply - self.__requestFilename = requestFilename - self.__page = webPage - self.__pageUrl = webPage and webPage.mainFrame().url() or QUrl() - self.__toDownload = download - self.__bytesReceived = 0 - self.__bytesTotal = -1 - self.__downloadTime = QTime() - self.__output = QFile() - self.__fileName = "" - self.__originalFileName = "" - self.__startedSaving = False - self.__finishedDownloading = False - self.__gettingFileName = False - self.__canceledFileSelect = False - self.__autoOpen = False - self.__downloadedDateTime = QDateTime() - - self.__sha1Hash = QCryptographicHash(QCryptographicHash.Sha1) - self.__md5Hash = QCryptographicHash(QCryptographicHash.Md5) - - if not requestFilename: - self.__requestFilename = \ - Preferences.getUI("RequestDownloadFilename") - - self.__initialize() - - def __initialize(self, tryAgain=False): - """ - Private method to (re)initialize the widget. - - @param tryAgain flag indicating a retry (boolean) - """ - if self.__reply is None: - return - - self.__startedSaving = False - self.__finishedDownloading = False - self.__bytesReceived = 0 - self.__bytesTotal = -1 - - self.__sha1Hash.reset() - self.__md5Hash.reset() - - # start timer for the download estimation - self.__downloadTime.start() - - # attach to the reply object - self.__url = self.__reply.url() - self.__reply.setParent(self) - self.__reply.setReadBufferSize(16 * 1024 * 1024) - self.__reply.readyRead.connect(self.__readyRead) - self.__reply.error.connect(self.__networkError) - self.__reply.downloadProgress.connect(self.__downloadProgress) - self.__reply.metaDataChanged.connect(self.__metaDataChanged) - self.__reply.finished.connect(self.__finished) - - # reset info - self.datetimeLabel.clear() - self.datetimeLabel.hide() - self.infoLabel.clear() - self.progressBar.setValue(0) - self.__getFileName() - - if self.__reply.error() != QNetworkReply.NoError: - self.__networkError() - self.__finished() - - def __getFileName(self): - """ - Private method to get the file name to save to from the user. - """ - if self.__gettingFileName: - return - - import Helpviewer.HelpWindow - downloadDirectory = Helpviewer.HelpWindow.HelpWindow\ - .downloadManager().downloadDirectory() - - if self.__fileName: - fileName = self.__fileName - originalFileName = self.__originalFileName - self.__toDownload = True - ask = False - else: - defaultFileName, originalFileName = \ - self.__saveFileName(downloadDirectory) - fileName = defaultFileName - self.__originalFileName = originalFileName - ask = True - self.__autoOpen = False - if not self.__toDownload: - from .DownloadAskActionDialog import DownloadAskActionDialog - url = self.__reply.url() - dlg = DownloadAskActionDialog( - QFileInfo(originalFileName).fileName(), - self.__reply.header(QNetworkRequest.ContentTypeHeader), - "{0}://{1}".format(url.scheme(), url.authority()), - self) - if dlg.exec_() == QDialog.Rejected or dlg.getAction() == "cancel": - self.progressBar.setVisible(False) - self.__reply.close() - self.on_stopButton_clicked() - self.filenameLabel.setText( - self.tr("Download canceled: {0}").format( - QFileInfo(defaultFileName).fileName())) - self.__canceledFileSelect = True - self.__setDateTime() - return - - if dlg.getAction() == "scan": - self.__mainWindow.requestVirusTotalScan(url) - - self.progressBar.setVisible(False) - self.__reply.close() - self.on_stopButton_clicked() - self.filenameLabel.setText( - self.tr("VirusTotal scan scheduled: {0}").format( - QFileInfo(defaultFileName).fileName())) - self.__canceledFileSelect = True - return - - self.__autoOpen = dlg.getAction() == "open" - if PYQT_VERSION_STR >= "5.0.0": - from PyQt5.QtCore import QStandardPaths - tempLocation = QStandardPaths.standardLocations( - QStandardPaths.TempLocation)[0] - else: - from PyQt5.QtGui import QDesktopServices - tempLocation = QDesktopServices.storageLocation( - QDesktopServices.TempLocation) - fileName = tempLocation + '/' + \ - QFileInfo(fileName).completeBaseName() - - if ask and not self.__autoOpen and self.__requestFilename: - self.__gettingFileName = True - fileName = E5FileDialog.getSaveFileName( - None, - self.tr("Save File"), - defaultFileName, - "") - self.__gettingFileName = False - if not fileName: - self.progressBar.setVisible(False) - self.__reply.close() - self.on_stopButton_clicked() - self.filenameLabel.setText( - self.tr("Download canceled: {0}") - .format(QFileInfo(defaultFileName).fileName())) - self.__canceledFileSelect = True - self.__setDateTime() - return - - fileInfo = QFileInfo(fileName) - Helpviewer.HelpWindow.HelpWindow.downloadManager()\ - .setDownloadDirectory(fileInfo.absoluteDir().absolutePath()) - self.filenameLabel.setText(fileInfo.fileName()) - - self.__output.setFileName(fileName + ".part") - self.__fileName = fileName - - # check file path for saving - saveDirPath = QFileInfo(self.__fileName).dir() - if not saveDirPath.exists(): - if not saveDirPath.mkpath(saveDirPath.absolutePath()): - self.progressBar.setVisible(False) - self.on_stopButton_clicked() - self.infoLabel.setText(self.tr( - "Download directory ({0}) couldn't be created.") - .format(saveDirPath.absolutePath())) - self.__setDateTime() - return - - self.filenameLabel.setText(QFileInfo(self.__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 and original filename (string, string) - """ - path = parseContentDisposition(self.__reply) - info = QFileInfo(path) - baseName = info.completeBaseName() - endName = info.suffix() - - origName = baseName - if endName: - origName += '.' + endName - - name = directory + baseName - if endName: - name += '.' + endName - if not self.__requestFilename: - # do not overwrite, if the user is not being asked - i = 1 - while QFile.exists(name): - # file exists already, don't overwrite - name = directory + baseName + ('-{0:d}'.format(i)) - if endName: - name += '.' + endName - i += 1 - return name, origName - - @pyqtSlot() - def on_tryAgainButton_clicked(self): - """ - Private slot to retry the download. - """ - self.retry() - - def retry(self): - """ - Public slot to retry the download. - """ - if not self.tryAgainButton.isEnabled(): - return - - self.tryAgainButton.setEnabled(False) - self.tryAgainButton.setVisible(False) - self.openButton.setEnabled(False) - self.openButton.setVisible(False) - if not self.__isFtpDownload: - self.stopButton.setEnabled(True) - self.stopButton.setVisible(True) - self.pauseButton.setEnabled(True) - self.pauseButton.setVisible(True) - self.progressBar.setVisible(True) - - if self.__page: - nam = self.__page.networkAccessManager() - else: - import Helpviewer.HelpWindow - nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() - reply = nam.get(QNetworkRequest(self.__url)) - if self.__output.exists(): - self.__output.remove() - self.__output = QFile() - self.__reply = reply - self.__initialize(tryAgain=True) - self.__state = DownloadItem.Downloading - self.statusChanged.emit() - - @pyqtSlot(bool) - def on_pauseButton_clicked(self, checked): - """ - Private slot to pause the download. - - @param checked flag indicating the state of the button (boolean) - """ - if checked: - self.__reply.readyRead.disconnect(self.__readyRead) - self.__reply.setReadBufferSize(16 * 1024) - else: - self.__reply.readyRead.connect(self.__readyRead) - self.__reply.setReadBufferSize(16 * 1024 * 1024) - self.__readyRead() - - @pyqtSlot() - def on_stopButton_clicked(self): - """ - Private slot to stop the download. - """ - self.cancelDownload() - - def cancelDownload(self): - """ - Public slot to stop the download. - """ - self.setUpdatesEnabled(False) - if not self.__isFtpDownload: - self.stopButton.setEnabled(False) - self.stopButton.setVisible(False) - self.pauseButton.setEnabled(False) - self.pauseButton.setVisible(False) - self.tryAgainButton.setEnabled(True) - self.tryAgainButton.setVisible(True) - self.openButton.setEnabled(False) - self.openButton.setVisible(False) - self.setUpdatesEnabled(True) - self.__state = DownloadItem.DownloadCancelled - self.__reply.abort() - self.__setDateTime() - self.downloadFinished.emit() - - @pyqtSlot() - def on_openButton_clicked(self): - """ - Private slot to open the downloaded file. - """ - self.openFile() - - def openFile(self): - """ - Public slot to open the downloaded file. - """ - info = QFileInfo(self.__fileName) - url = QUrl.fromLocalFile(info.absoluteFilePath()) - QDesktopServices.openUrl(url) - - def openFolder(self): - """ - Public slot to open the folder containing the downloaded file. - """ - info = QFileInfo(self.__fileName) - url = QUrl.fromLocalFile(info.absolutePath()) - QDesktopServices.openUrl(url) - - 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.tr("Error opening save file: {0}") - .format(self.__output.errorString())) - self.on_stopButton_clicked() - self.statusChanged.emit() - return - self.statusChanged.emit() - - buffer = self.__reply.readAll() - self.__sha1Hash.addData(buffer) - self.__md5Hash.addData(buffer) - bytesWritten = self.__output.write(buffer) - if bytesWritten == -1: - self.infoLabel.setText( - self.tr("Error saving: {0}") - .format(self.__output.errorString())) - self.on_stopButton_clicked() - else: - self.__startedSaving = True - if self.__finishedDownloading: - self.__finished() - - def __networkError(self): - """ - Private slot to handle a network error. - """ - self.infoLabel.setText( - self.tr("Network Error: {0}") - .format(self.__reply.errorString())) - self.tryAgainButton.setEnabled(True) - self.tryAgainButton.setVisible(True) - self.downloadFinished.emit() - - def __metaDataChanged(self): - """ - Private slot to handle a change of the meta data. - """ - locationHeader = self.__reply.header(QNetworkRequest.LocationHeader) - if locationHeader and locationHeader.isValid(): - self.__url = QUrl(locationHeader) - import Helpviewer.HelpWindow - self.__reply = Helpviewer.HelpWindow.HelpWindow\ - .networkAccessManager().get(QNetworkRequest(self.__url)) - self.__initialize() - - def __downloadProgress(self, bytesReceived, bytesTotal): - """ - Private method to show the download progress. - - @param bytesReceived number of bytes received (integer) - @param bytesTotal number of total bytes (integer) - """ - self.__bytesReceived = bytesReceived - self.__bytesTotal = bytesTotal - currentValue = 0 - totalValue = 0 - if bytesTotal > 0: - currentValue = bytesReceived * 100 / bytesTotal - totalValue = 100 - self.progressBar.setValue(currentValue) - self.progressBar.setMaximum(totalValue) - - self.progress.emit(currentValue, totalValue) - self.__updateInfoLabel() - - def bytesTotal(self): - """ - Public method to get the total number of bytes of the download. - - @return total number of bytes (integer) - """ - if self.__bytesTotal == -1: - self.__bytesTotal = self.__reply.header( - QNetworkRequest.ContentLengthHeader) - if self.__bytesTotal is None: - self.__bytesTotal = -1 - return self.__bytesTotal - - def bytesReceived(self): - """ - Public method to get the number of bytes received. - - @return number of bytes received (integer) - """ - return self.__bytesReceived - - def remainingTime(self): - """ - Public method to get an estimation for the remaining time. - - @return estimation for the remaining time (float) - """ - if not self.downloading(): - return -1.0 - - if self.bytesTotal() == -1: - return -1.0 - - cSpeed = self.currentSpeed() - if cSpeed != 0: - timeRemaining = (self.bytesTotal() - self.bytesReceived()) / cSpeed - else: - timeRemaining = 1 - - # ETA should never be 0 - if timeRemaining == 0: - timeRemaining = 1 - - return timeRemaining - - def currentSpeed(self): - """ - Public method to get an estimation for the download speed. - - @return estimation for the download speed (float) - """ - if not self.downloading(): - return -1.0 - - return self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed() - - def __updateInfoLabel(self): - """ - Private method to update the info label. - """ - if self.__reply.error() != QNetworkReply.NoError: - return - - bytesTotal = self.bytesTotal() - running = not self.downloadedSuccessfully() - - speed = self.currentSpeed() - timeRemaining = self.remainingTime() - - info = "" - if running: - remaining = "" - - if bytesTotal > 0: - remaining = timeString(timeRemaining) - - info = self.tr("{0} of {1} ({2}/sec)\n{3}")\ - .format( - dataString(self.__bytesReceived), - bytesTotal == -1 and self.tr("?") or - dataString(bytesTotal), - dataString(int(speed)), - remaining) - else: - if self.__bytesReceived == bytesTotal or bytesTotal == -1: - info = self.tr("{0} downloaded\nSHA1: {1}\nMD5: {2}")\ - .format(dataString(self.__output.size()), - str(self.__sha1Hash.result().toHex(), - encoding="ascii"), - str(self.__md5Hash.result().toHex(), - encoding="ascii") - ) - else: - info = self.tr("{0} of {1} - Stopped")\ - .format(dataString(self.__bytesReceived), - dataString(bytesTotal)) - self.infoLabel.setText(info) - - def downloading(self): - """ - Public method to determine, if a download is in progress. - - @return flag indicating a download is in progress (boolean) - """ - return self.__state == DownloadItem.Downloading - - def downloadedSuccessfully(self): - """ - Public method to check for a successful download. - - @return flag indicating a successful download (boolean) - """ - return self.__state == DownloadItem.DownloadSuccessful - - def downloadCanceled(self): - """ - Public method to check, if the download was cancelled. - - @return flag indicating a canceled download (boolean) - """ - return self.__state == DownloadItem.DownloadCancelled - - def __finished(self): - """ - Private slot to handle the download finished. - """ - self.__finishedDownloading = True - if not self.__startedSaving: - return - - noError = self.__reply.error() == QNetworkReply.NoError - - self.progressBar.setVisible(False) - if not self.__isFtpDownload: - self.stopButton.setEnabled(False) - self.stopButton.setVisible(False) - self.pauseButton.setEnabled(False) - self.pauseButton.setVisible(False) - self.openButton.setEnabled(noError) - self.openButton.setVisible(noError) - self.__output.close() - if QFile.exists(self.__fileName): - QFile.remove(self.__fileName) - self.__output.rename(self.__fileName) - self.__state = DownloadItem.DownloadSuccessful - self.__updateInfoLabel() - self.__setDateTime() - - self.__adjustSize() - - self.statusChanged.emit() - self.downloadFinished.emit() - - if self.__autoOpen: - self.openFile() - - def canceledFileSelect(self): - """ - Public method to check, if the user canceled the file selection. - - @return flag indicating cancellation (boolean) - """ - return self.__canceledFileSelect - - def setIcon(self, icon): - """ - Public method to set the download icon. - - @param icon reference to the icon to be set (QIcon) - """ - self.fileIcon.setPixmap(icon.pixmap(48, 48)) - - def fileName(self): - """ - Public method to get the name of the output file. - - @return name of the output file (string) - """ - return self.__fileName - - def absoluteFilePath(self): - """ - Public method to get the absolute path of the output file. - - @return absolute path of the output file (string) - """ - return QFileInfo(self.__fileName).absoluteFilePath() - - def getData(self): - """ - Public method to get the relevant download data. - - @return tuple of URL, save location, flag, the - URL of the related web page and the date and time - of the download - @rtype tuple of (QUrl, str, bool, QUrl, QDateTime) - """ - return (self.__url, QFileInfo(self.__fileName).filePath(), - self.downloadedSuccessfully(), self.__pageUrl, - self.__downloadedDateTime) - - def setData(self, data): - """ - Public method to set the relevant download data. - - @param data tuple of URL, save location, flag, the - URL of the related web page and the date and time - of the download - @type QUrl, str, bool, QUrl, QDateTime - """ - self.__url = data[0] - self.__fileName = data[1] - self.__pageUrl = data[3] - self.__isFtpDownload = self.__url.scheme() == "ftp" - - self.filenameLabel.setText(QFileInfo(self.__fileName).fileName()) - self.infoLabel.setText(self.__fileName) - - if len(data) == 5: - self.__setDateTime(data[4]) - else: - self.__setDateTime(QDateTime()) - - self.stopButton.setEnabled(False) - self.stopButton.setVisible(False) - self.pauseButton.setEnabled(False) - self.pauseButton.setVisible(False) - self.openButton.setEnabled(data[2]) - self.openButton.setVisible(data[2]) - self.tryAgainButton.setEnabled(not data[2]) - self.tryAgainButton.setVisible(not data[2]) - if data[2]: - self.__state = DownloadItem.DownloadSuccessful - else: - self.__state = DownloadItem.DownloadCancelled - self.progressBar.setVisible(False) - - self.__adjustSize() - - def getInfoData(self): - """ - Public method to get the text of the info label. - - @return text of the info label (string) - """ - return self.infoLabel.text() - - def getPageUrl(self): - """ - Public method to get the URL of the download page. - - @return URL of the download page (QUrl) - """ - return self.__pageUrl - - def __adjustSize(self): - """ - Private method to adjust the size of the download item. - """ - self.ensurePolished() - - msh = self.minimumSizeHint() - self.resize(max(self.width(), msh.width()), msh.height()) - - def __setDateTime(self, dateTime=None): - """ - Private method to set the download date and time. - - @param dateTime date and time to be set - @type QDateTime - """ - if dateTime is None: - self.__downloadedDateTime = QDateTime.currentDateTime() - else: - self.__downloadedDateTime = dateTime - if self.__downloadedDateTime.isValid(): - labelText = self.__downloadedDateTime.toString("yyyy-MM-dd hh:mm") - self.datetimeLabel.setText(labelText) - self.datetimeLabel.show() - else: - self.datetimeLabel.clear() - self.datetimeLabel.hide()