eric6/WebBrowser/Download/DownloadItem.py

branch
maintenance
changeset 6989
8b8cadf8d7e9
parent 6646
51eefa621de4
parent 6942
2602857055c5
child 7286
7eb04391adf7
equal deleted inserted replaced
6938:7926553b7509 6989:8b8cadf8d7e9
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a widget controlling a download.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13
14 from PyQt5.QtCore import pyqtSlot, pyqtSignal, PYQT_VERSION, Qt, QTime, QUrl, \
15 QStandardPaths, QFileInfo, QDateTime
16 from PyQt5.QtGui import QPalette, QDesktopServices
17 from PyQt5.QtWidgets import QWidget, QStyle, QDialog
18 from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
19
20 from E5Gui import E5FileDialog
21
22 from .Ui_DownloadItem import Ui_DownloadItem
23
24 from .DownloadUtilities import timeString, dataString, speedString
25 from WebBrowser.WebBrowserWindow import WebBrowserWindow
26
27 import UI.PixmapCache
28 import Utilities.MimeTypes
29 import Globals
30 from Globals import qVersionTuple
31
32
33 class DownloadItem(QWidget, Ui_DownloadItem):
34 """
35 Class implementing a widget controlling a download.
36
37 @signal statusChanged() emitted upon a status change of a download
38 @signal downloadFinished(success) emitted when a download finished
39 @signal progress(int, int) emitted to signal the download progress
40 """
41 statusChanged = pyqtSignal()
42 downloadFinished = pyqtSignal(bool)
43 progress = pyqtSignal(int, int)
44
45 Downloading = 0
46 DownloadSuccessful = 1
47 DownloadCancelled = 2
48
49 def __init__(self, downloadItem=None, pageUrl=None, parent=None):
50 """
51 Constructor
52
53 @param downloadItem reference to the download object containing the
54 download data.
55 @type QWebEngineDownloadItem
56 @param pageUrl URL of the calling page
57 @type QUrl
58 @param parent reference to the parent widget
59 @type QWidget
60 """
61 super(DownloadItem, self).__init__(parent)
62 self.setupUi(self)
63
64 p = self.infoLabel.palette()
65 p.setColor(QPalette.Text, Qt.darkGray)
66 self.infoLabel.setPalette(p)
67
68 self.progressBar.setMaximum(0)
69
70 self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause.png"))
71 self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading.png"))
72 self.openButton.setIcon(UI.PixmapCache.getIcon("open.png"))
73 self.openButton.setEnabled(False)
74 self.openButton.setVisible(False)
75 if not hasattr(QWebEngineDownloadItem, "pause"):
76 # pause/resume was defined in Qt 5.10.0 / PyQt 5.10.0
77 self.pauseButton.setEnabled(False)
78 self.pauseButton.setVisible(False)
79
80 self.__state = DownloadItem.Downloading
81
82 icon = self.style().standardIcon(QStyle.SP_FileIcon)
83 self.fileIcon.setPixmap(icon.pixmap(48, 48))
84
85 self.__downloadItem = downloadItem
86 if pageUrl is None:
87 self.__pageUrl = QUrl()
88 else:
89 self.__pageUrl = pageUrl
90 self.__bytesReceived = 0
91 self.__bytesTotal = -1
92 self.__downloadTime = QTime()
93 self.__fileName = ""
94 self.__originalFileName = ""
95 self.__finishedDownloading = False
96 self.__gettingFileName = False
97 self.__canceledFileSelect = False
98 self.__autoOpen = False
99 self.__downloadedDateTime = QDateTime()
100
101 self.__initialize()
102
103 def __initialize(self):
104 """
105 Private method to initialize the widget.
106 """
107 if self.__downloadItem is None:
108 return
109
110 self.__finishedDownloading = False
111 self.__bytesReceived = 0
112 self.__bytesTotal = -1
113
114 # start timer for the download estimation
115 self.__downloadTime.start()
116
117 # attach to the download item object
118 self.__url = self.__downloadItem.url()
119 self.__downloadItem.downloadProgress.connect(self.__downloadProgress)
120 self.__downloadItem.finished.connect(self.__finished)
121
122 # reset info
123 self.datetimeLabel.clear()
124 self.datetimeLabel.hide()
125 self.infoLabel.clear()
126 self.progressBar.setValue(0)
127 if self.__downloadItem.state() == \
128 QWebEngineDownloadItem.DownloadRequested:
129 self.__getFileName()
130 if not self.__fileName:
131 self.__downloadItem.cancel()
132 else:
133 self.__downloadItem.setPath(self.__fileName)
134 self.__downloadItem.accept()
135 else:
136 fileName = self.__downloadItem.path()
137 self.__setFileName(fileName)
138
139 def __getFileName(self):
140 """
141 Private method to get the file name to save to from the user.
142 """
143 if self.__gettingFileName:
144 return
145
146 if qVersionTuple() >= (5, 8, 0) and PYQT_VERSION >= 0x50800:
147 savePage = self.__downloadItem.type() == \
148 QWebEngineDownloadItem.SavePage
149 elif qVersionTuple() >= (5, 7, 0) and PYQT_VERSION >= 0x50700:
150 savePage = self.__downloadItem.savePageFormat() != \
151 QWebEngineDownloadItem.UnknownSaveFormat
152 else:
153 savePage = self.__downloadItem.path().lower().endswith(
154 (".mhtml", ".mht"))
155
156 documentLocation = QStandardPaths.writableLocation(
157 QStandardPaths.DocumentsLocation)
158 downloadDirectory = WebBrowserWindow\
159 .downloadManager().downloadDirectory()
160
161 if self.__fileName:
162 fileName = self.__fileName
163 originalFileName = self.__originalFileName
164 self.__toDownload = True
165 ask = False
166 else:
167 defaultFileName, originalFileName = \
168 self.__saveFileName(
169 documentLocation if savePage else downloadDirectory)
170 fileName = defaultFileName
171 self.__originalFileName = originalFileName
172 ask = True
173 self.__autoOpen = False
174
175 if not savePage:
176 from .DownloadAskActionDialog import DownloadAskActionDialog
177 url = self.__downloadItem.url()
178 mimetype = Utilities.MimeTypes.mimeType(originalFileName)
179 dlg = DownloadAskActionDialog(
180 QFileInfo(originalFileName).fileName(),
181 mimetype,
182 "{0}://{1}".format(url.scheme(), url.authority()),
183 self)
184
185 if dlg.exec_() == QDialog.Rejected or dlg.getAction() == "cancel":
186 self.progressBar.setVisible(False)
187 self.on_stopButton_clicked()
188 self.filenameLabel.setText(
189 self.tr("Download canceled: {0}").format(
190 QFileInfo(defaultFileName).fileName()))
191 self.__canceledFileSelect = True
192 self.__setDateTime()
193 return
194
195 if dlg.getAction() == "scan":
196 self.__mainWindow.requestVirusTotalScan(url)
197
198 self.progressBar.setVisible(False)
199 self.on_stopButton_clicked()
200 self.filenameLabel.setText(
201 self.tr("VirusTotal scan scheduled: {0}").format(
202 QFileInfo(defaultFileName).fileName()))
203 self.__canceledFileSelect = True
204 return
205
206 self.__autoOpen = dlg.getAction() == "open"
207
208 tempLocation = QStandardPaths.writableLocation(
209 QStandardPaths.TempLocation)
210 fileName = tempLocation + '/' + \
211 QFileInfo(fileName).completeBaseName()
212
213 if ask and not self.__autoOpen:
214 self.__gettingFileName = True
215 fileName = E5FileDialog.getSaveFileName(
216 None,
217 self.tr("Save File"),
218 defaultFileName,
219 "")
220 self.__gettingFileName = False
221 else:
222 # save page file name and format selection for Qt < 5.8.0
223 self.__autoOpen = False
224
225 filterList = [
226 self.tr("Web Archive (*.mhtml *.mht)"),
227 self.tr("HTML File (*.html *.htm)"),
228 self.tr("HTML File with all resources (*.html *.htm)"),
229 ]
230 extensionsList = [
231 # tuple of extensions for *nix and Windows
232 # keep in sync with filters list
233 (".mhtml", ".mht"),
234 (".html", ".htm"),
235 (".html", ".htm"),
236 ]
237 self.__gettingFileName = True
238 fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
239 None,
240 self.tr("Save Web Page"),
241 defaultFileName,
242 ";;".join(filterList),
243 None)
244 self.__gettingFileName = False
245 if fileName:
246 index = filterList.index(selectedFilter)
247 if index == 0:
248 self.__downloadItem.setSavePageFormat(
249 QWebEngineDownloadItem.MimeHtmlSaveFormat)
250 elif index == 1:
251 self.__downloadItem.setSavePageFormat(
252 QWebEngineDownloadItem.SingleHtmlSaveFormat)
253 else:
254 self.__downloadItem.setSavePageFormat(
255 QWebEngineDownloadItem.CompleteHtmlSaveFormat)
256 extension = os.path.splitext(fileName)[1]
257 if not extension:
258 # add the platform specific default extension
259 if Globals.isWindowsPlatform():
260 extensionsIndex = 1
261 else:
262 extensionsIndex = 0
263 extensions = extensionsList[index]
264 fileName += extensions[extensionsIndex]
265
266 if not fileName:
267 self.progressBar.setVisible(False)
268 self.on_stopButton_clicked()
269 self.filenameLabel.setText(
270 self.tr("Download canceled: {0}")
271 .format(QFileInfo(defaultFileName).fileName()))
272 self.__canceledFileSelect = True
273 self.__setDateTime()
274 return
275
276 self.__setFileName(fileName)
277
278 def __setFileName(self, fileName):
279 """
280 Private method to set the file name to save the download into.
281
282 @param fileName name of the file to save into
283 @type str
284 """
285 fileInfo = QFileInfo(fileName)
286 WebBrowserWindow.downloadManager()\
287 .setDownloadDirectory(fileInfo.absoluteDir().absolutePath())
288 self.filenameLabel.setText(fileInfo.fileName())
289
290 self.__fileName = fileName
291
292 # check file path for saving
293 saveDirPath = QFileInfo(self.__fileName).dir()
294 if not saveDirPath.exists():
295 if not saveDirPath.mkpath(saveDirPath.absolutePath()):
296 self.progressBar.setVisible(False)
297 self.on_stopButton_clicked()
298 self.infoLabel.setText(self.tr(
299 "Download directory ({0}) couldn't be created.")
300 .format(saveDirPath.absolutePath()))
301 self.__setDateTime()
302 return
303
304 self.filenameLabel.setText(QFileInfo(self.__fileName).fileName())
305
306 def __saveFileName(self, directory):
307 """
308 Private method to calculate a name for the file to download.
309
310 @param directory name of the directory to store the file into (string)
311 @return proposed filename and original filename (string, string)
312 """
313 path = self.__downloadItem.path()
314 info = QFileInfo(path)
315 baseName = info.completeBaseName()
316 endName = info.suffix()
317
318 origName = baseName
319 if endName:
320 origName += '.' + endName
321
322 name = os.path.join(directory, baseName)
323 if endName:
324 name += '.' + endName
325 return name, origName
326
327 @pyqtSlot(bool)
328 def on_pauseButton_clicked(self, checked):
329 """
330 Private slot to pause the download.
331
332 @param checked flag indicating the state of the button
333 @type bool
334 """
335 if checked:
336 self.__downloadItem.pause()
337 else:
338 self.__downloadItem.resume()
339
340 @pyqtSlot()
341 def on_stopButton_clicked(self):
342 """
343 Private slot to stop the download.
344 """
345 self.cancelDownload()
346
347 def cancelDownload(self):
348 """
349 Public slot to stop the download.
350 """
351 self.setUpdatesEnabled(False)
352 self.stopButton.setEnabled(False)
353 self.stopButton.setVisible(False)
354 self.openButton.setEnabled(False)
355 self.openButton.setVisible(False)
356 self.pauseButton.setEnabled(False)
357 self.pauseButton.setVisible(False)
358 self.setUpdatesEnabled(True)
359 self.__state = DownloadItem.DownloadCancelled
360 self.__downloadItem.cancel()
361 self.__setDateTime()
362 self.downloadFinished.emit(False)
363
364 @pyqtSlot()
365 def on_openButton_clicked(self):
366 """
367 Private slot to open the downloaded file.
368 """
369 self.openFile()
370
371 def openFile(self):
372 """
373 Public slot to open the downloaded file.
374 """
375 info = QFileInfo(self.__fileName)
376 url = QUrl.fromLocalFile(info.absoluteFilePath())
377 QDesktopServices.openUrl(url)
378
379 def openFolder(self):
380 """
381 Public slot to open the folder containing the downloaded file.
382 """
383 info = QFileInfo(self.__fileName)
384 url = QUrl.fromLocalFile(info.absolutePath())
385 QDesktopServices.openUrl(url)
386
387 def __downloadProgress(self, bytesReceived, bytesTotal):
388 """
389 Private method to show the download progress.
390
391 @param bytesReceived number of bytes received (integer)
392 @param bytesTotal number of total bytes (integer)
393 """
394 self.__bytesReceived = bytesReceived
395 self.__bytesTotal = bytesTotal
396 currentValue = 0
397 totalValue = 0
398 if bytesTotal > 0:
399 currentValue = bytesReceived * 100 / bytesTotal
400 totalValue = 100
401 self.progressBar.setValue(currentValue)
402 self.progressBar.setMaximum(totalValue)
403
404 self.progress.emit(currentValue, totalValue)
405 self.__updateInfoLabel()
406
407 def downloadProgress(self):
408 """
409 Public method to get the download progress.
410
411 @return current download progress
412 @rtype int
413 """
414 return self.progressBar.value()
415
416 def bytesTotal(self):
417 """
418 Public method to get the total number of bytes of the download.
419
420 @return total number of bytes (integer)
421 """
422 if self.__bytesTotal == -1:
423 self.__bytesTotal = self.__downloadItem.totalBytes()
424 return self.__bytesTotal
425
426 def bytesReceived(self):
427 """
428 Public method to get the number of bytes received.
429
430 @return number of bytes received (integer)
431 """
432 return self.__bytesReceived
433
434 def remainingTime(self):
435 """
436 Public method to get an estimation for the remaining time.
437
438 @return estimation for the remaining time (float)
439 """
440 if not self.downloading():
441 return -1.0
442
443 if self.bytesTotal() == -1:
444 return -1.0
445
446 cSpeed = self.currentSpeed()
447 if cSpeed != 0:
448 timeRemaining = (self.bytesTotal() - self.bytesReceived()) / cSpeed
449 else:
450 timeRemaining = 1
451
452 # ETA should never be 0
453 if timeRemaining == 0:
454 timeRemaining = 1
455
456 return timeRemaining
457
458 def currentSpeed(self):
459 """
460 Public method to get an estimation for the download speed.
461
462 @return estimation for the download speed (float)
463 """
464 if not self.downloading():
465 return -1.0
466
467 return self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed()
468
469 def __updateInfoLabel(self):
470 """
471 Private method to update the info label.
472 """
473 bytesTotal = self.bytesTotal()
474 running = not self.downloadedSuccessfully()
475
476 speed = self.currentSpeed()
477 timeRemaining = self.remainingTime()
478
479 info = ""
480 if running:
481 remaining = ""
482
483 if bytesTotal > 0:
484 remaining = timeString(timeRemaining)
485
486 info = self.tr("{0} of {1} ({2}/sec) {3}")\
487 .format(
488 dataString(self.__bytesReceived),
489 bytesTotal == -1 and self.tr("?") or
490 dataString(bytesTotal),
491 speedString(speed),
492 remaining)
493 else:
494 if self.__bytesReceived == bytesTotal or bytesTotal == -1:
495 info = self.tr("{0} downloaded")\
496 .format(dataString(self.__bytesReceived))
497 else:
498 info = self.tr("{0} of {1} - Stopped")\
499 .format(dataString(self.__bytesReceived),
500 dataString(bytesTotal))
501 self.infoLabel.setText(info)
502
503 def downloading(self):
504 """
505 Public method to determine, if a download is in progress.
506
507 @return flag indicating a download is in progress (boolean)
508 """
509 return self.__state == DownloadItem.Downloading
510
511 def downloadedSuccessfully(self):
512 """
513 Public method to check for a successful download.
514
515 @return flag indicating a successful download (boolean)
516 """
517 return self.__state == DownloadItem.DownloadSuccessful
518
519 def downloadCanceled(self):
520 """
521 Public method to check, if the download was cancelled.
522
523 @return flag indicating a canceled download (boolean)
524 """
525 return self.__state == DownloadItem.DownloadCancelled
526
527 def __finished(self):
528 """
529 Private slot to handle the download finished.
530 """
531 self.__finishedDownloading = True
532
533 noError = (self.__downloadItem.state() ==
534 QWebEngineDownloadItem.DownloadCompleted)
535
536 self.progressBar.setVisible(False)
537 self.pauseButton.setEnabled(False)
538 self.pauseButton.setVisible(False)
539 self.stopButton.setEnabled(False)
540 self.stopButton.setVisible(False)
541 self.openButton.setEnabled(noError)
542 self.openButton.setVisible(noError)
543 self.__state = DownloadItem.DownloadSuccessful
544 self.__updateInfoLabel()
545 self.__setDateTime()
546
547 self.__adjustSize()
548
549 self.statusChanged.emit()
550 self.downloadFinished.emit(True)
551
552 if self.__autoOpen:
553 self.openFile()
554
555 def canceledFileSelect(self):
556 """
557 Public method to check, if the user canceled the file selection.
558
559 @return flag indicating cancellation (boolean)
560 """
561 return self.__canceledFileSelect
562
563 def setIcon(self, icon):
564 """
565 Public method to set the download icon.
566
567 @param icon reference to the icon to be set (QIcon)
568 """
569 self.fileIcon.setPixmap(icon.pixmap(48, 48))
570
571 def fileName(self):
572 """
573 Public method to get the name of the output file.
574
575 @return name of the output file (string)
576 """
577 return self.__fileName
578
579 def absoluteFilePath(self):
580 """
581 Public method to get the absolute path of the output file.
582
583 @return absolute path of the output file (string)
584 """
585 return QFileInfo(self.__fileName).absoluteFilePath()
586
587 def getData(self):
588 """
589 Public method to get the relevant download data.
590
591 @return dictionary containing the URL, save location, done flag,
592 the URL of the related web page and the date and time of the
593 download
594 @rtype dict of {"URL": QUrl, "Location": str, "Done": bool,
595 "PageURL": QUrl, "Downloaded": QDateTime}
596 """
597 return {
598 "URL": self.__url,
599 "Location": QFileInfo(self.__fileName).filePath(),
600 "Done": self.downloadedSuccessfully(),
601 "PageURL": self.__pageUrl,
602 "Downloaded": self.__downloadedDateTime
603 }
604
605 def setData(self, data):
606 """
607 Public method to set the relevant download data.
608
609 @param data dictionary containing the URL, save location, done flag,
610 the URL of the related web page and the date and time of the
611 download
612 @type dict of {"URL": QUrl, "Location": str, "Done": bool,
613 "PageURL": QUrl, "Downloaded": QDateTime}
614 """
615 self.__url = data["URL"]
616 self.__fileName = data["Location"]
617 self.__pageUrl = data["PageURL"]
618
619 self.filenameLabel.setText(QFileInfo(self.__fileName).fileName())
620 self.infoLabel.setText(self.__fileName)
621
622 try:
623 self.__setDateTime(data["Downloaded"])
624 except KeyError:
625 self.__setDateTime(QDateTime())
626
627 self.pauseButton.setEnabled(False)
628 self.pauseButton.setVisible(False)
629 self.stopButton.setEnabled(False)
630 self.stopButton.setVisible(False)
631 self.openButton.setEnabled(data["Done"])
632 self.openButton.setVisible(data["Done"])
633 if data["Done"]:
634 self.__state = DownloadItem.DownloadSuccessful
635 else:
636 self.__state = DownloadItem.DownloadCancelled
637 self.progressBar.setVisible(False)
638
639 self.__adjustSize()
640
641 def getInfoData(self):
642 """
643 Public method to get the text of the info label.
644
645 @return text of the info label (string)
646 """
647 return self.infoLabel.text()
648
649 def getPageUrl(self):
650 """
651 Public method to get the URL of the download page.
652
653 @return URL of the download page (QUrl)
654 """
655 return self.__pageUrl
656
657 def __adjustSize(self):
658 """
659 Private method to adjust the size of the download item.
660 """
661 self.ensurePolished()
662
663 msh = self.minimumSizeHint()
664 self.resize(max(self.width(), msh.width()), msh.height())
665
666 def __setDateTime(self, dateTime=None):
667 """
668 Private method to set the download date and time.
669
670 @param dateTime date and time to be set
671 @type QDateTime
672 """
673 if dateTime is None:
674 self.__downloadedDateTime = QDateTime.currentDateTime()
675 else:
676 self.__downloadedDateTime = dateTime
677 if self.__downloadedDateTime.isValid():
678 labelText = self.__downloadedDateTime.toString("yyyy-MM-dd hh:mm")
679 self.datetimeLabel.setText(labelText)
680 self.datetimeLabel.show()
681 else:
682 self.datetimeLabel.clear()
683 self.datetimeLabel.hide()

eric ide

mercurial