--- a/src/eric7/WebBrowser/Download/DownloadManager.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/WebBrowser/Download/DownloadManager.py Wed Jul 13 14:55:47 2022 +0200 @@ -8,12 +8,16 @@ """ from PyQt6.QtCore import ( - pyqtSlot, pyqtSignal, Qt, QModelIndex, QFileInfo, QUrl, QBasicTimer + pyqtSlot, + pyqtSignal, + Qt, + QModelIndex, + QFileInfo, + QUrl, + QBasicTimer, ) from PyQt6.QtGui import QCursor, QKeySequence, QShortcut -from PyQt6.QtWidgets import ( - QDialog, QStyle, QFileIconProvider, QMenu, QApplication -) +from PyQt6.QtWidgets import QDialog, QStyle, QFileIconProvider, QMenu, QApplication from EricWidgets import EricMessageBox from EricWidgets.EricApplication import ericApp @@ -34,156 +38,159 @@ class DownloadManager(QDialog, Ui_DownloadManager): """ Class implementing the download manager. - + @signal downloadsCountChanged() emitted to indicate a change of the count of download items """ + RemoveNever = 0 RemoveExit = 1 RemoveSuccessFullDownload = 2 - + UpdateTimerTimeout = 1000 - + downloadsCountChanged = pyqtSignal() - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget (QWidget) """ super().__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.WindowType.Window) - + self.__winTaskbarButton = None - + self.__saveTimer = AutoSaver(self, self.save) - + self.__model = DownloadModel(self) self.__manager = WebBrowserWindow.networkManager() - + self.__iconProvider = None self.__downloads = [] self.__downloadDirectory = "" self.__loaded = False - + self.__rowHeightMultiplier = 1.1 - + self.setDownloadDirectory(Preferences.getUI("DownloadPath")) - + self.downloadsView.setShowGrid(False) self.downloadsView.verticalHeader().hide() self.downloadsView.horizontalHeader().hide() self.downloadsView.setAlternatingRowColors(True) self.downloadsView.horizontalHeader().setStretchLastSection(True) self.downloadsView.setModel(self.__model) - self.downloadsView.setContextMenuPolicy( - Qt.ContextMenuPolicy.CustomContextMenu) + self.downloadsView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.downloadsView.customContextMenuRequested.connect( - self.__customContextMenuRequested) - + self.__customContextMenuRequested + ) + self.__clearShortcut = QShortcut(QKeySequence("Ctrl+L"), self) self.__clearShortcut.activated.connect(self.on_cleanupButton_clicked) - + self.__load() - + self.__updateTimer = QBasicTimer() - + def __customContextMenuRequested(self, pos): """ Private slot to handle the context menu request for the bookmarks tree. - + @param pos position the context menu was requested (QPoint) """ menu = QMenu() - - selectedRowsCount = len( - self.downloadsView.selectionModel().selectedRows()) - + + selectedRowsCount = len(self.downloadsView.selectionModel().selectedRows()) + if selectedRowsCount == 1: row = self.downloadsView.selectionModel().selectedRows()[0].row() itm = self.__downloads[row] if itm.downloadedSuccessfully(): menu.addAction( UI.PixmapCache.getIcon("open"), - self.tr("Open"), self.__contextMenuOpen) + self.tr("Open"), + self.__contextMenuOpen, + ) elif itm.downloading(): menu.addAction( UI.PixmapCache.getIcon("stopLoading"), - self.tr("Cancel"), self.__contextMenuCancel) + self.tr("Cancel"), + self.__contextMenuCancel, + ) menu.addSeparator() menu.addAction( - self.tr("Open Containing Folder"), - self.__contextMenuOpenFolder) + self.tr("Open Containing Folder"), self.__contextMenuOpenFolder + ) menu.addSeparator() - menu.addAction( - self.tr("Go to Download Page"), - self.__contextMenuGotoPage) - menu.addAction( - self.tr("Copy Download Link"), - self.__contextMenuCopyLink) + menu.addAction(self.tr("Go to Download Page"), self.__contextMenuGotoPage) + menu.addAction(self.tr("Copy Download Link"), self.__contextMenuCopyLink) menu.addSeparator() menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll) - if ( - selectedRowsCount > 1 or - (selectedRowsCount == 1 and - not self.__downloads[ - self.downloadsView.selectionModel().selectedRows()[0].row()] - .downloading()) + if selectedRowsCount > 1 or ( + selectedRowsCount == 1 + and not self.__downloads[ + self.downloadsView.selectionModel().selectedRows()[0].row() + ].downloading() ): menu.addSeparator() menu.addAction( - self.tr("Remove From List"), - self.__contextMenuRemoveSelected) - + self.tr("Remove From List"), self.__contextMenuRemoveSelected + ) + menu.exec(QCursor.pos()) - + def shutdown(self): """ Public method to stop the download manager. """ self.save() self.close() - + def activeDownloadsCount(self): """ Public method to get the number of active downloads. - + @return number of active downloads (integer) """ count = 0 - + for download in self.__downloads: if download.downloading(): count += 1 return count - + def allowQuit(self): """ Public method to check, if it is ok to quit. - + @return flag indicating allowance to quit (boolean) """ if self.activeDownloadsCount() > 0: res = EricMessageBox.yesNo( self, self.tr(""), - self.tr("""There are %n downloads in progress.\n""" - """Do you want to quit anyway?""", "", - self.activeDownloadsCount()), - icon=EricMessageBox.Warning) + self.tr( + """There are %n downloads in progress.\n""" + """Do you want to quit anyway?""", + "", + self.activeDownloadsCount(), + ), + icon=EricMessageBox.Warning, + ) if not res: self.show() return False - + self.close() return True - + def __testWebBrowserView(self, view, url): """ Private method to test a web browser view against an URL. - + @param view reference to the web browser view to be tested @type WebBrowserView @param url URL to test against @@ -193,45 +200,43 @@ """ if view.tabWidget().count() < 2: return False - + page = view.page() if page.history().count() != 0: return False - - if ( - not page.url().isEmpty() and - page.url().host() == url.host() - ): + + if not page.url().isEmpty() and page.url().host() == url.host(): return True - + requestedUrl = page.requestedUrl() if requestedUrl.isEmpty(): requestedUrl = QUrl(view.tabWidget().urlBarForView(view).text()) return requestedUrl.isEmpty() or requestedUrl.host() == url.host() - + def __closeDownloadTab(self, url): """ Private method to close an empty tab, that was opened only for loading the download URL. - + @param url download URL @type QUrl """ if self.__testWebBrowserView( - WebBrowserWindow.getWindow().currentBrowser(), url): + WebBrowserWindow.getWindow().currentBrowser(), url + ): WebBrowserWindow.getWindow().closeCurrentBrowser() return - + for window in WebBrowserWindow.mainWindows(): for browser in window.browsers(): if self.__testWebBrowserView(browser, url): window.closeBrowser(browser) return - + def download(self, downloadRequest): """ Public method to download a file. - + @param downloadRequest reference to the download object containing the download data. @type QWebEngineDownloadRequest @@ -239,60 +244,62 @@ url = downloadRequest.url() if url.isEmpty(): return - + self.__closeDownloadTab(url) - + # Safe Browsing - from WebBrowser.SafeBrowsing.SafeBrowsingManager import ( - SafeBrowsingManager - ) + from WebBrowser.SafeBrowsing.SafeBrowsingManager import SafeBrowsingManager + if SafeBrowsingManager.isEnabled(): - threatLists = ( - WebBrowserWindow.safeBrowsingManager().lookupUrl(url)[0] - ) + threatLists = WebBrowserWindow.safeBrowsingManager().lookupUrl(url)[0] if threatLists: threatMessages = ( - WebBrowserWindow.safeBrowsingManager() - .getThreatMessages(threatLists) + WebBrowserWindow.safeBrowsingManager().getThreatMessages( + threatLists + ) ) res = EricMessageBox.warning( WebBrowserWindow.getWindow(), self.tr("Suspicuous URL detected"), - self.tr("<p>The URL <b>{0}</b> was found in the Safe" - " Browsing database.</p>{1}").format( - url.toString(), "".join(threatMessages)), + self.tr( + "<p>The URL <b>{0}</b> was found in the Safe" + " Browsing database.</p>{1}" + ).format(url.toString(), "".join(threatMessages)), EricMessageBox.Abort | EricMessageBox.Ignore, - EricMessageBox.Abort) + EricMessageBox.Abort, + ) if res == EricMessageBox.Abort: downloadRequest.cancel() return - + window = WebBrowserWindow.getWindow() pageUrl = window.currentBrowser().url() if window else QUrl() from .DownloadItem import DownloadItem - itm = DownloadItem(downloadRequest=downloadRequest, pageUrl=pageUrl, - parent=self) + + itm = DownloadItem( + downloadRequest=downloadRequest, pageUrl=pageUrl, parent=self + ) self.__addItem(itm) - + if Preferences.getWebBrowser("DownloadManagerAutoOpen"): self.show() else: self.__startUpdateTimer() - + def show(self): """ Public slot to show the download manager dialog. """ self.__startUpdateTimer() - + super().show() self.activateWindow() self.raise_() - + def __addItem(self, itm, append=False): """ Private method to add a download to the list of downloads. - + @param itm reference to the download item @type DownloadItem @param append flag indicating to append the item @@ -300,7 +307,7 @@ """ itm.statusChanged.connect(lambda: self.__updateRow(itm)) itm.downloadFinished.connect(self.__finished) - + # insert at top of window row = self.downloadsCount() if append else 0 self.__model.beginInsertRows(QModelIndex(), row, row) @@ -309,153 +316,157 @@ else: self.__downloads.insert(0, itm) self.__model.endInsertRows() - + self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm) icon = self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight( - row, int(itm.sizeHint().height() * self.__rowHeightMultiplier)) + row, int(itm.sizeHint().height() * self.__rowHeightMultiplier) + ) # just in case the download finished before the constructor returned self.__updateRow(itm) self.changeOccurred() - + self.downloadsCountChanged.emit() - + def __updateRow(self, itm): """ Private slot to update a download item. - + @param itm reference to the download item @type DownloadItem """ if itm not in self.__downloads: return - + row = self.__downloads.index(itm) - + if self.__iconProvider is None: self.__iconProvider = QFileIconProvider() - + icon = self.__iconProvider.icon(QFileInfo(itm.fileName())) if icon.isNull(): icon = self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon) itm.setIcon(icon) - + self.downloadsView.setRowHeight( - row, - int(itm.minimumSizeHint().height() * self.__rowHeightMultiplier)) - + row, int(itm.minimumSizeHint().height() * self.__rowHeightMultiplier) + ) + remove = False - + if ( - itm.downloadedSuccessfully() and - self.removePolicy() == DownloadManager.RemoveSuccessFullDownload + itm.downloadedSuccessfully() + and self.removePolicy() == DownloadManager.RemoveSuccessFullDownload ): remove = True - + if remove: self.__model.removeRow(row) - + self.cleanupButton.setEnabled( - (self.downloadsCount() - self.activeDownloadsCount()) > 0) - + (self.downloadsCount() - self.activeDownloadsCount()) > 0 + ) + # record the change self.changeOccurred() - + def removePolicy(self): """ Public method to get the remove policy. - + @return remove policy (integer) """ return Preferences.getWebBrowser("DownloadManagerRemovePolicy") - + def setRemovePolicy(self, policy): """ Public method to set the remove policy. - + @param policy policy to be set (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) """ - if policy in (DownloadManager.RemoveExit, - DownloadManager.RemoveNever, - DownloadManager.RemoveSuccessFullDownload): - + if policy in ( + DownloadManager.RemoveExit, + DownloadManager.RemoveNever, + DownloadManager.RemoveSuccessFullDownload, + ): + if policy == self.removePolicy(): return - - Preferences.setWebBrowser("DownloadManagerRemovePolicy", - self.policy) - + + Preferences.setWebBrowser("DownloadManagerRemovePolicy", self.policy) + def save(self): """ Public method to save the download settings. """ if not self.__loaded: return - + Preferences.setWebBrowser("DownloadManagerSize", self.size()) Preferences.setWebBrowser("DownloadManagerPosition", self.pos()) if self.removePolicy() == DownloadManager.RemoveExit: return - + from WebBrowser.WebBrowserWindow import WebBrowserWindow + if WebBrowserWindow.isPrivate(): return - + downloads = [] for download in self.__downloads: downloads.append(download.getData()) Preferences.setWebBrowser("DownloadManagerDownloads", downloads) - + def __load(self): """ Private method to load the download settings. """ if self.__loaded: return - + size = Preferences.getWebBrowser("DownloadManagerSize") if size.isValid(): self.resize(size) pos = Preferences.getWebBrowser("DownloadManagerPosition") self.move(pos) - + from WebBrowser.WebBrowserWindow import WebBrowserWindow + if not WebBrowserWindow.isPrivate(): downloads = Preferences.getWebBrowser("DownloadManagerDownloads") for download in downloads: - if ( - not download["URL"].isEmpty() and - bool(download["Location"]) - ): + if not download["URL"].isEmpty() and bool(download["Location"]): from .DownloadItem import DownloadItem + itm = DownloadItem(parent=self) itm.setData(download) self.__addItem(itm, append=True) self.cleanupButton.setEnabled( - (self.downloadsCount() - self.activeDownloadsCount()) > 0) - + (self.downloadsCount() - self.activeDownloadsCount()) > 0 + ) + self.__loaded = True - + self.downloadsCountChanged.emit() - + def closeEvent(self, evt): """ Protected event handler for the close event. - + @param evt reference to the close event @type QCloseEvent """ self.save() - + def cleanup(self): """ Public slot to cleanup the downloads. """ self.on_cleanupButton_clicked() - + @pyqtSlot() def on_cleanupButton_clicked(self): """ @@ -463,114 +474,111 @@ """ if self.downloadsCount() == 0: return - + self.__model.removeRows(0, self.downloadsCount()) - if ( - self.downloadsCount() == 0 and - self.__iconProvider is not None - ): + if self.downloadsCount() == 0 and self.__iconProvider is not None: self.__iconProvider = None - + self.changeOccurred() - + self.downloadsCountChanged.emit() - + def __finished(self, success): """ Private slot to handle a finished download. - + @param success flag indicating a successful download @type bool """ if self.isVisible(): QApplication.alert(self) - + self.downloadsCountChanged.emit() - + if self.activeDownloadsCount() == 0: # all active downloads are done if success and ericApp().activeWindow() is not self: WebBrowserWindow.showNotification( UI.PixmapCache.getPixmap("downloads48"), self.tr("Downloads finished"), - self.tr("All files have been downloaded.") + self.tr("All files have been downloaded."), ) if not Preferences.getWebBrowser("DownloadManagerAutoClose"): self.raise_() self.activateWindow() - + self.__stopUpdateTimer() self.infoLabel.clear() self.setWindowTitle(self.tr("Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().hide() - + if Preferences.getWebBrowser("DownloadManagerAutoClose"): self.close() - + def setDownloadDirectory(self, directory): """ Public method to set the current download directory. - + @param directory current download directory (string) """ self.__downloadDirectory = directory if self.__downloadDirectory != "": self.__downloadDirectory += "/" - + def downloadDirectory(self): """ Public method to get the current download directory. - + @return current download directory (string) """ return self.__downloadDirectory - + def downloadsCount(self): """ Public method to get the number of downloads. - + @return number of downloads @rtype int """ return len(self.__downloads) - + def downloads(self): """ Public method to get a reference to the downloads. - + @return reference to the downloads (list of DownloadItem) """ return self.__downloads - + def changeOccurred(self): """ Public method to signal a change. """ self.__saveTimer.changeOccurred() - + def __taskbarButton(self): """ Private method to get a reference to the task bar button (Windows only). - + @return reference to the task bar button @rtype QWinTaskbarButton or None """ if Globals.isWindowsPlatform(): from PyQt6.QtWinExtras import QWinTaskbarButton + if self.__winTaskbarButton is None: window = WebBrowserWindow.mainWindow() - self.__winTaskbarButton = QWinTaskbarButton( - window.windowHandle()) + self.__winTaskbarButton = QWinTaskbarButton(window.windowHandle()) self.__winTaskbarButton.progress().setRange(0, 100) - + return self.__winTaskbarButton - + def timerEvent(self, evt): """ Protected event handler for timer events. - + @param evt reference to the timer event @type QTimerEvent """ @@ -584,79 +592,79 @@ else: progresses = [] for itm in self.__downloads: - if ( - itm is None or - itm.downloadCanceled() or - not itm.downloading() - ): + if itm is None or itm.downloadCanceled() or not itm.downloading(): continue - - progresses.append(( - itm.downloadProgress(), - itm.remainingTime(), - itm.currentSpeed() - )) - + + progresses.append( + ( + itm.downloadProgress(), + itm.remainingTime(), + itm.currentSpeed(), + ) + ) + if not progresses: return - + remaining = 0 progress = 0 speed = 0.0 - + for progressData in progresses: if progressData[1] > remaining: remaining = progressData[1] progress += progressData[0] speed += progressData[2] progress /= len(progresses) - + if self.isVisible(): - self.infoLabel.setText(self.tr( - "{0}% of %n file(s) ({1}) {2}", "", - len(progresses)).format( - progress, - speedString(speed), - timeString(remaining), - )) + self.infoLabel.setText( + self.tr( + "{0}% of %n file(s) ({1}) {2}", "", len(progresses) + ).format( + progress, + speedString(speed), + timeString(remaining), + ) + ) self.setWindowTitle(self.tr("{0}% - Download Manager")) - + if Globals.isWindowsPlatform(): self.__taskbarButton().progress().show() self.__taskbarButton().progress().setValue(progress) - + super().timerEvent(evt) - + def __startUpdateTimer(self): """ Private slot to start the update timer. """ if self.activeDownloadsCount() and not self.__updateTimer.isActive(): self.__updateTimer.start(DownloadManager.UpdateTimerTimeout, self) - + def __stopUpdateTimer(self): """ Private slot to stop the update timer. """ self.__updateTimer.stop() - + ########################################################################### ## Context menu related methods below ########################################################################### - + def __currentItem(self): """ Private method to get a reference to the current item. - + @return reference to the current item (DownloadItem) """ index = self.downloadsView.currentIndex() if index and index.isValid(): row = index.row() return self.__downloads[row] - + return None - + def __contextMenuOpen(self): """ Private method to open the downloaded file. @@ -664,7 +672,7 @@ itm = self.__currentItem() if itm is not None: itm.openFile() - + def __contextMenuOpenFolder(self): """ Private method to open the folder containing the downloaded file. @@ -672,7 +680,7 @@ itm = self.__currentItem() if itm is not None: itm.openFolder() - + def __contextMenuCancel(self): """ Private method to cancel the current download. @@ -680,7 +688,7 @@ itm = self.__currentItem() if itm is not None: itm.cancelDownload() - + def __contextMenuGotoPage(self): """ Private method to open the download page. @@ -689,7 +697,7 @@ if itm is not None: url = itm.getPageUrl() WebBrowserWindow.mainWindow().openUrl(url, "") - + def __contextMenuCopyLink(self): """ Private method to copy the download link to the clipboard. @@ -697,15 +705,16 @@ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl().toDisplayString( - QUrl.ComponentFormattingOption.FullyDecoded) + QUrl.ComponentFormattingOption.FullyDecoded + ) QApplication.clipboard().setText(url) - + def __contextMenuSelectAll(self): """ Private method to select all downloads. """ self.downloadsView.selectAll() - + def __contextMenuRemoveSelected(self): """ Private method to remove the selected downloads from the list.