diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/browser/downloadmanager.d	Sun May 17 18:49:59 2009 +0000
@@ -0,0 +1,663 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+module downloadmanager;
+
+import ui_downloads;
+import ui_downloaditem;
+
+import QtNetwork.QNetworkReply;
+
+import QtCore.QFile;
+import QtCore.QTime;
+
+
+import autosaver;
+import browserapplication;
+import networkaccessmanager;
+
+import math;
+
+import QtCore.QMetaEnum;
+import QtCore.QSettings;
+
+import QtGui.QDesktopServices;
+import QtGui.QFileDialog;
+import QtGui.QHeaderView;
+import QtGui.QFileIconProvider;
+
+import QtCore.QDebug;
+
+import QtWebKit.QWebSettings;
+
+
+
+class DownloadItem : public QWidget, public Ui_DownloadItem
+{
+    Q_OBJECT
+
+signals:
+    void statusChanged();
+
+public:
+ 
+/*!
+    DownloadItem is a widget that is displayed in the download manager list.
+    It moves the data from the QNetworkReply into the QFile as well
+    as update the information/progressbar and report errors.
+ */
+   DownloadItem(QNetworkReply *reply = 0, bool requestFileName = false, QWidget *parent = 0)
+    : QWidget(parent)
+{
+	m_reply = reply;
+m_requestFileName = requestFileName;
+m_bytesReceived = 0;
+	
+    setupUi(this);
+    QPalette p = downloadInfoLabel.palette();
+    p.setColor(QPalette::Text, Qt.darkGray);
+    downloadInfoLabel.setPalette(p);
+    progressBar.setMaximum(0);
+    tryAgainButton.hide();
+    connect(stopButton, SIGNAL(clicked()), this, SLOT(stop()));
+    connect(openButton, SIGNAL(clicked()), this, SLOT(open()));
+    connect(tryAgainButton, SIGNAL(clicked()), this, SLOT(tryAgain()));
+
+    init();
+}
+
+
+    bool downloading()
+{
+    return (progressBar.isVisible());
+}
+
+
+    bool downloadedSuccessfully()
+{
+    return (stopButton.isHidden() && tryAgainButton.isHidden());
+}
+
+
+    QUrl m_url;
+
+    QFile m_output;
+    QNetworkReply *m_reply;
+
+private slots:
+    void stop()
+{
+    setUpdatesEnabled(false);
+    stopButton.setEnabled(false);
+    stopButton.hide();
+    tryAgainButton.setEnabled(true);
+    tryAgainButton.show();
+    setUpdatesEnabled(true);
+    m_reply.abort();
+}
+
+    void tryAgain()
+{
+    if (!tryAgainButton.isEnabled())
+        return;
+
+    tryAgainButton.setEnabled(false);
+    tryAgainButton.setVisible(false);
+    stopButton.setEnabled(true);
+    stopButton.setVisible(true);
+    progressBar.setVisible(true);
+
+    QNetworkReply *r = BrowserApplication::networkAccessManager().get(QNetworkRequest(m_url));
+    if (m_reply)
+        m_reply.deleteLater();
+    if (m_output.exists())
+        m_output.remove();
+    m_reply = r;
+    init();
+    emit statusChanged();
+}
+
+    void open()
+{
+    QFileInfo info(m_output);
+    QUrl url = QUrl::fromLocalFile(info.absolutePath());
+    QDesktopServices::openUrl(url);
+}
+
+    void downloadReadyRead()
+{
+    if (m_requestFileName && m_output.fileName().isEmpty())
+        return;
+    if (!m_output.isOpen()) {
+        // in case someone else has already put a file there
+        if (!m_requestFileName)
+            getFileName();
+        if (!m_output.open(QIODevice::WriteOnly)) {
+            downloadInfoLabel.setText(tr("Error opening save file: %1")
+                    .arg(m_output.errorString()));
+            stopButton.click();
+            emit statusChanged();
+            return;
+        }
+        emit statusChanged();
+    }
+    if (-1 == m_output.write(m_reply.readAll())) {
+        downloadInfoLabel.setText(tr("Error saving: %1")
+                .arg(m_output.errorString()));
+        stopButton.click();
+    }
+}
+
+    void error(QNetworkReply::NetworkError code)
+{
+    qDebug() << "DownloadItem::error" << m_reply.errorString() << m_url;
+    downloadInfoLabel.setText(tr("Network Error: %1").arg(m_reply.errorString()));
+    tryAgainButton.setEnabled(true);
+    tryAgainButton.setVisible(true);
+}
+
+
+    void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+    m_bytesReceived = bytesReceived;
+    if (bytesTotal == -1) {
+        progressBar.setValue(0);
+        progressBar.setMaximum(0);
+    } else {
+        progressBar.setValue(bytesReceived);
+        progressBar.setMaximum(bytesTotal);
+    }
+    updateInfoLabel();
+}
+
+
+    void metaDataChanged()
+{
+    qDebug() << "DownloadItem::metaDataChanged: not handled.";
+}
+
+    void finished()
+{
+    progressBar.hide();
+    stopButton.setEnabled(false);
+    stopButton.hide();
+    m_output.close();
+    updateInfoLabel();
+    emit statusChanged();
+}
+
+private:
+    void getFileName()
+{
+    QSettings settings;
+    settings.beginGroup(QLatin1String("downloadmanager"));
+    QString defaultLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation);
+    QString downloadDirectory = settings.value(QLatin1String("downloadDirectory"), defaultLocation).toString();
+    if (!downloadDirectory.isEmpty())
+        downloadDirectory += QLatin1Char('/');
+
+    QString defaultFileName = saveFileName(downloadDirectory);
+    QString fileName = defaultFileName;
+    if (m_requestFileName) {
+        fileName = QFileDialog::getSaveFileName(this, tr("Save File"), defaultFileName);
+        if (fileName.isEmpty()) {
+            m_reply.close();
+            fileNameLabel.setText(tr("Download canceled: %1").arg(QFileInfo(defaultFileName).fileName()));
+            return;
+        }
+    }
+    m_output.setFileName(fileName);
+    fileNameLabel.setText(QFileInfo(m_output.fileName()).fileName());
+    if (m_requestFileName)
+        downloadReadyRead();
+}
+
+    void init()
+{
+    if (!m_reply)
+        return;
+
+    // attach to the m_reply
+    m_url = m_reply.url();
+    m_reply.setParent(this);
+    connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
+    connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
+            this, SLOT(error(QNetworkReply::NetworkError)));
+    connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
+            this, SLOT(downloadProgress(qint64, qint64)));
+    connect(m_reply, SIGNAL(metaDataChanged()),
+            this, SLOT(metaDataChanged()));
+    connect(m_reply, SIGNAL(finished()),
+            this, SLOT(finished()));
+
+    // reset info
+    downloadInfoLabel.clear();
+    progressBar.setValue(0);
+    getFileName();
+
+    // start timer for the download estimation
+    m_downloadTime.start();
+
+    if (m_reply.error() != QNetworkReply::NoError) {
+        error(m_reply.error());
+        finished();
+    }
+}
+
+    void updateInfoLabel()
+{
+    if (m_reply.error() == QNetworkReply::NoError)
+        return;
+
+    qint64 bytesTotal = progressBar.maximum();
+    bool running = !downloadedSuccessfully();
+
+    // update info label
+    double speed = m_bytesReceived * 1000.0 / m_downloadTime.elapsed();
+    double timeRemaining = ((double)(bytesTotal - m_bytesReceived)) / speed;
+    QString timeRemainingString = tr("seconds");
+    if (timeRemaining > 60) {
+        timeRemaining = timeRemaining / 60;
+        timeRemainingString = tr("minutes");
+    }
+    timeRemaining = floor(timeRemaining);
+
+    // When downloading the eta should never be 0
+    if (timeRemaining == 0)
+        timeRemaining = 1;
+
+    QString info;
+    if (running) {
+        QString remaining;
+        if (bytesTotal != 0)
+            remaining = tr("- %4 %5 remaining")
+            .arg(timeRemaining)
+            .arg(timeRemainingString);
+        info = QString(tr("%1 of %2 (%3/sec) %4"))
+            .arg(dataString(m_bytesReceived))
+            .arg(bytesTotal == 0 ? tr("?") : dataString(bytesTotal))
+            .arg(dataString((int)speed))
+            .arg(remaining);
+    } else {
+        if (m_bytesReceived == bytesTotal)
+            info = dataString(m_output.size());
+        else
+            info = tr("%1 of %2 - Stopped")
+                .arg(dataString(m_bytesReceived))
+                .arg(dataString(bytesTotal));
+    }
+    downloadInfoLabel.setText(info);
+}
+
+    QString dataString(int size)
+{
+    QString unit;
+    if (size < 1024) {
+        unit = tr("bytes");
+    } else if (size < 1024*1024) {
+        size /= 1024;
+        unit = tr("kB");
+    } else {
+        size /= 1024*1024;
+        unit = tr("MB");
+    }
+    return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
+}
+
+    QString saveFileName(const QString &directory);
+{
+    // Move this function into QNetworkReply to also get file name sent from the server
+    QString path = m_url.path();
+    QFileInfo info(path);
+    QString baseName = info.completeBaseName();
+    QString endName = info.suffix();
+
+    if (baseName.isEmpty()) {
+        baseName = QLatin1String("unnamed_download");
+        qDebug() << "DownloadManager:: downloading unknown file:" << m_url;
+    }
+    QString name = directory + baseName + QLatin1Char('.') + endName;
+    if (QFile::exists(name)) {
+        // already exists, don't overwrite
+        int i = 1;
+        do {
+            name = directory + baseName + QLatin1Char('-') + QString::number(i++) + QLatin1Char('.') + endName;
+        } while (QFile::exists(name));
+    }
+    return name;
+}
+
+    bool m_requestFileName;
+    qint64 m_bytesReceived;
+    QTime m_downloadTime;
+};
+
+class AutoSaver;
+class DownloadModel;
+QT_BEGIN_NAMESPACE
+class QFileIconProvider;
+QT_END_NAMESPACE
+
+class DownloadManager : public QDialog, public Ui_DownloadDialog
+{
+    Q_OBJECT
+    Q_PROPERTY(RemovePolicy removePolicy READ removePolicy WRITE setRemovePolicy)
+    Q_ENUMS(RemovePolicy)
+
+public:
+    enum RemovePolicy {
+        Never,
+        Exit,
+        SuccessFullDownload
+    };
+
+    /*!
+    DownloadManager is a Dialog that contains a list of DownloadItems
+
+    It is a basic download manager.  It only downloads the file, doesn't do BitTorrent,
+    extract zipped files or anything fancy.
+  */
+this(QWidget *parent = null)
+    : QDialog(parent)
+    , m_autoSaver(new AutoSaver(this))
+    , m_manager(BrowserApplication::networkAccessManager())
+    , m_iconProvider(0)
+    , m_removePolicy(Never)
+{
+    setupUi(this);
+    downloadsView.setShowGrid(false);
+    downloadsView.verticalHeader().hide();
+    downloadsView.horizontalHeader().hide();
+    downloadsView.setAlternatingRowColors(true);
+    downloadsView.horizontalHeader().setStretchLastSection(true);
+    m_model = new DownloadModel(this);
+    downloadsView.setModel(m_model);
+    connect(cleanupButton, SIGNAL(clicked()), this, SLOT(cleanup()));
+    load();
+}
+
+    ~this()
+{
+    m_autoSaver.changeOccurred();
+    m_autoSaver.saveIfNeccessary();
+    if (m_iconProvider)
+        delete m_iconProvider;
+}
+
+    int activeDownloads()
+{
+    int count = 0;
+    for (int i = 0; i < m_downloads.count(); ++i) {
+        if (m_downloads.at(i).stopButton.isEnabled())
+            ++count;
+    }
+    return count;
+}
+
+    RemovePolicy removePolicy()
+{
+    return m_removePolicy;
+}
+
+    void setRemovePolicy(RemovePolicy policy)
+{
+    if (policy == m_removePolicy)
+        return;
+    m_removePolicy = policy;
+    m_autoSaver.changeOccurred();
+}
+
+
+public slots:
+    void download(const QNetworkRequest &request, bool requestFileName = false);
+{
+    if (request.url().isEmpty())
+        return;
+    handleUnsupportedContent(m_manager.get(request), requestFileName);
+}
+
+
+    inline void download(const QUrl &url, bool requestFileName = false)
+        { download(QNetworkRequest(url), requestFileName); }
+    void handleUnsupportedContent(QNetworkReply *reply, bool requestFileName = false);
+{
+    if (!reply || reply.url().isEmpty())
+        return;
+    QVariant header = reply.header(QNetworkRequest::ContentLengthHeader);
+    bool ok;
+    int size = header.toInt(&ok);
+    if (ok && size == 0)
+        return;
+
+    qDebug() << "DownloadManager::handleUnsupportedContent" << reply.url() << "requestFileName" << requestFileName;
+    DownloadItem *item = new DownloadItem(reply, requestFileName, this);
+    addItem(item);
+}
+	
+    void cleanup()
+{
+    if (m_downloads.isEmpty())
+        return;
+    m_model.removeRows(0, m_downloads.count());
+    updateItemCount();
+    if (m_downloads.isEmpty() && m_iconProvider) {
+        delete m_iconProvider;
+        m_iconProvider = 0;
+    }
+    m_autoSaver.changeOccurred();
+}
+
+private slots:
+    void save()
+{
+    QSettings settings;
+    settings.beginGroup(QLatin1String("downloadmanager"));
+    QMetaEnum removePolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("RemovePolicy"));
+    settings.setValue(QLatin1String("removeDownloadsPolicy"), QLatin1String(removePolicyEnum.valueToKey(m_removePolicy)));
+    settings.setValue(QLatin1String("size"), size());
+    if (m_removePolicy == Exit)
+        return;
+
+    for (int i = 0; i < m_downloads.count(); ++i) {
+        QString key = QString(QLatin1String("download_%1_")).arg(i);
+        settings.setValue(key + QLatin1String("url"), m_downloads[i].m_url);
+        settings.setValue(key + QLatin1String("location"), QFileInfo(m_downloads[i].m_output).filePath());
+        settings.setValue(key + QLatin1String("done"), m_downloads[i].downloadedSuccessfully());
+    }
+    int i = m_downloads.count();
+    QString key = QString(QLatin1String("download_%1_")).arg(i);
+    while (settings.contains(key + QLatin1String("url"))) {
+        settings.remove(key + QLatin1String("url"));
+        settings.remove(key + QLatin1String("location"));
+        settings.remove(key + QLatin1String("done"));
+        key = QString(QLatin1String("download_%1_")).arg(++i);
+    }
+}
+
+    void updateRow()
+{
+    DownloadItem *item = qobject_cast<DownloadItem*>(sender());
+    int row = m_downloads.indexOf(item);
+    if (-1 == row)
+        return;
+    if (!m_iconProvider)
+        m_iconProvider = new QFileIconProvider();
+    QIcon icon = m_iconProvider.icon(item.m_output.fileName());
+    if (icon.isNull())
+        icon = style().standardIcon(QStyle::SP_FileIcon);
+    item.fileIcon.setPixmap(icon.pixmap(48, 48));
+    downloadsView.setRowHeight(row, item.minimumSizeHint().height());
+
+    bool remove = false;
+    QWebSettings *globalSettings = QWebSettings::globalSettings();
+    if (!item.downloading()
+        && globalSettings.testAttribute(QWebSettings::PrivateBrowsingEnabled))
+        remove = true;
+
+    if (item.downloadedSuccessfully()
+        && removePolicy() == DownloadManager::SuccessFullDownload) {
+        remove = true;
+    }
+    if (remove)
+        m_model.removeRow(row);
+
+    cleanupButton.setEnabled(m_downloads.count() - activeDownloads() > 0);
+}
+
+private:
+    void addItem(DownloadItem *item)
+{
+    connect(item, SIGNAL(statusChanged()), this, SLOT(updateRow()));
+    int row = m_downloads.count();
+    m_model.beginInsertRows(QModelIndex(), row, row);
+    m_downloads.append(item);
+    m_model.endInsertRows();
+    updateItemCount();
+    if (row == 0)
+        show();
+    downloadsView.setIndexWidget(m_model.index(row, 0), item);
+    QIcon icon = style().standardIcon(QStyle::SP_FileIcon);
+    item.fileIcon.setPixmap(icon.pixmap(48, 48));
+    downloadsView.setRowHeight(row, item.sizeHint().height());
+}
+
+
+    void updateItemCount()
+{
+    int count = m_downloads.count();
+    itemCount.setText(count == 1 ? tr("1 Download") : tr("%1 Downloads").arg(count));
+}
+
+DownloadModel::DownloadModel(DownloadManager *downloadManager, QObject *parent)
+    : QAbstractListModel(parent)
+    , m_downloadManager(downloadManager)
+{
+}
+
+    void load()
+{
+    QSettings settings;
+    settings.beginGroup(QLatin1String("downloadmanager"));
+    QSize size = settings.value(QLatin1String("size")).toSize();
+    if (size.isValid())
+        resize(size);
+    QByteArray value = settings.value(QLatin1String("removeDownloadsPolicy"), QLatin1String("Never")).toByteArray();
+    QMetaEnum removePolicyEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("RemovePolicy"));
+    m_removePolicy = removePolicyEnum.keyToValue(value) == -1 ?
+                        Never :
+                        static_cast<RemovePolicy>(removePolicyEnum.keyToValue(value));
+
+    int i = 0;
+    QString key = QString(QLatin1String("download_%1_")).arg(i);
+    while (settings.contains(key + QLatin1String("url"))) {
+        QUrl url = settings.value(key + QLatin1String("url")).toUrl();
+        QString fileName = settings.value(key + QLatin1String("location")).toString();
+        bool done = settings.value(key + QLatin1String("done"), true).toBool();
+        if (!url.isEmpty() && !fileName.isEmpty()) {
+            DownloadItem *item = new DownloadItem(0, this);
+            item.m_output.setFileName(fileName);
+            item.fileNameLabel.setText(QFileInfo(item.m_output.fileName()).fileName());
+            item.m_url = url;
+            item.stopButton.setVisible(false);
+            item.stopButton.setEnabled(false);
+            item.tryAgainButton.setVisible(!done);
+            item.tryAgainButton.setEnabled(!done);
+            item.progressBar.setVisible(!done);
+            addItem(item);
+        }
+        key = QString(QLatin1String("download_%1_")).arg(++i);
+    }
+    cleanupButton.setEnabled(m_downloads.count() - activeDownloads() > 0);
+}
+
+    AutoSaver *m_autoSaver;
+    DownloadModel *m_model;
+    QNetworkAccessManager *m_manager;
+    QFileIconProvider *m_iconProvider;
+    QList<DownloadItem*> m_downloads;
+    RemovePolicy m_removePolicy;
+    friend class DownloadModel;
+};
+
+class DownloadModel : public QAbstractListModel
+{
+    friend class DownloadManager;
+    Q_OBJECT
+
+public:
+    DownloadModel(DownloadManager *downloadManager, QObject *parent = 0);
+    QVariant data(const QModelIndex &index, int role = Qt.DisplayRole)
+{
+    if (index.row() < 0 || index.row() >= rowCount(index.parent()))
+        return QVariant();
+    if (role == Qt.ToolTipRole)
+        if (!m_downloadManager.m_downloads.at(index.row()).downloadedSuccessfully())
+            return m_downloadManager.m_downloads.at(index.row()).downloadInfoLabel.text();
+    return QVariant();
+}
+
+    int rowCount(const QModelIndex &parent = QModelIndex())
+{
+    return (parent.isValid()) ? 0 : m_downloadManager.m_downloads.count();
+}
+
+
+    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
+{
+    if (parent.isValid())
+        return false;
+
+    int lastRow = row + count - 1;
+    for (int i = lastRow; i >= row; --i) {
+        if (m_downloadManager.m_downloads.at(i).downloadedSuccessfully()
+            || m_downloadManager.m_downloads.at(i).tryAgainButton.isEnabled()) {
+            beginRemoveRows(parent, i, i);
+            m_downloadManager.m_downloads.takeAt(i).deleteLater();
+            endRemoveRows();
+        }
+    }
+    m_downloadManager.m_autoSaver.changeOccurred();
+    return true;
+}
+
+private:
+    DownloadManager *m_downloadManager;
+
+}