Helpviewer/DownloadDialog.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the download dialog.
8 """
9
10 from PyQt4.QtCore import *
11 from PyQt4.QtGui import *
12 from PyQt4.QtNetwork import QNetworkReply, QNetworkAccessManager, QNetworkRequest
13
14 import Preferences
15
16 import Helpviewer.HelpWindow
17
18 from Ui_DownloadDialog import Ui_DownloadDialog
19
20 class DownloadDialog(QWidget, Ui_DownloadDialog):
21 """
22 Class implementing the download dialog.
23
24 @signal done() emitted just before the dialog is closed
25 """
26 done = pyqtSignal()
27
28 def __init__(self, reply = None, requestFilename = False, webPage = None,
29 download = False, parent = None):
30 """
31 Constructor
32
33 @param reply reference to the network reply object (QNetworkReply)
34 @param requestFilename flag indicating to ask the user for a filename (boolean)
35 @param webPage reference to the web page object the download originated
36 from (QWebPage)
37 @param download flag indicating a download operation (boolean)
38 @param parent reference to the parent widget (QWidget)
39 """
40 QWidget.__init__(self, parent)
41 self.setupUi(self)
42 self.setAttribute(Qt.WA_DeleteOnClose)
43
44 self.__tryAgainButton = \
45 self.buttonBox.addButton(self.trUtf8("Try Again"),
46 QDialogButtonBox.ActionRole)
47 self.__stopButton = \
48 self.buttonBox.addButton(self.trUtf8("Stop"), QDialogButtonBox.ActionRole)
49 self.__openButton = self.buttonBox.button(QDialogButtonBox.Open)
50 self.__closeButton = self.buttonBox.button(QDialogButtonBox.Close)
51
52 self.__tryAgainButton.setEnabled(False)
53 self.__closeButton.setEnabled(False)
54 self.__openButton.setEnabled(False)
55 self.keepOpenCheckBox.setChecked(True)
56
57 icon = self.style().standardIcon(QStyle.SP_FileIcon)
58 self.fileIcon.setPixmap(icon.pixmap(48, 48))
59
60 self.__reply = reply
61 self.__requestFilename = requestFilename
62 self.__page = webPage
63 self.__toDownload = download
64 self.__bytesReceived = 0
65 self.__downloadTime = QTime()
66 self.__output = QFile()
67
68 self.__initialize()
69
70 def __initialize(self):
71 """
72 Private method to (re)initialize the dialog.
73 """
74 if self.__reply is None:
75 return
76
77 self.__startedSaving = False
78 self.__downloadFinished = False
79
80 self.__url = self.__reply.url()
81 self.__reply.setParent(self)
82 self.connect(self.__reply, SIGNAL("readyRead()"), self.__readyRead)
83 self.connect(self.__reply, SIGNAL("error(QNetworkReply::NetworkError)"),
84 self.__networkError)
85 self.connect(self.__reply, SIGNAL("downloadProgress(qint64, qint64)"),
86 self.__downloadProgress)
87 self.connect(self.__reply, SIGNAL("metaDataChanged()"),
88 self.__metaDataChanged)
89 self.connect(self.__reply, SIGNAL("finished()"), self.__finished)
90
91 # reset info
92 self.infoLabel.clear()
93 self.progressBar.setValue(0)
94 self.__getFileName()
95
96 # start timer for the download estimation
97 self.__downloadTime.start()
98
99 if self.__reply.error() != QNetworkReply.NoError:
100 self.__networkError()
101 self.__finished()
102
103 def __getFileName(self):
104 """
105 Private method to get the filename to save to from the user.
106 """
107 downloadDirectory = Preferences.getUI("DownloadPath")
108 if not downloadDirectory:
109 downloadDirectory = \
110 QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation)
111 if downloadDirectory:
112 downloadDirectory += '/'
113
114 defaultFileName = self.__saveFileName(downloadDirectory)
115 fileName = defaultFileName
116 baseName = QFileInfo(fileName).completeBaseName()
117 self.__autoOpen = False
118 if not self.__toDownload:
119 res = QMessageBox.question(None,
120 self.trUtf8("Downloading"),
121 self.trUtf8("""<p>You are about to download the file <b>{0}</b>.</p>"""
122 """<p>What do you want to do?</p>""").format(baseName),
123 QMessageBox.StandardButtons(\
124 QMessageBox.Open | \
125 QMessageBox.Save))
126 self.__autoOpen = res == QMessageBox.Open
127 fileName = QDesktopServices.storageLocation(QDesktopServices.TempLocation) + \
128 '/' + baseName
129
130 if not self.__autoOpen and self.__requestFilename:
131 fileName = QFileDialog.getSaveFileName(
132 None,
133 self.trUtf8("Save File"),
134 defaultFileName,
135 "")
136 if not fileName:
137 self.__reply.close()
138 if not self.keepOpenCheckBox.isChecked():
139 self.close()
140 else:
141 self.filenameLabel.setText(self.trUtf8("Download canceled: {0}")\
142 .format(QFileInfo(defaultFileName).fileName()))
143 self.__stop()
144 return
145
146 self.__output.setFileName(fileName)
147 self.filenameLabel.setText(QFileInfo(self.__output.fileName()).fileName())
148 if self.__requestFilename:
149 self.__readyRead()
150
151 def __saveFileName(self, directory):
152 """
153 Private method to calculate a name for the file to download.
154
155 @param directory name of the directory to store the file into (string)
156 @return proposed filename (string)
157 """
158 path = ""
159 if self.__reply.hasRawHeader("Content-Disposition"):
160 header = unicode(self.__reply.rawHeader("Content-Disposition"))
161 if header:
162 pos = header.find("filename=")
163 if pos != -1:
164 path = header[pos + 9:]
165 if path.startswith('"') and path.endswith('"'):
166 path = path[1:-1]
167 if not path:
168 path = self.__url.path()
169
170 info = QFileInfo(path)
171 baseName = info.completeBaseName()
172 endName = info.suffix()
173
174 if not baseName:
175 baseName = "unnamed_download"
176
177 name = directory + baseName
178 if endName:
179 name += '.' + endName
180 i = 1
181 while QFile.exists(name):
182 # file exists already, don't overwrite
183 name = directory + baseName + ('-%d' % i)
184 if endName:
185 name += '.' + endName
186 i += 1
187 return name
188
189 @pyqtSlot(QAbstractButton)
190 def on_buttonBox_clicked(self, button):
191 """
192 Private slot called by a button of the button box clicked.
193
194 @param button button that was clicked (QAbstractButton)
195 """
196 if button == self.__closeButton:
197 self.close()
198 elif button == self.__openButton:
199 self.__open()
200 elif button == self.__stopButton:
201 self.__stop()
202 elif button == self.__tryAgainButton:
203 self.__tryAgain()
204
205 def __stop(self):
206 """
207 Private slot to stop the download.
208 """
209 self.__stopButton.setEnabled(False)
210 self.__closeButton.setEnabled(True)
211 self.__tryAgainButton.setEnabled(True)
212 self.__reply.abort()
213
214 def __open(self):
215 """
216 Private slot to open the downloaded file.
217 """
218 info = QFileInfo(self.__output)
219 url = QUrl.fromLocalFile(info.absoluteFilePath())
220 QDesktopServices.openUrl(url)
221
222 def __tryAgain(self):
223 """
224 Private slot to retry the download.
225 """
226 self.__tryAgainButton.setEnabled(False)
227 self.__closeButton.setEnabled(False)
228 self.__stopButton.setEnabled(True)
229
230 if self.__page:
231 nam = self.__page.networkAccessManager()
232 else:
233 nam = QNetworkAccessManager()
234 reply = nam.get(QNetworkRequest(self.__url))
235 if self.__reply:
236 self.__reply.deleteLater()
237 if self.__output.exists():
238 self.__output.remove()
239 self.__reply = reply
240 self.__initialize()
241
242 def __readyRead(self):
243 """
244 Private slot to read the available data.
245 """
246 if self.__requestFilename and not self.__output.fileName():
247 return
248
249 if not self.__output.isOpen():
250 # in case someone else has already put a file there
251 if not self.__requestFilename:
252 self.__getFileName()
253 if not self.__output.open(QIODevice.WriteOnly):
254 self.infoLabel.setText(self.trUtf8("Error opening save file: {0}")\
255 .format(self.__output.errorString()))
256 self.__stopButton.click()
257 return
258
259 bytesWritten = self.__output.write(self.__reply.readAll())
260 if bytesWritten == -1:
261 self.infoLabel.setText(self.trUtf8("Error saving: {0}")\
262 .format(self.__output.errorString()))
263 self.__stopButton.click()
264 else:
265 size = self.__reply.header(QNetworkRequest.ContentLengthHeader).toInt()[0]
266 if size == bytesWritten:
267 self.__downloadProgress(size, size)
268 self.__downloadFinished = True
269 self.__startedSaving = True
270 if self.__downloadFinished:
271 self.__finished()
272
273 def __networkError(self):
274 """
275 Private slot to handle a network error.
276 """
277 if self.__reply.error() != QNetworkReply.OperationCanceledError:
278 self.infoLabel.setText(self.trUtf8("Network Error: {0}")\
279 .format(self.__reply.errorString()))
280 self.__tryAgainButton.setEnabled(True)
281 self.__closeButton.setEnabled(True)
282 self.__openButton.setEnabled(False)
283
284 def __metaDataChanged(self):
285 """
286 Private slot to handle a change of the meta data.
287 """
288 locationHeader = self.__reply.header(QNetworkRequest.LocationHeader)
289 if locationHeader.isValid():
290 self.__url = locationHeader.toUrl()
291 self.__reply.deleteLater()
292 self.__reply = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().get(
293 QNetworkRequest(self.__url))
294 self.__initialize()
295
296 def __downloadProgress(self, received, total):
297 """
298 Private method show the download progress.
299
300 @param received number of bytes received (integer)
301 @param total number of total bytes (integer)
302 """
303 self.__bytesReceived = received
304 if total == -1:
305 self.progressBar.setValue(0)
306 self.progressBar.setMaximum(0)
307 else:
308 self.progressBar.setValue(received)
309 self.progressBar.setMaximum(total)
310 self.__updateInfoLabel()
311
312 def __updateInfoLabel(self):
313 """
314 Private method to update the info label.
315 """
316 if self.__reply.error() != QNetworkReply.NoError and \
317 self.__reply.error() != QNetworkReply.OperationCanceledError:
318 return
319
320 bytesTotal = self.progressBar.maximum()
321 running = not self.__downloadedSuccessfully()
322
323 info = ""
324 if self.__downloading():
325 remaining = ""
326 speed = self.__bytesReceived * 1000.0 / self.__downloadTime.elapsed()
327 if bytesTotal != 0:
328 timeRemaining = int((bytesTotal - self.__bytesReceived) / speed)
329
330 if timeRemaining > 60:
331 minutes = int(timeRemaining / 60)
332 seconds = int(timeRemaining % 60)
333 remaining = self.trUtf8("- {0}:{1:02} minutes remaining")\
334 .format(minutes, seconds)
335 else:
336 # when downloading, the eta should never be 0
337 if timeRemaining == 0:
338 timeRemaining = 1
339
340 remaining = self.trUtf8("- {0} seconds remaining")\
341 .format(timeRemaining)
342 info = self.trUtf8("{0} of {1} ({2}/sec) {3}")\
343 .format(
344 self.__dataString(self.__bytesReceived),
345 bytesTotal == 0 and self.trUtf8("?") \
346 or self.__dataString(bytesTotal),
347 self.__dataString(int(speed)),
348 remaining)
349 else:
350 if self.__bytesReceived == bytesTotal:
351 info = self.trUtf8("{0} downloaded")\
352 .format(self.__dataString(self.__output.size()))
353 else:
354 info = self.trUtf8("{0} of {1} - Stopped")\
355 .format(self.__dataString(self.__bytesReceived),
356 self.__dataString(bytesTotal))
357 self.infoLabel.setText(info)
358
359 def __dataString(self, size):
360 """
361 Private method to generate a formatted data string.
362
363 @param size size to be formatted (integer)
364 @return formatted data string (QString)
365 """
366 unit = ""
367 if size < 1024:
368 unit = self.trUtf8("bytes")
369 elif size < 1024 * 1024:
370 size /= 1024
371 unit = self.trUtf8("kB")
372 else:
373 size /= 1024 * 1024
374 unit = self.trUtf8("MB")
375 return "{0} {1}".format(size, unit)
376
377 def __downloading(self):
378 """
379 Private method to determine, if a download is in progress.
380
381 @return flag indicating a download is in progress (boolean)
382 """
383 return self.__stopButton.isEnabled()
384
385 def __downloadedSuccessfully(self):
386 """
387 Private method to determine the download status.
388
389 @return download status (boolean)
390 """
391 return (not self.__stopButton.isEnabled() and \
392 not self.__tryAgainButton.isEnabled())
393
394 def __finished(self):
395 """
396 Private slot to handle the download finished.
397 """
398 self.__downloadFinished = True
399 if not self.__startedSaving:
400 return
401
402 self.__stopButton.setEnabled(False)
403 self.__closeButton.setEnabled(True)
404 self.__openButton.setEnabled(True)
405 self.__output.close()
406 self.__updateInfoLabel()
407
408 if not self.keepOpenCheckBox.isChecked() and \
409 self.__reply.error() == QNetworkReply.NoError:
410 self.close()
411
412 if self.__autoOpen:
413 self.__open()
414
415 def closeEvent(self, evt):
416 """
417 Protected method called when the dialog is closed.
418 """
419 self.__output.close()
420 self.done.emit()

eric ide

mercurial