eric6/Helpviewer/Download/DownloadManager.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
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, Qt, QModelIndex, QFileInfo
13 from PyQt5.QtGui import QCursor
14 from PyQt5.QtWidgets import QDialog, QStyle, QFileIconProvider, QMenu, \
15 QApplication
16 from PyQt5.QtNetwork import QNetworkRequest
17 from PyQt5.QtWebKit import QWebSettings
18
19 from E5Gui import E5MessageBox
20
21 from .Ui_DownloadManager import Ui_DownloadManager
22
23 from .DownloadModel import DownloadModel
24
25 import Helpviewer.HelpWindow
26
27 from Utilities.AutoSaver import AutoSaver
28 import UI.PixmapCache
29 import Preferences
30
31
32 class DownloadManager(QDialog, Ui_DownloadManager):
33 """
34 Class implementing the download manager.
35 """
36 RemoveNever = 0
37 RemoveExit = 1
38 RemoveSuccessFullDownload = 2
39
40 def __init__(self, parent=None):
41 """
42 Constructor
43
44 @param parent reference to the parent widget (QWidget)
45 """
46 super(DownloadManager, self).__init__(parent)
47 self.setupUi(self)
48 self.setWindowFlags(Qt.Window)
49
50 self.__saveTimer = AutoSaver(self, self.save)
51
52 self.__model = DownloadModel(self)
53 self.__manager = Helpviewer.HelpWindow.HelpWindow\
54 .networkAccessManager()
55
56 self.__iconProvider = None
57 self.__downloads = []
58 self.__downloadDirectory = ""
59 self.__loaded = False
60
61 self.__rowHeightMultiplier = 1.1
62
63 self.setDownloadDirectory(Preferences.getUI("DownloadPath"))
64
65 self.downloadsView.setShowGrid(False)
66 self.downloadsView.verticalHeader().hide()
67 self.downloadsView.horizontalHeader().hide()
68 self.downloadsView.setAlternatingRowColors(True)
69 self.downloadsView.horizontalHeader().setStretchLastSection(True)
70 self.downloadsView.setModel(self.__model)
71 self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu)
72 self.downloadsView.customContextMenuRequested.connect(
73 self.__customContextMenuRequested)
74
75 self.__load()
76
77 def __customContextMenuRequested(self, pos):
78 """
79 Private slot to handle the context menu request for the bookmarks tree.
80
81 @param pos position the context menu was requested (QPoint)
82 """
83 menu = QMenu()
84
85 selectedRowsCount = len(
86 self.downloadsView.selectionModel().selectedRows())
87
88 if selectedRowsCount == 1:
89 row = self.downloadsView.selectionModel().selectedRows()[0].row()
90 itm = self.__downloads[row]
91 if itm.downloadCanceled():
92 menu.addAction(
93 UI.PixmapCache.getIcon("restart.png"),
94 self.tr("Retry"), self.__contextMenuRetry)
95 else:
96 if itm.downloadedSuccessfully():
97 menu.addAction(
98 UI.PixmapCache.getIcon("open.png"),
99 self.tr("Open"), self.__contextMenuOpen)
100 elif itm.downloading():
101 menu.addAction(
102 UI.PixmapCache.getIcon("stopLoading.png"),
103 self.tr("Cancel"), self.__contextMenuCancel)
104 menu.addSeparator()
105 menu.addAction(
106 self.tr("Open Containing Folder"),
107 self.__contextMenuOpenFolder)
108 menu.addSeparator()
109 menu.addAction(
110 self.tr("Go to Download Page"),
111 self.__contextMenuGotoPage)
112 menu.addAction(
113 self.tr("Copy Download Link"),
114 self.__contextMenuCopyLink)
115 menu.addSeparator()
116 menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll)
117 if selectedRowsCount > 1 or \
118 (selectedRowsCount == 1 and
119 not self.__downloads[
120 self.downloadsView.selectionModel().selectedRows()[0].row()]
121 .downloading()):
122 menu.addSeparator()
123 menu.addAction(
124 self.tr("Remove From List"),
125 self.__contextMenuRemoveSelected)
126
127 menu.exec_(QCursor.pos())
128
129 def shutdown(self):
130 """
131 Public method to stop the download manager.
132 """
133 self.__saveTimer.changeOccurred()
134 self.__saveTimer.saveIfNeccessary()
135 self.close()
136
137 def activeDownloads(self):
138 """
139 Public method to get the number of active downloads.
140
141 @return number of active downloads (integer)
142 """
143 count = 0
144
145 for download in self.__downloads:
146 if download.downloading():
147 count += 1
148 return count
149
150 def allowQuit(self):
151 """
152 Public method to check, if it is ok to quit.
153
154 @return flag indicating allowance to quit (boolean)
155 """
156 if self.activeDownloads() > 0:
157 res = E5MessageBox.yesNo(
158 self,
159 self.tr(""),
160 self.tr("""There are %n downloads in progress.\n"""
161 """Do you want to quit anyway?""", "",
162 self.activeDownloads()),
163 icon=E5MessageBox.Warning)
164 if not res:
165 self.show()
166 return False
167 return True
168
169 def download(self, requestOrUrl, requestFileName=False, mainWindow=None):
170 """
171 Public method to download a file.
172
173 @param requestOrUrl reference to a request object (QNetworkRequest)
174 or a URL to be downloaded (QUrl)
175 @keyparam requestFileName flag indicating to ask for the
176 download file name (boolean)
177 @keyparam mainWindow reference to the main window (HelpWindow)
178 """
179 request = QNetworkRequest(requestOrUrl)
180 if request.url().isEmpty():
181 return
182 self.handleUnsupportedContent(
183 self.__manager.get(request),
184 requestFileName=requestFileName,
185 download=True,
186 mainWindow=mainWindow)
187
188 def handleUnsupportedContent(self, reply, requestFileName=False,
189 webPage=None, download=False,
190 mainWindow=None):
191 """
192 Public method to handle unsupported content by downloading the
193 referenced resource.
194
195 @param reply reference to the reply object (QNetworkReply)
196 @keyparam requestFileName indicating to ask for a filename
197 (boolean)
198 @keyparam webPage reference to the web page (HelpWebPage)
199 @keyparam download flag indicating a download request (boolean)
200 @keyparam mainWindow reference to the main window (HelpWindow)
201 """
202 if reply is None or reply.url().isEmpty():
203 return
204
205 size = reply.header(QNetworkRequest.ContentLengthHeader)
206 if size == 0:
207 return
208
209 from .DownloadItem import DownloadItem
210 itm = DownloadItem(
211 reply=reply, requestFilename=requestFileName,
212 webPage=webPage, download=download, parent=self,
213 mainWindow=mainWindow)
214 self.__addItem(itm)
215
216 if itm.canceledFileSelect():
217 return
218
219 if not self.isVisible():
220 self.show()
221
222 self.activateWindow()
223 self.raise_()
224
225 def __addItem(self, itm, append=False):
226 """
227 Private method to add a download to the list of downloads.
228
229 @param itm reference to the download item
230 @type DownloadItem
231 @param append flag indicating to append the item
232 @type bool
233 """
234 itm.statusChanged.connect(lambda: self.__updateRow(itm))
235 itm.downloadFinished.connect(self.__finished)
236
237 # insert at top of window
238 if append:
239 row = len(self.__downloads)
240 else:
241 row = 0
242 self.__model.beginInsertRows(QModelIndex(), row, row)
243 if append:
244 self.__downloads.append(itm)
245 else:
246 self.__downloads.insert(0, itm)
247 self.__model.endInsertRows()
248
249 self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm)
250 icon = self.style().standardIcon(QStyle.SP_FileIcon)
251 itm.setIcon(icon)
252 self.downloadsView.setRowHeight(
253 row, itm.sizeHint().height() * self.__rowHeightMultiplier)
254 # just in case the download finished before the constructor returned
255 self.__updateRow(itm)
256 self.changeOccurred()
257 self.__updateActiveItemCount()
258
259 def __updateRow(self, itm):
260 """
261 Private slot to update a download item.
262
263 @param itm reference to the download item
264 @type DownloadItem
265 """
266 if itm not in self.__downloads:
267 return
268
269 row = self.__downloads.index(itm)
270
271 if self.__iconProvider is None:
272 self.__iconProvider = QFileIconProvider()
273
274 icon = self.__iconProvider.icon(QFileInfo(itm.fileName()))
275 if icon.isNull():
276 icon = self.style().standardIcon(QStyle.SP_FileIcon)
277 itm.setIcon(icon)
278
279 self.downloadsView.setRowHeight(
280 row,
281 itm.minimumSizeHint().height() * self.__rowHeightMultiplier)
282
283 remove = False
284 globalSettings = QWebSettings.globalSettings()
285 if not itm.downloading() and \
286 globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled):
287 remove = True
288
289 if itm.downloadedSuccessfully() and \
290 self.removePolicy() == DownloadManager.RemoveSuccessFullDownload:
291 remove = True
292
293 if remove:
294 self.__model.removeRow(row)
295
296 self.cleanupButton.setEnabled(
297 (len(self.__downloads) - self.activeDownloads()) > 0)
298
299 # record the change
300 self.changeOccurred()
301
302 def removePolicy(self):
303 """
304 Public method to get the remove policy.
305
306 @return remove policy (integer)
307 """
308 return Preferences.getHelp("DownloadManagerRemovePolicy")
309
310 def setRemovePolicy(self, policy):
311 """
312 Public method to set the remove policy.
313
314 @param policy policy to be set
315 (DownloadManager.RemoveExit, DownloadManager.RemoveNever,
316 DownloadManager.RemoveSuccessFullDownload)
317 """
318 assert policy in (DownloadManager.RemoveExit,
319 DownloadManager.RemoveNever,
320 DownloadManager.RemoveSuccessFullDownload)
321
322 if policy == self.removePolicy():
323 return
324
325 Preferences.setHelp("DownloadManagerRemovePolicy", self.policy)
326
327 def save(self):
328 """
329 Public method to save the download settings.
330 """
331 if not self.__loaded:
332 return
333
334 Preferences.setHelp("DownloadManagerSize", self.size())
335 Preferences.setHelp("DownloadManagerPosition", self.pos())
336 if self.removePolicy() == DownloadManager.RemoveExit:
337 return
338
339 downloads = []
340 for download in self.__downloads:
341 downloads.append(download.getData())
342 Preferences.setHelp("DownloadManagerDownloads", downloads)
343
344 def __load(self):
345 """
346 Private method to load the download settings.
347 """
348 if self.__loaded:
349 return
350
351 size = Preferences.getHelp("DownloadManagerSize")
352 if size.isValid():
353 self.resize(size)
354 pos = Preferences.getHelp("DownloadManagerPosition")
355 self.move(pos)
356
357 downloads = Preferences.getHelp("DownloadManagerDownloads")
358 for download in downloads:
359 if not download[0].isEmpty() and \
360 download[1] != "":
361 from .DownloadItem import DownloadItem
362 itm = DownloadItem(parent=self)
363 itm.setData(download)
364 self.__addItem(itm, append=True)
365 self.cleanupButton.setEnabled(
366 (len(self.__downloads) - self.activeDownloads()) > 0)
367
368 self.__loaded = True
369 self.__updateActiveItemCount()
370
371 def cleanup(self):
372 """
373 Public slot to cleanup the downloads.
374 """
375 self.on_cleanupButton_clicked()
376
377 @pyqtSlot()
378 def on_cleanupButton_clicked(self):
379 """
380 Private slot cleanup the downloads.
381 """
382 if len(self.__downloads) == 0:
383 return
384
385 self.__model.removeRows(0, len(self.__downloads))
386 if len(self.__downloads) == 0 and \
387 self.__iconProvider is not None:
388 self.__iconProvider = None
389
390 self.changeOccurred()
391 self.__updateActiveItemCount()
392
393 def __updateItemCount(self):
394 """
395 Private method to update the count label.
396 """
397 count = len(self.__downloads)
398 self.countLabel.setText(self.tr("%n Download(s)", "", count))
399
400 def __updateActiveItemCount(self):
401 """
402 Private method to update the window title.
403 """
404 count = self.activeDownloads()
405 if count > 0:
406 self.setWindowTitle(
407 self.tr("Downloading %n file(s)", "", count))
408 else:
409 self.setWindowTitle(self.tr("Downloads"))
410
411 def __finished(self):
412 """
413 Private slot to handle a finished download.
414 """
415 self.__updateActiveItemCount()
416 if self.isVisible():
417 QApplication.alert(self)
418
419 def setDownloadDirectory(self, directory):
420 """
421 Public method to set the current download directory.
422
423 @param directory current download directory (string)
424 """
425 self.__downloadDirectory = directory
426 if self.__downloadDirectory != "":
427 self.__downloadDirectory += "/"
428
429 def downloadDirectory(self):
430 """
431 Public method to get the current download directory.
432
433 @return current download directory (string)
434 """
435 return self.__downloadDirectory
436
437 def count(self):
438 """
439 Public method to get the number of downloads.
440
441 @return number of downloads (integer)
442 """
443 return len(self.__downloads)
444
445 def downloads(self):
446 """
447 Public method to get a reference to the downloads.
448
449 @return reference to the downloads (list of DownloadItem)
450 """
451 return self.__downloads
452
453 def changeOccurred(self):
454 """
455 Public method to signal a change.
456 """
457 self.__saveTimer.changeOccurred()
458 self.__updateItemCount()
459
460 ###########################################################################
461 ## Context menu related methods below
462 ###########################################################################
463
464 def __currentItem(self):
465 """
466 Private method to get a reference to the current item.
467
468 @return reference to the current item (DownloadItem)
469 """
470 index = self.downloadsView.currentIndex()
471 if index and index.isValid():
472 row = index.row()
473 return self.__downloads[row]
474
475 return None
476
477 def __contextMenuRetry(self):
478 """
479 Private method to retry of the download.
480 """
481 itm = self.__currentItem()
482 if itm is not None:
483 itm.retry()
484
485 def __contextMenuOpen(self):
486 """
487 Private method to open the downloaded file.
488 """
489 itm = self.__currentItem()
490 if itm is not None:
491 itm.openFile()
492
493 def __contextMenuOpenFolder(self):
494 """
495 Private method to open the folder containing the downloaded file.
496 """
497 itm = self.__currentItem()
498 if itm is not None:
499 itm.openFolder()
500
501 def __contextMenuCancel(self):
502 """
503 Private method to cancel the current download.
504 """
505 itm = self.__currentItem()
506 if itm is not None:
507 itm.cancelDownload()
508
509 def __contextMenuGotoPage(self):
510 """
511 Private method to open the download page.
512 """
513 itm = self.__currentItem()
514 if itm is not None:
515 url = itm.getPageUrl()
516 Helpviewer.HelpWindow.HelpWindow.mainWindow().openUrl(url, "")
517
518 def __contextMenuCopyLink(self):
519 """
520 Private method to copy the download link to the clipboard.
521 """
522 itm = self.__currentItem()
523 if itm is not None:
524 url = itm.getPageUrl().toString()
525 QApplication.clipboard().setText(url)
526
527 def __contextMenuSelectAll(self):
528 """
529 Private method to select all downloads.
530 """
531 self.downloadsView.selectAll()
532
533 def __contextMenuRemoveSelected(self):
534 """
535 Private method to remove the selected downloads from the list.
536 """
537 self.downloadsView.removeSelected()

eric ide

mercurial