view 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 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
{
    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;

}