src/eric7/WebBrowser/Download/DownloadItem.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/WebBrowser/Download/DownloadItem.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,634 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget controlling a download.
+"""
+
+import enum
+import os
+import pathlib
+
+from PyQt6.QtCore import (
+    pyqtSlot, pyqtSignal, QTime, QUrl, QStandardPaths, QDateTime
+)
+from PyQt6.QtGui import QDesktopServices
+from PyQt6.QtWidgets import QWidget, QStyle, QDialog
+from PyQt6.QtWebEngineCore import QWebEngineDownloadRequest
+
+from EricWidgets import EricFileDialog
+from EricWidgets.EricApplication import ericApp
+
+from .Ui_DownloadItem import Ui_DownloadItem
+
+from .DownloadUtilities import timeString, dataString, speedString
+from WebBrowser.WebBrowserWindow import WebBrowserWindow
+
+import UI.PixmapCache
+import Utilities.MimeTypes
+
+
+class DownloadState(enum.Enum):
+    """
+    Class implementing the various download states.
+    """
+    Downloading = 0
+    Successful = 1
+    Cancelled = 2
+
+
+class DownloadItem(QWidget, Ui_DownloadItem):
+    """
+    Class implementing a widget controlling a download.
+    
+    @signal statusChanged() emitted upon a status change of a download
+    @signal downloadFinished(success) emitted when a download finished
+    @signal progress(int, int) emitted to signal the download progress
+    """
+    statusChanged = pyqtSignal()
+    downloadFinished = pyqtSignal(bool)
+    progress = pyqtSignal(int, int)
+    
+    def __init__(self, downloadRequest=None, pageUrl=None, parent=None):
+        """
+        Constructor
+        
+        @param downloadRequest reference to the download object containing the
+        download data.
+        @type QWebEngineDownloadRequest
+        @param pageUrl URL of the calling page
+        @type QUrl
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.fileIcon.setStyleSheet("background-color: transparent")
+        self.datetimeLabel.setStyleSheet("background-color: transparent")
+        self.filenameLabel.setStyleSheet("background-color: transparent")
+        if ericApp().usesDarkPalette():
+            self.infoLabel.setStyleSheet(
+                "color: #c0c0c0; background-color: transparent"
+            )      # light gray
+        else:
+            self.infoLabel.setStyleSheet(
+                "color: #808080; background-color: transparent"
+            )      # dark gray
+        
+        self.progressBar.setMaximum(0)
+        
+        self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause"))
+        self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading"))
+        self.openButton.setIcon(UI.PixmapCache.getIcon("open"))
+        self.openButton.setEnabled(False)
+        self.openButton.setVisible(False)
+        
+        self.__state = DownloadState.Downloading
+        
+        icon = self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon)
+        self.fileIcon.setPixmap(icon.pixmap(48, 48))
+        
+        self.__downloadRequest = downloadRequest
+        if pageUrl is None:
+            self.__pageUrl = QUrl()
+        else:
+            self.__pageUrl = pageUrl
+        self.__bytesReceived = 0
+        self.__bytesTotal = -1
+        self.__downloadTime = QTime()
+        self.__fileName = ""
+        self.__originalFileName = ""
+        self.__finishedDownloading = False
+        self.__gettingFileName = False
+        self.__canceledFileSelect = False
+        self.__autoOpen = False
+        self.__downloadedDateTime = QDateTime()
+        
+        self.__initialize()
+    
+    def __initialize(self):
+        """
+        Private method to initialize the widget.
+        """
+        if self.__downloadRequest is None:
+            return
+        
+        self.__finishedDownloading = False
+        self.__bytesReceived = 0
+        self.__bytesTotal = -1
+        
+        # start timer for the download estimation
+        self.__downloadTime = QTime.currentTime()
+        
+        # attach to the download item object
+        self.__url = self.__downloadRequest.url()
+        self.__downloadRequest.receivedBytesChanged.connect(
+            self.__downloadProgress)
+        self.__downloadRequest.isFinishedChanged.connect(self.__finished)
+        
+        # reset info
+        self.datetimeLabel.clear()
+        self.datetimeLabel.hide()
+        self.infoLabel.clear()
+        self.progressBar.setValue(0)
+        if (
+            self.__downloadRequest.state() ==
+            QWebEngineDownloadRequest.DownloadState.DownloadRequested
+        ):
+            self.__getFileName()
+            if not self.__fileName:
+                self.__downloadRequest.cancel()
+            else:
+                self.__downloadRequest.setDownloadFileName(self.__fileName)
+                self.__downloadRequest.accept()
+        else:
+            fileName = self.__downloadRequest.downloadFileName()
+            self.__setFileName(fileName)
+    
+    def __getFileName(self):
+        """
+        Private method to get the file name to save to from the user.
+        """
+        if self.__gettingFileName:
+            return
+        
+        savePage = self.__downloadRequest.isSavePageDownload()
+        
+        documentLocation = QStandardPaths.writableLocation(
+            QStandardPaths.StandardLocation.DocumentsLocation)
+        downloadDirectory = (
+            WebBrowserWindow.downloadManager().downloadDirectory()
+        )
+        
+        if self.__fileName:
+            fileName = self.__fileName
+            originalFileName = self.__originalFileName
+            self.__toDownload = True
+            ask = False
+        else:
+            defaultFileName, originalFileName = self.__saveFileName(
+                documentLocation if savePage else downloadDirectory)
+            fileName = defaultFileName
+            self.__originalFileName = originalFileName
+            ask = True
+        self.__autoOpen = False
+        
+        if not savePage:
+            from .DownloadAskActionDialog import DownloadAskActionDialog
+            url = self.__downloadRequest.url()
+            mimetype = Utilities.MimeTypes.mimeType(originalFileName)
+            dlg = DownloadAskActionDialog(
+                pathlib.Path(originalFileName).name,
+                mimetype,
+                "{0}://{1}".format(url.scheme(), url.authority()),
+                self)
+            
+            if (
+                dlg.exec() == QDialog.DialogCode.Rejected or
+                dlg.getAction() == "cancel"
+            ):
+                self.progressBar.setVisible(False)
+                self.on_stopButton_clicked()
+                self.filenameLabel.setText(
+                    self.tr("Download canceled: {0}").format(
+                        pathlib.Path(defaultFileName).name))
+                self.__canceledFileSelect = True
+                self.__setDateTime()
+                return
+            
+            if dlg.getAction() == "scan":
+                self.__mainWindow.requestVirusTotalScan(url)
+                
+                self.progressBar.setVisible(False)
+                self.on_stopButton_clicked()
+                self.filenameLabel.setText(
+                    self.tr("VirusTotal scan scheduled: {0}").format(
+                        pathlib.Path(defaultFileName).name))
+                self.__canceledFileSelect = True
+                return
+        
+            self.__autoOpen = dlg.getAction() == "open"
+            
+            tempLocation = QStandardPaths.writableLocation(
+                QStandardPaths.StandardLocation.TempLocation)
+            fileName = (
+                tempLocation + '/' +
+                pathlib.Path(fileName).stem
+            )
+            
+            if ask and not self.__autoOpen:
+                self.__gettingFileName = True
+                fileName = EricFileDialog.getSaveFileName(
+                    None,
+                    self.tr("Save File"),
+                    defaultFileName,
+                    "")
+                self.__gettingFileName = False
+        
+        if not fileName:
+            self.progressBar.setVisible(False)
+            self.on_stopButton_clicked()
+            self.filenameLabel.setText(
+                self.tr("Download canceled: {0}")
+                    .format(pathlib.Path(defaultFileName).name))
+            self.__canceledFileSelect = True
+            self.__setDateTime()
+            return
+        
+        self.__setFileName(fileName)
+    
+    def __setFileName(self, fileName):
+        """
+        Private method to set the file name to save the download into.
+        
+        @param fileName name of the file to save into
+        @type str
+        """
+        fpath = pathlib.Path(fileName)
+        WebBrowserWindow.downloadManager().setDownloadDirectory(
+            fpath.parent.resolve())
+        self.filenameLabel.setText(fpath.name)
+        
+        self.__fileName = str(fpath)
+        
+        # check file path for saving
+        saveDirPath = pathlib.Path(self.__fileName).parent()
+        if not saveDirPath.exists():
+            saveDirPath.mkdir(parents=True)
+    
+    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)
+        """
+        fpath = pathlib.Path(self.__downloadRequest.downloadFileName())
+        origName = fpath.name
+        name = os.path.join(directory, origName)
+        return name, origName
+    
+    @pyqtSlot(bool)
+    def on_pauseButton_clicked(self, checked):
+        """
+        Private slot to pause the download.
+        
+        @param checked flag indicating the state of the button
+        @type bool
+        """
+        if checked:
+            self.__downloadRequest.pause()
+        else:
+            self.__downloadRequest.resume()
+    
+    @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)
+        self.stopButton.setEnabled(False)
+        self.stopButton.setVisible(False)
+        self.openButton.setEnabled(False)
+        self.openButton.setVisible(False)
+        self.pauseButton.setEnabled(False)
+        self.pauseButton.setVisible(False)
+        self.setUpdatesEnabled(True)
+        self.__state = DownloadState.Cancelled
+        self.__downloadRequest.cancel()
+        self.__setDateTime()
+        self.downloadFinished.emit(False)
+    
+    @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.
+        """
+        url = QUrl.fromLocalFile(pathlib.Path(self.__fileName).resolve())
+        QDesktopServices.openUrl(url)
+    
+    def openFolder(self):
+        """
+        Public slot to open the folder containing the downloaded file.
+        """
+        url = QUrl.fromLocalFile(pathlib.Path(self.__fileName).resolve())
+        QDesktopServices.openUrl(url)
+    
+    @pyqtSlot()
+    def __downloadProgress(self):
+        """
+        Private slot to show the download progress.
+        """
+        self.__bytesReceived = self.__downloadRequest.receivedBytes()
+        self.__bytesTotal = self.__downloadRequest.totalBytes()
+        currentValue = 0
+        totalValue = 0
+        if self.__bytesTotal > 0:
+            currentValue = self.__bytesReceived * 100 // self.__bytesTotal
+            totalValue = 100
+        self.progressBar.setValue(currentValue)
+        self.progressBar.setMaximum(totalValue)
+        
+        self.progress.emit(currentValue, totalValue)
+        self.__updateInfoLabel()
+    
+    def downloadProgress(self):
+        """
+        Public method to get the download progress.
+        
+        @return current download progress
+        @rtype int
+        """
+        return self.progressBar.value()
+    
+    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.__downloadRequest.totalBytes()
+        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()
+        timeRemaining = (
+            (self.bytesTotal() - self.bytesReceived()) / cSpeed
+            if cSpeed != 0 else
+            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.msecsTo(QTime.currentTime())
+        )
+    
+    def __updateInfoLabel(self):
+        """
+        Private method to update the info label.
+        """
+        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) {3}"
+            ).format(
+                dataString(self.__bytesReceived),
+                bytesTotal == -1 and self.tr("?") or
+                dataString(bytesTotal),
+                speedString(speed),
+                remaining
+            )
+        else:
+            if bytesTotal in (self.__bytesReceived, -1):
+                info = self.tr(
+                    "{0} downloaded"
+                ).format(dataString(self.__bytesReceived))
+            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 == DownloadState.Downloading
+    
+    def downloadedSuccessfully(self):
+        """
+        Public method to check for a successful download.
+        
+        @return flag indicating a successful download (boolean)
+        """
+        return self.__state == DownloadState.Successful
+    
+    def downloadCanceled(self):
+        """
+        Public method to check, if the download was cancelled.
+        
+        @return flag indicating a canceled download (boolean)
+        """
+        return self.__state == DownloadState.Cancelled
+    
+    def __finished(self):
+        """
+        Private slot to handle the download finished.
+        """
+        self.__finishedDownloading = True
+        
+        noError = (self.__downloadRequest.state() ==
+                   QWebEngineDownloadRequest.DownloadState.DownloadCompleted)
+        
+        self.progressBar.setVisible(False)
+        self.pauseButton.setEnabled(False)
+        self.pauseButton.setVisible(False)
+        self.stopButton.setEnabled(False)
+        self.stopButton.setVisible(False)
+        self.openButton.setEnabled(noError)
+        self.openButton.setVisible(noError)
+        self.__state = DownloadState.Successful
+        self.__updateInfoLabel()
+        self.__setDateTime()
+        
+        self.__adjustSize()
+        
+        self.statusChanged.emit()
+        self.downloadFinished.emit(True)
+        
+        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 pathlib.Path(self.__fileName).resolve()
+    
+    def getData(self):
+        """
+        Public method to get the relevant download data.
+        
+        @return dictionary containing the URL, save location, done flag,
+            the URL of the related web page and the date and time of the
+            download
+        @rtype dict of {"URL": QUrl, "Location": str, "Done": bool,
+            "PageURL": QUrl, "Downloaded": QDateTime}
+        """
+        return {
+            "URL": self.__url,
+            "Location": self.__fileName,
+            "Done": self.downloadedSuccessfully(),
+            "PageURL": self.__pageUrl,
+            "Downloaded": self.__downloadedDateTime
+        }
+    
+    def setData(self, data):
+        """
+        Public method to set the relevant download data.
+        
+        @param data dictionary containing the URL, save location, done flag,
+            the URL of the related web page and the date and time of the
+            download
+        @type dict of {"URL": QUrl, "Location": str, "Done": bool,
+            "PageURL": QUrl, "Downloaded": QDateTime}
+        """
+        self.__url = data["URL"]
+        self.__fileName = data["Location"]
+        self.__pageUrl = data["PageURL"]
+        
+        self.filenameLabel.setText(pathlib.Path(self.__fileName).name)
+        self.infoLabel.setText(self.__fileName)
+        
+        try:
+            self.__setDateTime(data["Downloaded"])
+        except KeyError:
+            self.__setDateTime(QDateTime())
+        
+        self.pauseButton.setEnabled(False)
+        self.pauseButton.setVisible(False)
+        self.stopButton.setEnabled(False)
+        self.stopButton.setVisible(False)
+        self.openButton.setEnabled(data["Done"])
+        self.openButton.setVisible(data["Done"])
+        if data["Done"]:
+            self.__state = DownloadState.Successful
+        else:
+            self.__state = DownloadState.Cancelled
+        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()

eric ide

mercurial