eric7/WebBrowser/Download/DownloadItem.py

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

eric ide

mercurial