src/eric7/WebBrowser/Download/DownloadItem.py

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

eric ide

mercurial