WebBrowser/Download/DownloadItem.py

branch
QtWebEngine
changeset 4768
57da9217196b
parent 4631
5c1a96925da4
child 4769
2b6f7e026cdc
equal deleted inserted replaced
4767:0bace7c5ebc9 4768:57da9217196b
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2016 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 try:
12 str = unicode
13 except NameError:
14 pass
15
16 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QTime, QFile, QFileInfo, \
17 QUrl, QIODevice, QCryptographicHash, PYQT_VERSION_STR
18 from PyQt5.QtGui import QPalette, QDesktopServices
19 from PyQt5.QtWidgets import QWidget, QStyle, QDialog
20 from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
21
22 from E5Gui import E5FileDialog
23
24 from .Ui_DownloadItem import Ui_DownloadItem
25
26 from .DownloadUtilities import timeString, dataString
27 ##from ..HelpUtilities import parseContentDisposition
28
29 import UI.PixmapCache
30 import Preferences
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() emitted when a download finished
39 @signal progress(int, int) emitted to signal the download progress
40 """
41 statusChanged = pyqtSignal()
42 downloadFinished = pyqtSignal()
43 progress = pyqtSignal(int, int)
44
45 Downloading = 0
46 DownloadSuccessful = 1
47 DownloadCancelled = 2
48
49 def __init__(self, reply=None, requestFilename=False, webPage=None,
50 download=False, parent=None, mainWindow=None):
51 """
52 Constructor
53
54 @keyparam reply reference to the network reply object (QNetworkReply)
55 @keyparam requestFilename flag indicating to ask the user for a
56 filename (boolean)
57 @keyparam webPage reference to the web page object the download
58 originated from (QWebPage)
59 @keyparam download flag indicating a download operation (boolean)
60 @keyparam parent reference to the parent widget (QWidget)
61 @keyparam mainWindow reference to the main window (HelpWindow)
62 """
63 super(DownloadItem, self).__init__(parent)
64 self.setupUi(self)
65
66 p = self.infoLabel.palette()
67 p.setColor(QPalette.Text, Qt.darkGray)
68 self.infoLabel.setPalette(p)
69
70 self.progressBar.setMaximum(0)
71
72 self.__isFtpDownload = reply is not None and \
73 reply.url().scheme() == "ftp"
74
75 self.tryAgainButton.setIcon(UI.PixmapCache.getIcon("restart.png"))
76 self.tryAgainButton.setEnabled(False)
77 self.tryAgainButton.setVisible(False)
78 self.stopButton.setIcon(UI.PixmapCache.getIcon("stopLoading.png"))
79 self.pauseButton.setIcon(UI.PixmapCache.getIcon("pause.png"))
80 self.openButton.setIcon(UI.PixmapCache.getIcon("open.png"))
81 self.openButton.setEnabled(False)
82 self.openButton.setVisible(False)
83 if self.__isFtpDownload:
84 self.stopButton.setEnabled(False)
85 self.stopButton.setVisible(False)
86 self.pauseButton.setEnabled(False)
87 self.pauseButton.setVisible(False)
88
89 self.__state = DownloadItem.Downloading
90
91 icon = self.style().standardIcon(QStyle.SP_FileIcon)
92 self.fileIcon.setPixmap(icon.pixmap(48, 48))
93
94 self.__mainWindow = mainWindow
95 self.__reply = reply
96 self.__requestFilename = requestFilename
97 self.__page = webPage
98 self.__pageUrl = webPage and webPage.mainFrame().url() or QUrl()
99 self.__toDownload = download
100 self.__bytesReceived = 0
101 self.__bytesTotal = -1
102 self.__downloadTime = QTime()
103 self.__output = QFile()
104 self.__fileName = ""
105 self.__originalFileName = ""
106 self.__startedSaving = False
107 self.__finishedDownloading = False
108 self.__gettingFileName = False
109 self.__canceledFileSelect = False
110 self.__autoOpen = False
111
112 self.__sha1Hash = QCryptographicHash(QCryptographicHash.Sha1)
113 self.__md5Hash = QCryptographicHash(QCryptographicHash.Md5)
114
115 if not requestFilename:
116 self.__requestFilename = \
117 Preferences.getUI("RequestDownloadFilename")
118
119 self.__initialize()
120
121 def __initialize(self, tryAgain=False):
122 """
123 Private method to (re)initialize the widget.
124
125 @param tryAgain flag indicating a retry (boolean)
126 """
127 if self.__reply is None:
128 return
129
130 self.__startedSaving = False
131 self.__finishedDownloading = False
132 self.__bytesReceived = 0
133 self.__bytesTotal = -1
134
135 self.__sha1Hash.reset()
136 self.__md5Hash.reset()
137
138 # start timer for the download estimation
139 self.__downloadTime.start()
140
141 # attach to the reply object
142 self.__url = self.__reply.url()
143 self.__reply.setParent(self)
144 self.__reply.setReadBufferSize(16 * 1024 * 1024)
145 self.__reply.readyRead.connect(self.__readyRead)
146 self.__reply.error.connect(self.__networkError)
147 self.__reply.downloadProgress.connect(self.__downloadProgress)
148 self.__reply.metaDataChanged.connect(self.__metaDataChanged)
149 self.__reply.finished.connect(self.__finished)
150
151 # reset info
152 self.infoLabel.clear()
153 self.progressBar.setValue(0)
154 self.__getFileName()
155
156 if self.__reply.error() != QNetworkReply.NoError:
157 self.__networkError()
158 self.__finished()
159
160 def __getFileName(self):
161 """
162 Private method to get the file name to save to from the user.
163 """
164 if self.__gettingFileName:
165 return
166
167 from WebBrowser.WebBrowserWindow import WebBrowserWindow
168 downloadDirectory = WebBrowserWindow\
169 .downloadManager().downloadDirectory()
170
171 if self.__fileName:
172 fileName = self.__fileName
173 originalFileName = self.__originalFileName
174 self.__toDownload = True
175 ask = False
176 else:
177 defaultFileName, originalFileName = \
178 self.__saveFileName(downloadDirectory)
179 fileName = defaultFileName
180 self.__originalFileName = originalFileName
181 ask = True
182 self.__autoOpen = False
183 if not self.__toDownload:
184 from .DownloadAskActionDialog import DownloadAskActionDialog
185 url = self.__reply.url()
186 dlg = DownloadAskActionDialog(
187 QFileInfo(originalFileName).fileName(),
188 self.__reply.header(QNetworkRequest.ContentTypeHeader),
189 "{0}://{1}".format(url.scheme(), url.authority()),
190 self)
191 if dlg.exec_() == QDialog.Rejected or dlg.getAction() == "cancel":
192 self.progressBar.setVisible(False)
193 self.__reply.close()
194 self.on_stopButton_clicked()
195 self.filenameLabel.setText(
196 self.tr("Download canceled: {0}").format(
197 QFileInfo(defaultFileName).fileName()))
198 self.__canceledFileSelect = True
199 return
200
201 if dlg.getAction() == "scan":
202 self.__mainWindow.requestVirusTotalScan(url)
203
204 self.progressBar.setVisible(False)
205 self.__reply.close()
206 self.on_stopButton_clicked()
207 self.filenameLabel.setText(
208 self.tr("VirusTotal scan scheduled: {0}").format(
209 QFileInfo(defaultFileName).fileName()))
210 self.__canceledFileSelect = True
211 return
212
213 self.__autoOpen = dlg.getAction() == "open"
214 if PYQT_VERSION_STR >= "5.0.0":
215 from PyQt5.QtCore import QStandardPaths
216 tempLocation = QStandardPaths.storageLocation(
217 QStandardPaths.TempLocation)
218 else:
219 from PyQt5.QtGui import QDesktopServices
220 tempLocation = QDesktopServices.storageLocation(
221 QDesktopServices.TempLocation)
222 fileName = tempLocation + '/' + \
223 QFileInfo(fileName).completeBaseName()
224
225 if ask and not self.__autoOpen and self.__requestFilename:
226 self.__gettingFileName = True
227 fileName = E5FileDialog.getSaveFileName(
228 None,
229 self.tr("Save File"),
230 defaultFileName,
231 "")
232 self.__gettingFileName = False
233 if not fileName:
234 self.progressBar.setVisible(False)
235 self.__reply.close()
236 self.on_stopButton_clicked()
237 self.filenameLabel.setText(
238 self.tr("Download canceled: {0}")
239 .format(QFileInfo(defaultFileName).fileName()))
240 self.__canceledFileSelect = True
241 return
242
243 fileInfo = QFileInfo(fileName)
244 WebBrowserWindow.downloadManager()\
245 .setDownloadDirectory(fileInfo.absoluteDir().absolutePath())
246 self.filenameLabel.setText(fileInfo.fileName())
247
248 self.__output.setFileName(fileName + ".part")
249 self.__fileName = fileName
250
251 # check file path for saving
252 saveDirPath = QFileInfo(self.__fileName).dir()
253 if not saveDirPath.exists():
254 if not saveDirPath.mkpath(saveDirPath.absolutePath()):
255 self.progressBar.setVisible(False)
256 self.on_stopButton_clicked()
257 self.infoLabel.setText(self.tr(
258 "Download directory ({0}) couldn't be created.")
259 .format(saveDirPath.absolutePath()))
260 return
261
262 self.filenameLabel.setText(QFileInfo(self.__fileName).fileName())
263 if self.__requestFilename:
264 self.__readyRead()
265
266 def __saveFileName(self, directory):
267 """
268 Private method to calculate a name for the file to download.
269
270 @param directory name of the directory to store the file into (string)
271 @return proposed filename and original filename (string, string)
272 """
273 path = parseContentDisposition(self.__reply)
274 info = QFileInfo(path)
275 baseName = info.completeBaseName()
276 endName = info.suffix()
277
278 origName = baseName
279 if endName:
280 origName += '.' + endName
281
282 name = directory + baseName
283 if endName:
284 name += '.' + endName
285 if not self.__requestFilename:
286 # do not overwrite, if the user is not being asked
287 i = 1
288 while QFile.exists(name):
289 # file exists already, don't overwrite
290 name = directory + baseName + ('-{0:d}'.format(i))
291 if endName:
292 name += '.' + endName
293 i += 1
294 return name, origName
295
296 def __open(self):
297 """
298 Private slot to open the downloaded file.
299 """
300 info = QFileInfo(self.__output)
301 url = QUrl.fromLocalFile(info.absoluteFilePath())
302 QDesktopServices.openUrl(url)
303
304 @pyqtSlot()
305 def on_tryAgainButton_clicked(self):
306 """
307 Private slot to retry the download.
308 """
309 self.retry()
310
311 def retry(self):
312 """
313 Public slot to retry the download.
314 """
315 if not self.tryAgainButton.isEnabled():
316 return
317
318 self.tryAgainButton.setEnabled(False)
319 self.tryAgainButton.setVisible(False)
320 self.openButton.setEnabled(False)
321 self.openButton.setVisible(False)
322 if not self.__isFtpDownload:
323 self.stopButton.setEnabled(True)
324 self.stopButton.setVisible(True)
325 self.pauseButton.setEnabled(True)
326 self.pauseButton.setVisible(True)
327 self.progressBar.setVisible(True)
328
329 if self.__page:
330 nam = self.__page.networkAccessManager()
331 else:
332 from WebBrowser.WebBrowserWindow import WebBrowserWindow
333 nam = WebBrowserWindow.networkAccessManager()
334 reply = nam.get(QNetworkRequest(self.__url))
335 if self.__output.exists():
336 self.__output.remove()
337 self.__output = QFile()
338 self.__reply = reply
339 self.__initialize(tryAgain=True)
340 self.__state = DownloadItem.Downloading
341 self.statusChanged.emit()
342
343 @pyqtSlot(bool)
344 def on_pauseButton_clicked(self, checked):
345 """
346 Private slot to pause the download.
347
348 @param checked flag indicating the state of the button (boolean)
349 """
350 if checked:
351 self.__reply.readyRead.disconnect(self.__readyRead)
352 self.__reply.setReadBufferSize(16 * 1024)
353 else:
354 self.__reply.readyRead.connect(self.__readyRead)
355 self.__reply.setReadBufferSize(16 * 1024 * 1024)
356 self.__readyRead()
357
358 @pyqtSlot()
359 def on_stopButton_clicked(self):
360 """
361 Private slot to stop the download.
362 """
363 self.cancelDownload()
364
365 def cancelDownload(self):
366 """
367 Public slot to stop the download.
368 """
369 self.setUpdatesEnabled(False)
370 if not self.__isFtpDownload:
371 self.stopButton.setEnabled(False)
372 self.stopButton.setVisible(False)
373 self.pauseButton.setEnabled(False)
374 self.pauseButton.setVisible(False)
375 self.tryAgainButton.setEnabled(True)
376 self.tryAgainButton.setVisible(True)
377 self.openButton.setEnabled(False)
378 self.openButton.setVisible(False)
379 self.setUpdatesEnabled(True)
380 self.__state = DownloadItem.DownloadCancelled
381 self.__reply.abort()
382 self.downloadFinished.emit()
383
384 @pyqtSlot()
385 def on_openButton_clicked(self):
386 """
387 Private slot to open the downloaded file.
388 """
389 self.openFile()
390
391 def openFile(self):
392 """
393 Public slot to open the downloaded file.
394 """
395 info = QFileInfo(self.__fileName)
396 url = QUrl.fromLocalFile(info.absoluteFilePath())
397 QDesktopServices.openUrl(url)
398
399 def openFolder(self):
400 """
401 Public slot to open the folder containing the downloaded file.
402 """
403 info = QFileInfo(self.__fileName)
404 url = QUrl.fromLocalFile(info.absolutePath())
405 QDesktopServices.openUrl(url)
406
407 def __readyRead(self):
408 """
409 Private slot to read the available data.
410 """
411 if self.__requestFilename and not self.__output.fileName():
412 return
413
414 if not self.__output.isOpen():
415 # in case someone else has already put a file there
416 if not self.__requestFilename:
417 self.__getFileName()
418 if not self.__output.open(QIODevice.WriteOnly):
419 self.infoLabel.setText(
420 self.tr("Error opening save file: {0}")
421 .format(self.__output.errorString()))
422 self.on_stopButton_clicked()
423 self.statusChanged.emit()
424 return
425 self.statusChanged.emit()
426
427 buffer = self.__reply.readAll()
428 self.__sha1Hash.addData(buffer)
429 self.__md5Hash.addData(buffer)
430 bytesWritten = self.__output.write(buffer)
431 if bytesWritten == -1:
432 self.infoLabel.setText(
433 self.tr("Error saving: {0}")
434 .format(self.__output.errorString()))
435 self.on_stopButton_clicked()
436 else:
437 self.__startedSaving = True
438 if self.__finishedDownloading:
439 self.__finished()
440
441 def __networkError(self):
442 """
443 Private slot to handle a network error.
444 """
445 self.infoLabel.setText(
446 self.tr("Network Error: {0}")
447 .format(self.__reply.errorString()))
448 self.tryAgainButton.setEnabled(True)
449 self.tryAgainButton.setVisible(True)
450 self.downloadFinished.emit()
451
452 def __metaDataChanged(self):
453 """
454 Private slot to handle a change of the meta data.
455 """
456 locationHeader = self.__reply.header(QNetworkRequest.LocationHeader)
457 if locationHeader and locationHeader.isValid():
458 self.__url = QUrl(locationHeader)
459 from WebBrowser.WebBrowserWindow import WebBrowserWindow
460 self.__reply = WebBrowserWindow\
461 .networkAccessManager().get(QNetworkRequest(self.__url))
462 self.__initialize()
463
464 def __downloadProgress(self, bytesReceived, bytesTotal):
465 """
466 Private method to show the download progress.
467
468 @param bytesReceived number of bytes received (integer)
469 @param bytesTotal number of total bytes (integer)
470 """
471 self.__bytesReceived = bytesReceived
472 self.__bytesTotal = bytesTotal
473 currentValue = 0
474 totalValue = 0
475 if bytesTotal > 0:
476 currentValue = bytesReceived * 100 / bytesTotal
477 totalValue = 100
478 self.progressBar.setValue(currentValue)
479 self.progressBar.setMaximum(totalValue)
480
481 self.progress.emit(currentValue, totalValue)
482 self.__updateInfoLabel()
483
484 def bytesTotal(self):
485 """
486 Public method to get the total number of bytes of the download.
487
488 @return total number of bytes (integer)
489 """
490 if self.__bytesTotal == -1:
491 self.__bytesTotal = self.__reply.header(
492 QNetworkRequest.ContentLengthHeader)
493 if self.__bytesTotal is None:
494 self.__bytesTotal = -1
495 return self.__bytesTotal
496
497 def bytesReceived(self):
498 """
499 Public method to get the number of bytes received.
500
501 @return number of bytes received (integer)
502 """
503 return self.__bytesReceived
504
505 def remainingTime(self):
506 """
507 Public method to get an estimation for the remaining time.
508
509 @return estimation for the remaining time (float)
510 """
511 if not self.downloading():
512 return -1.0
513
514 if self.bytesTotal() == -1:
515 return -1.0
516
517 cSpeed = self.currentSpeed()
518 if cSpeed != 0:
519 timeRemaining = (self.bytesTotal() - self.bytesReceived()) / cSpeed
520 else:
521 timeRemaining = 1
522
523 # ETA should never be 0
524 if timeRemaining == 0:
525 timeRemaining = 1
526
527 return timeRemaining
528
529 def currentSpeed(self):
530 """
531 Public method to get an estimation for the download speed.
532
533 @return estimation for the download speed (float)
534 """
535 if not self.downloading():
536 return -1.0
537
538 return self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed()
539
540 def __updateInfoLabel(self):
541 """
542 Private method to update the info label.
543 """
544 if self.__reply.error() != QNetworkReply.NoError:
545 return
546
547 bytesTotal = self.bytesTotal()
548 running = not self.downloadedSuccessfully()
549
550 speed = self.currentSpeed()
551 timeRemaining = self.remainingTime()
552
553 info = ""
554 if running:
555 remaining = ""
556
557 if bytesTotal > 0:
558 remaining = timeString(timeRemaining)
559
560 info = self.tr("{0} of {1} ({2}/sec)\n{3}")\
561 .format(
562 dataString(self.__bytesReceived),
563 bytesTotal == -1 and self.tr("?")
564 or dataString(bytesTotal),
565 dataString(int(speed)),
566 remaining)
567 else:
568 if self.__bytesReceived == bytesTotal or bytesTotal == -1:
569 info = self.tr("{0} downloaded\nSHA1: {1}\nMD5: {2}")\
570 .format(dataString(self.__output.size()),
571 str(self.__sha1Hash.result().toHex(),
572 encoding="ascii"),
573 str(self.__md5Hash.result().toHex(),
574 encoding="ascii")
575 )
576 else:
577 info = self.tr("{0} of {1} - Stopped")\
578 .format(dataString(self.__bytesReceived),
579 dataString(bytesTotal))
580 self.infoLabel.setText(info)
581
582 def downloading(self):
583 """
584 Public method to determine, if a download is in progress.
585
586 @return flag indicating a download is in progress (boolean)
587 """
588 return self.__state == DownloadItem.Downloading
589
590 def downloadedSuccessfully(self):
591 """
592 Public method to check for a successful download.
593
594 @return flag indicating a successful download (boolean)
595 """
596 return self.__state == DownloadItem.DownloadSuccessful
597
598 def downloadCanceled(self):
599 """
600 Public method to check, if the download was cancelled.
601
602 @return flag indicating a canceled download (boolean)
603 """
604 return self.__state == DownloadItem.DownloadCancelled
605
606 def __finished(self):
607 """
608 Private slot to handle the download finished.
609 """
610 self.__finishedDownloading = True
611 if not self.__startedSaving:
612 return
613
614 noError = self.__reply.error() == QNetworkReply.NoError
615
616 self.progressBar.setVisible(False)
617 if not self.__isFtpDownload:
618 self.stopButton.setEnabled(False)
619 self.stopButton.setVisible(False)
620 self.pauseButton.setEnabled(False)
621 self.pauseButton.setVisible(False)
622 self.openButton.setEnabled(noError)
623 self.openButton.setVisible(noError)
624 self.__output.close()
625 if QFile.exists(self.__fileName):
626 QFile.remove(self.__fileName)
627 self.__output.rename(self.__fileName)
628 self.__updateInfoLabel()
629 self.__state = DownloadItem.DownloadSuccessful
630 self.statusChanged.emit()
631 self.downloadFinished.emit()
632
633 if self.__autoOpen:
634 self.__open()
635
636 def canceledFileSelect(self):
637 """
638 Public method to check, if the user canceled the file selection.
639
640 @return flag indicating cancellation (boolean)
641 """
642 return self.__canceledFileSelect
643
644 def setIcon(self, icon):
645 """
646 Public method to set the download icon.
647
648 @param icon reference to the icon to be set (QIcon)
649 """
650 self.fileIcon.setPixmap(icon.pixmap(48, 48))
651
652 def fileName(self):
653 """
654 Public method to get the name of the output file.
655
656 @return name of the output file (string)
657 """
658 return self.__fileName
659
660 def absoluteFilePath(self):
661 """
662 Public method to get the absolute path of the output file.
663
664 @return absolute path of the output file (string)
665 """
666 return QFileInfo(self.__fileName).absoluteFilePath()
667
668 def getData(self):
669 """
670 Public method to get the relevant download data.
671
672 @return tuple of URL, save location, flag and the
673 URL of the related web page (QUrl, string, boolean,QUrl)
674 """
675 return (self.__url, QFileInfo(self.__fileName).filePath(),
676 self.downloadedSuccessfully(), self.__pageUrl)
677
678 def setData(self, data):
679 """
680 Public method to set the relevant download data.
681
682 @param data tuple of URL, save location, flag and the
683 URL of the related web page (QUrl, string, boolean, QUrl)
684 """
685 self.__url = data[0]
686 self.__fileName = data[1]
687 self.__pageUrl = data[3]
688 self.__isFtpDownload = self.__url.scheme() == "ftp"
689
690 self.filenameLabel.setText(QFileInfo(self.__fileName).fileName())
691 self.infoLabel.setText(self.__fileName)
692
693 self.stopButton.setEnabled(False)
694 self.stopButton.setVisible(False)
695 self.pauseButton.setEnabled(False)
696 self.pauseButton.setVisible(False)
697 self.openButton.setEnabled(data[2])
698 self.openButton.setVisible(data[2])
699 self.tryAgainButton.setEnabled(not data[2])
700 self.tryAgainButton.setVisible(not data[2])
701 if data[2]:
702 self.__state = DownloadItem.DownloadSuccessful
703 else:
704 self.__state = DownloadItem.DownloadCancelled
705 self.progressBar.setVisible(False)
706
707 def getInfoData(self):
708 """
709 Public method to get the text of the info label.
710
711 @return text of the info label (string)
712 """
713 return self.infoLabel.text()
714
715 def getPageUrl(self):
716 """
717 Public method to get the URL of the download page.
718
719 @return URL of the download page (QUrl)
720 """
721 return self.__pageUrl

eric ide

mercurial