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