eric6/WebBrowser/Download/DownloadManager.py

changeset 6942
2602857055c5
parent 6735
31e263d49c04
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the download manager class.
8 """
9
10 from __future__ import unicode_literals
11
12 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QModelIndex, QFileInfo, \
13 QUrl, QBasicTimer
14 from PyQt5.QtGui import QCursor, QKeySequence
15 from PyQt5.QtWidgets import QDialog, QStyle, QFileIconProvider, QMenu, \
16 QApplication, QShortcut
17
18 from E5Gui import E5MessageBox
19 from E5Gui.E5Application import e5App
20
21 from .Ui_DownloadManager import Ui_DownloadManager
22
23 from .DownloadModel import DownloadModel
24 from .DownloadUtilities import speedString, timeString
25
26 from WebBrowser.WebBrowserWindow import WebBrowserWindow
27
28 from Utilities.AutoSaver import AutoSaver
29 import UI.PixmapCache
30 import Preferences
31 import Globals
32
33
34 class DownloadManager(QDialog, Ui_DownloadManager):
35 """
36 Class implementing the download manager.
37
38 @signal downloadsCountChanged() emitted to indicate a change of the
39 count of download items
40 """
41 RemoveNever = 0
42 RemoveExit = 1
43 RemoveSuccessFullDownload = 2
44
45 UpdateTimerTimeout = 1000
46
47 downloadsCountChanged = pyqtSignal()
48
49 def __init__(self, parent=None):
50 """
51 Constructor
52
53 @param parent reference to the parent widget (QWidget)
54 """
55 super(DownloadManager, self).__init__(parent)
56 self.setupUi(self)
57 self.setWindowFlags(Qt.Window)
58
59 self.__winTaskbarButton = None
60
61 self.__saveTimer = AutoSaver(self, self.save)
62
63 self.__model = DownloadModel(self)
64 self.__manager = WebBrowserWindow.networkManager()
65
66 self.__iconProvider = None
67 self.__downloads = []
68 self.__downloadDirectory = ""
69 self.__loaded = False
70
71 self.__rowHeightMultiplier = 1.1
72
73 self.setDownloadDirectory(Preferences.getUI("DownloadPath"))
74
75 self.downloadsView.setShowGrid(False)
76 self.downloadsView.verticalHeader().hide()
77 self.downloadsView.horizontalHeader().hide()
78 self.downloadsView.setAlternatingRowColors(True)
79 self.downloadsView.horizontalHeader().setStretchLastSection(True)
80 self.downloadsView.setModel(self.__model)
81 self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu)
82 self.downloadsView.customContextMenuRequested.connect(
83 self.__customContextMenuRequested)
84
85 self.__clearShortcut = QShortcut(QKeySequence("Ctrl+L"), self)
86 self.__clearShortcut.activated.connect(self.on_cleanupButton_clicked)
87
88 self.__load()
89
90 self.__updateTimer = QBasicTimer()
91
92 def __customContextMenuRequested(self, pos):
93 """
94 Private slot to handle the context menu request for the bookmarks tree.
95
96 @param pos position the context menu was requested (QPoint)
97 """
98 menu = QMenu()
99
100 selectedRowsCount = len(
101 self.downloadsView.selectionModel().selectedRows())
102
103 if selectedRowsCount == 1:
104 row = self.downloadsView.selectionModel().selectedRows()[0].row()
105 itm = self.__downloads[row]
106 if itm.downloadedSuccessfully():
107 menu.addAction(
108 UI.PixmapCache.getIcon("open.png"),
109 self.tr("Open"), self.__contextMenuOpen)
110 elif itm.downloading():
111 menu.addAction(
112 UI.PixmapCache.getIcon("stopLoading.png"),
113 self.tr("Cancel"), self.__contextMenuCancel)
114 menu.addSeparator()
115 menu.addAction(
116 self.tr("Open Containing Folder"),
117 self.__contextMenuOpenFolder)
118 menu.addSeparator()
119 menu.addAction(
120 self.tr("Go to Download Page"),
121 self.__contextMenuGotoPage)
122 menu.addAction(
123 self.tr("Copy Download Link"),
124 self.__contextMenuCopyLink)
125 menu.addSeparator()
126 menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll)
127 if selectedRowsCount > 1 or \
128 (selectedRowsCount == 1 and
129 not self.__downloads[
130 self.downloadsView.selectionModel().selectedRows()[0].row()]
131 .downloading()):
132 menu.addSeparator()
133 menu.addAction(
134 self.tr("Remove From List"),
135 self.__contextMenuRemoveSelected)
136
137 menu.exec_(QCursor.pos())
138
139 def shutdown(self):
140 """
141 Public method to stop the download manager.
142 """
143 self.save()
144 self.close()
145
146 def activeDownloadsCount(self):
147 """
148 Public method to get the number of active downloads.
149
150 @return number of active downloads (integer)
151 """
152 count = 0
153
154 for download in self.__downloads:
155 if download.downloading():
156 count += 1
157 return count
158
159 def allowQuit(self):
160 """
161 Public method to check, if it is ok to quit.
162
163 @return flag indicating allowance to quit (boolean)
164 """
165 if self.activeDownloadsCount() > 0:
166 res = E5MessageBox.yesNo(
167 self,
168 self.tr(""),
169 self.tr("""There are %n downloads in progress.\n"""
170 """Do you want to quit anyway?""", "",
171 self.activeDownloadsCount()),
172 icon=E5MessageBox.Warning)
173 if not res:
174 self.show()
175 return False
176
177 self.close()
178 return True
179
180 def __testWebBrowserView(self, view, url):
181 """
182 Private method to test a web browser view against an URL.
183
184 @param view reference to the web browser view to be tested
185 @type WebBrowserView
186 @param url URL to test against
187 @type QUrl
188 @return flag indicating, that the view is the one for the URL
189 @rtype bool
190 """
191 if view.tabWidget().count() < 2:
192 return False
193
194 page = view.page()
195 if page.history().count() != 0:
196 return False
197
198 if not page.url().isEmpty() and \
199 page.url().host() == url.host():
200 return True
201
202 requestedUrl = page.requestedUrl()
203 if requestedUrl.isEmpty():
204 requestedUrl = QUrl(view.tabWidget().urlBarForView(view).text())
205 return requestedUrl.isEmpty() or requestedUrl.host() == url.host()
206
207 def __closeDownloadTab(self, url):
208 """
209 Private method to close an empty tab, that was opened only for loading
210 the download URL.
211
212 @param url download URL
213 @type QUrl
214 """
215 if self.__testWebBrowserView(
216 WebBrowserWindow.getWindow().currentBrowser(), url):
217 WebBrowserWindow.getWindow().closeCurrentBrowser()
218 return
219
220 for window in WebBrowserWindow.mainWindows():
221 for browser in window.browsers():
222 if self.__testWebBrowserView(browser, url):
223 window.closeBrowser(browser)
224 return
225
226 def download(self, downloadItem):
227 """
228 Public method to download a file.
229
230 @param downloadItem reference to the download object containing the
231 download data.
232 @type QWebEngineDownloadItem
233 """
234 url = downloadItem.url()
235 if url.isEmpty():
236 return
237
238 self.__closeDownloadTab(url)
239
240 # Safe Browsing
241 from WebBrowser.SafeBrowsing.SafeBrowsingManager import \
242 SafeBrowsingManager
243 if SafeBrowsingManager.isEnabled():
244 threatLists = \
245 WebBrowserWindow.safeBrowsingManager().lookupUrl(url)[0]
246 if threatLists:
247 threatMessages = WebBrowserWindow.safeBrowsingManager()\
248 .getThreatMessages(threatLists)
249 res = E5MessageBox.warning(
250 WebBrowserWindow.getWindow(),
251 self.tr("Suspicuous URL detected"),
252 self.tr("<p>The URL <b>{0}</b> was found in the Safe"
253 " Browsing database.</p>{1}").format(
254 url.toString(), "".join(threatMessages)),
255 E5MessageBox.StandardButtons(
256 E5MessageBox.Abort |
257 E5MessageBox.Ignore),
258 E5MessageBox.Abort)
259 if res == E5MessageBox.Abort:
260 downloadItem.cancel()
261 return
262
263 window = WebBrowserWindow.getWindow()
264 if window:
265 pageUrl = window.currentBrowser().url()
266 else:
267 pageUrl = QUrl()
268 from .DownloadItem import DownloadItem
269 itm = DownloadItem(downloadItem=downloadItem, pageUrl=pageUrl,
270 parent=self)
271 self.__addItem(itm)
272
273 if Preferences.getWebBrowser("DownloadManagerAutoOpen"):
274 self.show()
275 else:
276 self.__startUpdateTimer()
277
278 def show(self):
279 """
280 Public slot to show the download manager dialog.
281 """
282 self.__startUpdateTimer()
283
284 super(DownloadManager, self).show()
285 self.activateWindow()
286 self.raise_()
287
288 def __addItem(self, itm, append=False):
289 """
290 Private method to add a download to the list of downloads.
291
292 @param itm reference to the download item
293 @type DownloadItem
294 @param append flag indicating to append the item
295 @type bool
296 """
297 itm.statusChanged.connect(lambda: self.__updateRow(itm))
298 itm.downloadFinished.connect(self.__finished)
299
300 # insert at top of window
301 if append:
302 row = self.downloadsCount()
303 else:
304 row = 0
305 self.__model.beginInsertRows(QModelIndex(), row, row)
306 if append:
307 self.__downloads.append(itm)
308 else:
309 self.__downloads.insert(0, itm)
310 self.__model.endInsertRows()
311
312 self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm)
313 icon = self.style().standardIcon(QStyle.SP_FileIcon)
314 itm.setIcon(icon)
315 self.downloadsView.setRowHeight(
316 row, itm.sizeHint().height() * self.__rowHeightMultiplier)
317 # just in case the download finished before the constructor returned
318 self.__updateRow(itm)
319 self.changeOccurred()
320
321 self.downloadsCountChanged.emit()
322
323 def __updateRow(self, itm):
324 """
325 Private slot to update a download item.
326
327 @param itm reference to the download item
328 @type DownloadItem
329 """
330 if itm not in self.__downloads:
331 return
332
333 row = self.__downloads.index(itm)
334
335 if self.__iconProvider is None:
336 self.__iconProvider = QFileIconProvider()
337
338 icon = self.__iconProvider.icon(QFileInfo(itm.fileName()))
339 if icon.isNull():
340 icon = self.style().standardIcon(QStyle.SP_FileIcon)
341 itm.setIcon(icon)
342
343 self.downloadsView.setRowHeight(
344 row,
345 itm.minimumSizeHint().height() * self.__rowHeightMultiplier)
346
347 remove = False
348
349 if itm.downloadedSuccessfully() and \
350 self.removePolicy() == DownloadManager.RemoveSuccessFullDownload:
351 remove = True
352
353 if remove:
354 self.__model.removeRow(row)
355
356 self.cleanupButton.setEnabled(
357 (self.downloadsCount() - self.activeDownloadsCount()) > 0)
358
359 # record the change
360 self.changeOccurred()
361
362 def removePolicy(self):
363 """
364 Public method to get the remove policy.
365
366 @return remove policy (integer)
367 """
368 return Preferences.getWebBrowser("DownloadManagerRemovePolicy")
369
370 def setRemovePolicy(self, policy):
371 """
372 Public method to set the remove policy.
373
374 @param policy policy to be set
375 (DownloadManager.RemoveExit, DownloadManager.RemoveNever,
376 DownloadManager.RemoveSuccessFullDownload)
377 """
378 assert policy in (DownloadManager.RemoveExit,
379 DownloadManager.RemoveNever,
380 DownloadManager.RemoveSuccessFullDownload)
381
382 if policy == self.removePolicy():
383 return
384
385 Preferences.setWebBrowser("DownloadManagerRemovePolicy", self.policy)
386
387 def save(self):
388 """
389 Public method to save the download settings.
390 """
391 if not self.__loaded:
392 return
393
394 Preferences.setWebBrowser("DownloadManagerSize", self.size())
395 Preferences.setWebBrowser("DownloadManagerPosition", self.pos())
396 if self.removePolicy() == DownloadManager.RemoveExit:
397 return
398
399 from WebBrowser.WebBrowserWindow import WebBrowserWindow
400 if WebBrowserWindow.isPrivate():
401 return
402
403 downloads = []
404 for download in self.__downloads:
405 downloads.append(download.getData())
406 Preferences.setWebBrowser("DownloadManagerDownloads", downloads)
407
408 def __load(self):
409 """
410 Private method to load the download settings.
411 """
412 if self.__loaded:
413 return
414
415 size = Preferences.getWebBrowser("DownloadManagerSize")
416 if size.isValid():
417 self.resize(size)
418 pos = Preferences.getWebBrowser("DownloadManagerPosition")
419 self.move(pos)
420
421 from WebBrowser.WebBrowserWindow import WebBrowserWindow
422 if not WebBrowserWindow.isPrivate():
423 downloads = Preferences.getWebBrowser("DownloadManagerDownloads")
424 for download in downloads:
425 if not download["URL"].isEmpty() and \
426 bool(download["Location"]):
427 from .DownloadItem import DownloadItem
428 itm = DownloadItem(parent=self)
429 itm.setData(download)
430 self.__addItem(itm, append=True)
431 self.cleanupButton.setEnabled(
432 (self.downloadsCount() - self.activeDownloadsCount()) > 0)
433
434 self.__loaded = True
435
436 self.downloadsCountChanged.emit()
437
438 def closeEvent(self, evt):
439 """
440 Protected event handler for the close event.
441
442 @param evt reference to the close event
443 @type QCloseEvent
444 """
445 self.save()
446
447 def cleanup(self):
448 """
449 Public slot to cleanup the downloads.
450 """
451 self.on_cleanupButton_clicked()
452
453 @pyqtSlot()
454 def on_cleanupButton_clicked(self):
455 """
456 Private slot to cleanup the downloads.
457 """
458 if self.downloadsCount() == 0:
459 return
460
461 self.__model.removeRows(0, self.downloadsCount())
462 if self.downloadsCount() == 0 and \
463 self.__iconProvider is not None:
464 self.__iconProvider = None
465
466 self.changeOccurred()
467
468 self.downloadsCountChanged.emit()
469
470 def __finished(self, success):
471 """
472 Private slot to handle a finished download.
473
474 @param success flag indicating a successful download
475 @type bool
476 """
477 if self.isVisible():
478 QApplication.alert(self)
479
480 self.downloadsCountChanged.emit()
481
482 if self.activeDownloadsCount() == 0:
483 # all active downloads are done
484 if success and e5App().activeWindow() is not self:
485 if WebBrowserWindow.notificationsEnabled():
486 WebBrowserWindow.showNotification(
487 UI.PixmapCache.getPixmap("downloads48.png"),
488 self.tr("Downloads finished"),
489 self.tr("All files have been downloaded.")
490 )
491 if not Preferences.getWebBrowser("DownloadManagerAutoClose"):
492 self.raise_()
493 self.activateWindow()
494
495 self.__stopUpdateTimer()
496 self.infoLabel.clear()
497 self.setWindowTitle(self.tr("Download Manager"))
498 if Globals.isWindowsPlatform():
499 self.__taskbarButton().progress().hide()
500
501 if Preferences.getWebBrowser("DownloadManagerAutoClose"):
502 self.close()
503
504 def setDownloadDirectory(self, directory):
505 """
506 Public method to set the current download directory.
507
508 @param directory current download directory (string)
509 """
510 self.__downloadDirectory = directory
511 if self.__downloadDirectory != "":
512 self.__downloadDirectory += "/"
513
514 def downloadDirectory(self):
515 """
516 Public method to get the current download directory.
517
518 @return current download directory (string)
519 """
520 return self.__downloadDirectory
521
522 def downloadsCount(self):
523 """
524 Public method to get the number of downloads.
525
526 @return number of downloads
527 @rtype int
528 """
529 return len(self.__downloads)
530
531 def downloads(self):
532 """
533 Public method to get a reference to the downloads.
534
535 @return reference to the downloads (list of DownloadItem)
536 """
537 return self.__downloads
538
539 def changeOccurred(self):
540 """
541 Public method to signal a change.
542 """
543 self.__saveTimer.changeOccurred()
544
545 def __taskbarButton(self):
546 """
547 Private method to get a reference to the task bar button (Windows
548 only).
549
550 @return reference to the task bar button
551 @rtype QWinTaskbarButton or None
552 """
553 if Globals.isWindowsPlatform():
554 from PyQt5.QtWinExtras import QWinTaskbarButton
555 if self.__winTaskbarButton is None:
556 window = WebBrowserWindow.mainWindow()
557 self.__winTaskbarButton = QWinTaskbarButton(
558 window.windowHandle())
559 self.__winTaskbarButton.progress().setRange(0, 100)
560
561 return self.__winTaskbarButton
562
563 def timerEvent(self, evt):
564 """
565 Protected event handler for timer events.
566
567 @param evt reference to the timer event
568 @type QTimerEvent
569 """
570 if evt.timerId() == self.__updateTimer.timerId():
571 if self.activeDownloadsCount() == 0:
572 self.__stopUpdateTimer()
573 self.infoLabel.clear()
574 self.setWindowTitle(self.tr("Download Manager"))
575 if Globals.isWindowsPlatform():
576 self.__taskbarButton().progress().hide()
577 else:
578 progresses = []
579 for itm in self.__downloads:
580 if itm is None or \
581 itm.downloadCanceled() or \
582 not itm.downloading():
583 continue
584
585 progresses.append((
586 itm.downloadProgress(),
587 itm.remainingTime(),
588 itm.currentSpeed()
589 ))
590
591 if not progresses:
592 return
593
594 remaining = 0
595 progress = 0
596 speed = 0.0
597
598 for progressData in progresses:
599 if progressData[1] > remaining:
600 remaining = progressData[1]
601 progress += progressData[0]
602 speed += progressData[2]
603 progress = progress / len(progresses)
604
605 if self.isVisible():
606 self.infoLabel.setText(self.tr(
607 "{0}% of %n file(s) ({1}) {2}", "",
608 len(progresses)).format(
609 progress,
610 speedString(speed),
611 timeString(remaining),
612 ))
613 self.setWindowTitle(self.tr("{0}% - Download Manager"))
614
615 if Globals.isWindowsPlatform():
616 self.__taskbarButton().progress().show()
617 self.__taskbarButton().progress().setValue(progress)
618
619 super(DownloadManager, self).timerEvent(evt)
620
621 def __startUpdateTimer(self):
622 """
623 Private slot to start the update timer.
624 """
625 if self.activeDownloadsCount() and not self.__updateTimer.isActive():
626 self.__updateTimer.start(DownloadManager.UpdateTimerTimeout, self)
627
628 def __stopUpdateTimer(self):
629 """
630 Private slot to stop the update timer.
631 """
632 self.__updateTimer.stop()
633
634 ###########################################################################
635 ## Context menu related methods below
636 ###########################################################################
637
638 def __currentItem(self):
639 """
640 Private method to get a reference to the current item.
641
642 @return reference to the current item (DownloadItem)
643 """
644 index = self.downloadsView.currentIndex()
645 if index and index.isValid():
646 row = index.row()
647 return self.__downloads[row]
648
649 return None
650
651 def __contextMenuOpen(self):
652 """
653 Private method to open the downloaded file.
654 """
655 itm = self.__currentItem()
656 if itm is not None:
657 itm.openFile()
658
659 def __contextMenuOpenFolder(self):
660 """
661 Private method to open the folder containing the downloaded file.
662 """
663 itm = self.__currentItem()
664 if itm is not None:
665 itm.openFolder()
666
667 def __contextMenuCancel(self):
668 """
669 Private method to cancel the current download.
670 """
671 itm = self.__currentItem()
672 if itm is not None:
673 itm.cancelDownload()
674
675 def __contextMenuGotoPage(self):
676 """
677 Private method to open the download page.
678 """
679 itm = self.__currentItem()
680 if itm is not None:
681 url = itm.getPageUrl()
682 WebBrowserWindow.mainWindow().openUrl(url, "")
683
684 def __contextMenuCopyLink(self):
685 """
686 Private method to copy the download link to the clipboard.
687 """
688 itm = self.__currentItem()
689 if itm is not None:
690 url = itm.getPageUrl().toDisplayString(QUrl.FullyDecoded)
691 QApplication.clipboard().setText(url)
692
693 def __contextMenuSelectAll(self):
694 """
695 Private method to select all downloads.
696 """
697 self.downloadsView.selectAll()
698
699 def __contextMenuRemoveSelected(self):
700 """
701 Private method to remove the selected downloads from the list.
702 """
703 self.downloadsView.removeSelected()

eric ide

mercurial