view demos/browser/downloadmanager.d @ 46:fd6eb3a1759d

license
author eldar
date Sun, 17 May 2009 21:50:06 +0000
parents 71b382c10ef6
children 7bfd46c330dc
line wrap: on
line source

/****************************************************************************
**
** 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
{

    mixin Signal!("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.
 */
    this(QNetworkReply reply = null, bool requestFileName = false, QWidget parent = null)
    {
        super(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();
        stopButton.clicked.connect(&this.stop);
        openButton.clicked.connect(&this.open);
        tryAgainButton.clicked.connect(&this.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;

}