comparison demos/browser/downloadmanager.d @ 45:71b382c10ef6

add coarse and incomplete QT browser port
author mandel
date Sun, 17 May 2009 18:49:59 +0000
parents
children fd6eb3a1759d
comparison
equal deleted inserted replaced
44:3cb15c92ac28 45:71b382c10ef6
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: Qt Software Information (qt-info@nokia.com)
5 **
6 ** This file is part of the demonstration applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial Usage
10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 ** accordance with the Qt Commercial License Agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Nokia.
14 **
15 ** GNU Lesser General Public License Usage
16 ** Alternatively, this file may be used under the terms of the GNU Lesser
17 ** General Public License version 2.1 as published by the Free Software
18 ** Foundation and appearing in the file LICENSE.LGPL included in the
19 ** packaging of this file. Please review the following information to
20 ** ensure the GNU Lesser General Public License version 2.1 requirements
21 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22 **
23 ** In addition, as a special exception, Nokia gives you certain
24 ** additional rights. These rights are described in the Nokia Qt LGPL
25 ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26 ** package.
27 **
28 ** GNU General Public License Usage
29 ** Alternatively, this file may be used under the terms of the GNU
30 ** General Public License version 3.0 as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL included in the
32 ** packaging of this file. Please review the following information to
33 ** ensure the GNU General Public License version 3.0 requirements will be
34 ** met: http://www.gnu.org/copyleft/gpl.html.
35 **
36 ** If you are unsure which license is appropriate for your use, please
37 ** contact the sales department at qt-sales@nokia.com.
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 module downloadmanager;
42
43 import ui_downloads;
44 import ui_downloaditem;
45
46 import QtNetwork.QNetworkReply;
47
48 import QtCore.QFile;
49 import QtCore.QTime;
50
51
52 import autosaver;
53 import browserapplication;
54 import networkaccessmanager;
55
56 import math;
57
58 import QtCore.QMetaEnum;
59 import QtCore.QSettings;
60
61 import QtGui.QDesktopServices;
62 import QtGui.QFileDialog;
63 import QtGui.QHeaderView;
64 import QtGui.QFileIconProvider;
65
66 import QtCore.QDebug;
67
68 import QtWebKit.QWebSettings;
69
70
71
72 class DownloadItem : public QWidget, public Ui_DownloadItem
73 {
74 Q_OBJECT
75
76 signals:
77 void statusChanged();
78
79 public:
80
81 /*!
82 DownloadItem is a widget that is displayed in the download manager list.
83 It moves the data from the QNetworkReply into the QFile as well
84 as update the information/progressbar and report errors.
85 */
86 DownloadItem(QNetworkReply *reply = 0, bool requestFileName = false, QWidget *parent = 0)
87 : QWidget(parent)
88 {
89 m_reply = reply;
90 m_requestFileName = requestFileName;
91 m_bytesReceived = 0;
92
93 setupUi(this);
94 QPalette p = downloadInfoLabel.palette();
95 p.setColor(QPalette::Text, Qt.darkGray);
96 downloadInfoLabel.setPalette(p);
97 progressBar.setMaximum(0);
98 tryAgainButton.hide();
99 connect(stopButton, SIGNAL(clicked()), this, SLOT(stop()));
100 connect(openButton, SIGNAL(clicked()), this, SLOT(open()));
101 connect(tryAgainButton, SIGNAL(clicked()), this, SLOT(tryAgain()));
102
103 init();
104 }
105
106
107 bool downloading()
108 {
109 return (progressBar.isVisible());
110 }
111
112
113 bool downloadedSuccessfully()
114 {
115 return (stopButton.isHidden() && tryAgainButton.isHidden());
116 }
117
118
119 QUrl m_url;
120
121 QFile m_output;
122 QNetworkReply *m_reply;
123
124 private slots:
125 void stop()
126 {
127 setUpdatesEnabled(false);
128 stopButton.setEnabled(false);
129 stopButton.hide();
130 tryAgainButton.setEnabled(true);
131 tryAgainButton.show();
132 setUpdatesEnabled(true);
133 m_reply.abort();
134 }
135
136 void tryAgain()
137 {
138 if (!tryAgainButton.isEnabled())
139 return;
140
141 tryAgainButton.setEnabled(false);
142 tryAgainButton.setVisible(false);
143 stopButton.setEnabled(true);
144 stopButton.setVisible(true);
145 progressBar.setVisible(true);
146
147 QNetworkReply *r = BrowserApplication::networkAccessManager().get(QNetworkRequest(m_url));
148 if (m_reply)
149 m_reply.deleteLater();
150 if (m_output.exists())
151 m_output.remove();
152 m_reply = r;
153 init();
154 emit statusChanged();
155 }
156
157 void open()
158 {
159 QFileInfo info(m_output);
160 QUrl url = QUrl::fromLocalFile(info.absolutePath());
161 QDesktopServices::openUrl(url);
162 }
163
164 void downloadReadyRead()
165 {
166 if (m_requestFileName && m_output.fileName().isEmpty())
167 return;
168 if (!m_output.isOpen()) {
169 // in case someone else has already put a file there
170 if (!m_requestFileName)
171 getFileName();
172 if (!m_output.open(QIODevice::WriteOnly)) {
173 downloadInfoLabel.setText(tr("Error opening save file: %1")
174 .arg(m_output.errorString()));
175 stopButton.click();
176 emit statusChanged();
177 return;
178 }
179 emit statusChanged();
180 }
181 if (-1 == m_output.write(m_reply.readAll())) {
182 downloadInfoLabel.setText(tr("Error saving: %1")
183 .arg(m_output.errorString()));
184 stopButton.click();
185 }
186 }
187
188 void error(QNetworkReply::NetworkError code)
189 {
190 qDebug() << "DownloadItem::error" << m_reply.errorString() << m_url;
191 downloadInfoLabel.setText(tr("Network Error: %1").arg(m_reply.errorString()));
192 tryAgainButton.setEnabled(true);
193 tryAgainButton.setVisible(true);
194 }
195
196
197 void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
198 {
199 m_bytesReceived = bytesReceived;
200 if (bytesTotal == -1) {
201 progressBar.setValue(0);
202 progressBar.setMaximum(0);
203 } else {
204 progressBar.setValue(bytesReceived);
205 progressBar.setMaximum(bytesTotal);
206 }
207 updateInfoLabel();
208 }
209
210
211 void metaDataChanged()
212 {
213 qDebug() << "DownloadItem::metaDataChanged: not handled.";
214 }
215
216 void finished()
217 {
218 progressBar.hide();
219 stopButton.setEnabled(false);
220 stopButton.hide();
221 m_output.close();
222 updateInfoLabel();
223 emit statusChanged();
224 }
225
226 private:
227 void getFileName()
228 {
229 QSettings settings;
230 settings.beginGroup(QLatin1String("downloadmanager"));
231 QString defaultLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation);
232 QString downloadDirectory = settings.value(QLatin1String("downloadDirectory"), defaultLocation).toString();
233 if (!downloadDirectory.isEmpty())
234 downloadDirectory += QLatin1Char('/');
235
236 QString defaultFileName = saveFileName(downloadDirectory);
237 QString fileName = defaultFileName;
238 if (m_requestFileName) {
239 fileName = QFileDialog::getSaveFileName(this, tr("Save File"), defaultFileName);
240 if (fileName.isEmpty()) {
241 m_reply.close();
242 fileNameLabel.setText(tr("Download canceled: %1").arg(QFileInfo(defaultFileName).fileName()));
243 return;
244 }
245 }
246 m_output.setFileName(fileName);
247 fileNameLabel.setText(QFileInfo(m_output.fileName()).fileName());
248 if (m_requestFileName)
249 downloadReadyRead();
250 }
251
252 void init()
253 {
254 if (!m_reply)
255 return;
256
257 // attach to the m_reply
258 m_url = m_reply.url();
259 m_reply.setParent(this);
260 connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
261 connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
262 this, SLOT(error(QNetworkReply::NetworkError)));
263 connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
264 this, SLOT(downloadProgress(qint64, qint64)));
265 connect(m_reply, SIGNAL(metaDataChanged()),
266 this, SLOT(metaDataChanged()));
267 connect(m_reply, SIGNAL(finished()),
268 this, SLOT(finished()));
269
270 // reset info
271 downloadInfoLabel.clear();
272 progressBar.setValue(0);
273 getFileName();
274
275 // start timer for the download estimation
276 m_downloadTime.start();
277
278 if (m_reply.error() != QNetworkReply::NoError) {
279 error(m_reply.error());
280 finished();
281 }
282 }
283
284 void updateInfoLabel()
285 {
286 if (m_reply.error() == QNetworkReply::NoError)
287 return;
288
289 qint64 bytesTotal = progressBar.maximum();
290 bool running = !downloadedSuccessfully();
291
292 // update info label
293 double speed = m_bytesReceived * 1000.0 / m_downloadTime.elapsed();
294 double timeRemaining = ((double)(bytesTotal - m_bytesReceived)) / speed;
295 QString timeRemainingString = tr("seconds");
296 if (timeRemaining > 60) {
297 timeRemaining = timeRemaining / 60;
298 timeRemainingString = tr("minutes");
299 }
300 timeRemaining = floor(timeRemaining);
301
302 // When downloading the eta should never be 0
303 if (timeRemaining == 0)
304 timeRemaining = 1;
305
306 QString info;
307 if (running) {
308 QString remaining;
309 if (bytesTotal != 0)
310 remaining = tr("- %4 %5 remaining")
311 .arg(timeRemaining)
312 .arg(timeRemainingString);
313 info = QString(tr("%1 of %2 (%3/sec) %4"))
314 .arg(dataString(m_bytesReceived))
315 .arg(bytesTotal == 0 ? tr("?") : dataString(bytesTotal))
316 .arg(dataString((int)speed))
317 .arg(remaining);
318 } else {
319 if (m_bytesReceived == bytesTotal)
320 info = dataString(m_output.size());
321 else
322 info = tr("%1 of %2 - Stopped")
323 .arg(dataString(m_bytesReceived))
324 .arg(dataString(bytesTotal));
325 }
326 downloadInfoLabel.setText(info);
327 }
328
329 QString dataString(int size)
330 {
331 QString unit;
332 if (size < 1024) {
333 unit = tr("bytes");
334 } else if (size < 1024*1024) {
335 size /= 1024;
336 unit = tr("kB");
337 } else {
338 size /= 1024*1024;
339 unit = tr("MB");
340 }
341 return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
342 }
343
344 QString saveFileName(const QString &directory);
345 {
346 // Move this function into QNetworkReply to also get file name sent from the server
347 QString path = m_url.path();
348 QFileInfo info(path);
349 QString baseName = info.completeBaseName();
350 QString endName = info.suffix();
351
352 if (baseName.isEmpty()) {
353 baseName = QLatin1String("unnamed_download");
354 qDebug() << "DownloadManager:: downloading unknown file:" << m_url;
355 }
356 QString name = directory + baseName + QLatin1Char('.') + endName;
357 if (QFile::exists(name)) {
358 // already exists, don't overwrite
359 int i = 1;
360 do {
361 name = directory + baseName + QLatin1Char('-') + QString::number(i++) + QLatin1Char('.') + endName;
362 } while (QFile::exists(name));
363 }
364 return name;
365 }
366
367 bool m_requestFileName;
368 qint64 m_bytesReceived;
369 QTime m_downloadTime;
370 };
371
372 class AutoSaver;
373 class DownloadModel;
374 QT_BEGIN_NAMESPACE
375 class QFileIconProvider;
376 QT_END_NAMESPACE
377
378 class DownloadManager : public QDialog, public Ui_DownloadDialog
379 {
380 Q_OBJECT
381 Q_PROPERTY(RemovePolicy removePolicy READ removePolicy WRITE setRemovePolicy)
382 Q_ENUMS(RemovePolicy)
383
384 public:
385 enum RemovePolicy {
386 Never,
387 Exit,
388 SuccessFullDownload
389 };
390
391 /*!
392 DownloadManager is a Dialog that contains a list of DownloadItems
393
394 It is a basic download manager. It only downloads the file, doesn't do BitTorrent,
395 extract zipped files or anything fancy.
396 */
397 this(QWidget *parent = null)
398 : QDialog(parent)
399 , m_autoSaver(new AutoSaver(this))
400 , m_manager(BrowserApplication::networkAccessManager())
401 , m_iconProvider(0)
402 , m_removePolicy(Never)
403 {
404 setupUi(this);
405 downloadsView.setShowGrid(false);
406 downloadsView.verticalHeader().hide();
407 downloadsView.horizontalHeader().hide();
408 downloadsView.setAlternatingRowColors(true);
409 downloadsView.horizontalHeader().setStretchLastSection(true);
410 m_model = new DownloadModel(this);
411 downloadsView.setModel(m_model);
412 connect(cleanupButton, SIGNAL(clicked()), this, SLOT(cleanup()));
413 load();
414 }
415
416 ~this()
417 {
418 m_autoSaver.changeOccurred();
419 m_autoSaver.saveIfNeccessary();
420 if (m_iconProvider)
421 delete m_iconProvider;
422 }
423
424 int activeDownloads()
425 {
426 int count = 0;
427 for (int i = 0; i < m_downloads.count(); ++i) {
428 if (m_downloads.at(i).stopButton.isEnabled())
429 ++count;
430 }
431 return count;
432 }
433
434 RemovePolicy removePolicy()
435 {
436 return m_removePolicy;
437 }
438
439 void setRemovePolicy(RemovePolicy policy)
440 {
441 if (policy == m_removePolicy)
442 return;
443 m_removePolicy = policy;
444 m_autoSaver.changeOccurred();
445 }
446
447
448 public slots:
449 void download(const QNetworkRequest &request, bool requestFileName = false);
450 {
451 if (request.url().isEmpty())
452 return;
453 handleUnsupportedContent(m_manager.get(request), requestFileName);
454 }
455
456
457 inline void download(const QUrl &url, bool requestFileName = false)
458 { download(QNetworkRequest(url), requestFileName); }
459 void handleUnsupportedContent(QNetworkReply *reply, bool requestFileName = false);
460 {
461 if (!reply || reply.url().isEmpty())
462 return;
463 QVariant header = reply.header(QNetworkRequest::ContentLengthHeader);
464 bool ok;
465 int size = header.toInt(&ok);
466 if (ok && size == 0)
467 return;
468
469 qDebug() << "DownloadManager::handleUnsupportedContent" << reply.url() << "requestFileName" << requestFileName;
470 DownloadItem *item = new DownloadItem(reply, requestFileName, this);
471 addItem(item);
472 }
473
474 void cleanup()
475 {
476 if (m_downloads.isEmpty())
477 return;
478 m_model.removeRows(0, m_downloads.count());
479 updateItemCount();
480 if (m_downloads.isEmpty() && m_iconProvider) {
481 delete m_iconProvider;
482 m_iconProvider = 0;
483 }
484 m_autoSaver.changeOccurred();
485 }
486
487 private slots:
488 void save()
489 {
490 QSettings settings;
491 settings.beginGroup(QLatin1String("downloadmanager"));
492 QMetaEnum removePolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("RemovePolicy"));
493 settings.setValue(QLatin1String("removeDownloadsPolicy"), QLatin1String(removePolicyEnum.valueToKey(m_removePolicy)));
494 settings.setValue(QLatin1String("size"), size());
495 if (m_removePolicy == Exit)
496 return;
497
498 for (int i = 0; i < m_downloads.count(); ++i) {
499 QString key = QString(QLatin1String("download_%1_")).arg(i);
500 settings.setValue(key + QLatin1String("url"), m_downloads[i].m_url);
501 settings.setValue(key + QLatin1String("location"), QFileInfo(m_downloads[i].m_output).filePath());
502 settings.setValue(key + QLatin1String("done"), m_downloads[i].downloadedSuccessfully());
503 }
504 int i = m_downloads.count();
505 QString key = QString(QLatin1String("download_%1_")).arg(i);
506 while (settings.contains(key + QLatin1String("url"))) {
507 settings.remove(key + QLatin1String("url"));
508 settings.remove(key + QLatin1String("location"));
509 settings.remove(key + QLatin1String("done"));
510 key = QString(QLatin1String("download_%1_")).arg(++i);
511 }
512 }
513
514 void updateRow()
515 {
516 DownloadItem *item = qobject_cast<DownloadItem*>(sender());
517 int row = m_downloads.indexOf(item);
518 if (-1 == row)
519 return;
520 if (!m_iconProvider)
521 m_iconProvider = new QFileIconProvider();
522 QIcon icon = m_iconProvider.icon(item.m_output.fileName());
523 if (icon.isNull())
524 icon = style().standardIcon(QStyle::SP_FileIcon);
525 item.fileIcon.setPixmap(icon.pixmap(48, 48));
526 downloadsView.setRowHeight(row, item.minimumSizeHint().height());
527
528 bool remove = false;
529 QWebSettings *globalSettings = QWebSettings::globalSettings();
530 if (!item.downloading()
531 && globalSettings.testAttribute(QWebSettings::PrivateBrowsingEnabled))
532 remove = true;
533
534 if (item.downloadedSuccessfully()
535 && removePolicy() == DownloadManager::SuccessFullDownload) {
536 remove = true;
537 }
538 if (remove)
539 m_model.removeRow(row);
540
541 cleanupButton.setEnabled(m_downloads.count() - activeDownloads() > 0);
542 }
543
544 private:
545 void addItem(DownloadItem *item)
546 {
547 connect(item, SIGNAL(statusChanged()), this, SLOT(updateRow()));
548 int row = m_downloads.count();
549 m_model.beginInsertRows(QModelIndex(), row, row);
550 m_downloads.append(item);
551 m_model.endInsertRows();
552 updateItemCount();
553 if (row == 0)
554 show();
555 downloadsView.setIndexWidget(m_model.index(row, 0), item);
556 QIcon icon = style().standardIcon(QStyle::SP_FileIcon);
557 item.fileIcon.setPixmap(icon.pixmap(48, 48));
558 downloadsView.setRowHeight(row, item.sizeHint().height());
559 }
560
561
562 void updateItemCount()
563 {
564 int count = m_downloads.count();
565 itemCount.setText(count == 1 ? tr("1 Download") : tr("%1 Downloads").arg(count));
566 }
567
568 DownloadModel::DownloadModel(DownloadManager *downloadManager, QObject *parent)
569 : QAbstractListModel(parent)
570 , m_downloadManager(downloadManager)
571 {
572 }
573
574 void load()
575 {
576 QSettings settings;
577 settings.beginGroup(QLatin1String("downloadmanager"));
578 QSize size = settings.value(QLatin1String("size")).toSize();
579 if (size.isValid())
580 resize(size);
581 QByteArray value = settings.value(QLatin1String("removeDownloadsPolicy"), QLatin1String("Never")).toByteArray();
582 QMetaEnum removePolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("RemovePolicy"));
583 m_removePolicy = removePolicyEnum.keyToValue(value) == -1 ?
584 Never :
585 static_cast<RemovePolicy>(removePolicyEnum.keyToValue(value));
586
587 int i = 0;
588 QString key = QString(QLatin1String("download_%1_")).arg(i);
589 while (settings.contains(key + QLatin1String("url"))) {
590 QUrl url = settings.value(key + QLatin1String("url")).toUrl();
591 QString fileName = settings.value(key + QLatin1String("location")).toString();
592 bool done = settings.value(key + QLatin1String("done"), true).toBool();
593 if (!url.isEmpty() && !fileName.isEmpty()) {
594 DownloadItem *item = new DownloadItem(0, this);
595 item.m_output.setFileName(fileName);
596 item.fileNameLabel.setText(QFileInfo(item.m_output.fileName()).fileName());
597 item.m_url = url;
598 item.stopButton.setVisible(false);
599 item.stopButton.setEnabled(false);
600 item.tryAgainButton.setVisible(!done);
601 item.tryAgainButton.setEnabled(!done);
602 item.progressBar.setVisible(!done);
603 addItem(item);
604 }
605 key = QString(QLatin1String("download_%1_")).arg(++i);
606 }
607 cleanupButton.setEnabled(m_downloads.count() - activeDownloads() > 0);
608 }
609
610 AutoSaver *m_autoSaver;
611 DownloadModel *m_model;
612 QNetworkAccessManager *m_manager;
613 QFileIconProvider *m_iconProvider;
614 QList<DownloadItem*> m_downloads;
615 RemovePolicy m_removePolicy;
616 friend class DownloadModel;
617 };
618
619 class DownloadModel : public QAbstractListModel
620 {
621 friend class DownloadManager;
622 Q_OBJECT
623
624 public:
625 DownloadModel(DownloadManager *downloadManager, QObject *parent = 0);
626 QVariant data(const QModelIndex &index, int role = Qt.DisplayRole)
627 {
628 if (index.row() < 0 || index.row() >= rowCount(index.parent()))
629 return QVariant();
630 if (role == Qt.ToolTipRole)
631 if (!m_downloadManager.m_downloads.at(index.row()).downloadedSuccessfully())
632 return m_downloadManager.m_downloads.at(index.row()).downloadInfoLabel.text();
633 return QVariant();
634 }
635
636 int rowCount(const QModelIndex &parent = QModelIndex())
637 {
638 return (parent.isValid()) ? 0 : m_downloadManager.m_downloads.count();
639 }
640
641
642 bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
643 {
644 if (parent.isValid())
645 return false;
646
647 int lastRow = row + count - 1;
648 for (int i = lastRow; i >= row; --i) {
649 if (m_downloadManager.m_downloads.at(i).downloadedSuccessfully()
650 || m_downloadManager.m_downloads.at(i).tryAgainButton.isEnabled()) {
651 beginRemoveRows(parent, i, i);
652 m_downloadManager.m_downloads.takeAt(i).deleteLater();
653 endRemoveRows();
654 }
655 }
656 m_downloadManager.m_autoSaver.changeOccurred();
657 return true;
658 }
659
660 private:
661 DownloadManager *m_downloadManager;
662
663 }