/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "downloadmanager.h"#include "autosaver.h"#include "browserapplication.h"#include <math.h>#include <QtCore/QMetaEnum>#include <QtCore/QSettings>#include <QtGui/QDesktopServices>#include <QtWidgets/QFileDialog>#include <QtWidgets/QHeaderView>#include <QtWidgets/QFileIconProvider>#include <QtCore/QDebug>#include <QWebEngineSettings>#include <QWebEngineDownloadItem>/*!
DownloadWidget is a widget that is displayed in the download manager list.
It moves the data from the QWebEngineDownloadItem into the QFile as well
as update the information/progressbar and report errors.
*/
DownloadWidget::DownloadWidget(QWebEngineDownloadItem*download,QWidget*parent)
: QWidget(parent)
, m_bytesReceived(0)
, m_download(download)
{
setupUi(this);
QPalette p = downloadInfoLabel->palette();
p.setColor(QPalette::Text,Qt::darkGray);
downloadInfoLabel->setPalette(p);
progressBar->setMaximum(0);
connect(stopButton, SIGNAL(clicked()),this, SLOT(stop()));
connect(openButton, SIGNAL(clicked()),this, SLOT(open()));
if (download) {
m_file.setFile(download->path());
m_url = download->url();
}
init();
}
void DownloadWidget::init()
{
if (m_download) {
connect(m_download.data(), SIGNAL(downloadProgress(qint64,qint64)),this, SLOT(downloadProgress(qint64,qint64)));
connect(m_download.data(), SIGNAL(finished()),this, SLOT(finished()));
}
// reset info
downloadInfoLabel->clear();
progressBar->setValue(0);
getFileName();
// start timer for the download estimation
m_downloadTime.start();
}
bool DownloadWidget::getFileName(bool promptForFileName)
{
QSettings settings;
settings.beginGroup(QLatin1String("downloadmanager"));
QString defaultLocation =QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
if (m_file.absoluteDir().exists())
defaultLocation = m_file.absolutePath();
QString downloadDirectory = settings.value(QLatin1String("downloadDirectory"), defaultLocation).toString();
if (!downloadDirectory.isEmpty())
downloadDirectory += QLatin1Char('/');
QString defaultFileName =QFileInfo(downloadDirectory, m_file.fileName()).absoluteFilePath();
QString fileName = defaultFileName;
if (promptForFileName) {
fileName =QFileDialog::getSaveFileName(this, tr("Save File"), defaultFileName);
if (fileName.isEmpty()) {
if (m_download)
m_download->cancel();
fileNameLabel->setText(tr("Download canceled: %1").arg(QFileInfo(defaultFileName).fileName()));
returnfalse;
}
}
m_file.setFile(fileName);
if (m_download && m_download->state() ==QWebEngineDownloadItem::DownloadRequested)
m_download->setPath(m_file.absoluteFilePath());
fileNameLabel->setText(m_file.fileName());
returntrue;
}
void DownloadWidget::stop()
{
setUpdatesEnabled(false);
stopButton->setEnabled(false);
stopButton->hide();
setUpdatesEnabled(true);
if (m_download)
m_download->cancel();
emit statusChanged();
}
void DownloadWidget::open()
{
QUrl url =QUrl::fromLocalFile(m_file.absoluteFilePath());
QDesktopServices::openUrl(url);
}
void DownloadWidget::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 DownloadWidget::updateInfoLabel()
{
qint64 bytesTotal = progressBar->maximum();
// update info labeldouble 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 0if (timeRemaining ==0)
timeRemaining =1;
QString info;
if (!downloadedSuccessfully()) {
QString remaining;
if (bytesTotal !=0)
remaining = tr("- %4 %5 remaining")
.arg(timeRemaining)
.arg(timeRemainingString);
info = 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 = tr("%1 of %2 - Stopped")
.arg(dataString(m_bytesReceived))
.arg(dataString(bytesTotal));
} else
info = dataString(m_bytesReceived);
}
downloadInfoLabel->setText(info);
}
QString DownloadWidget::dataString(int size) const
{
QString unit;
if (size <1024) {
unit = tr("bytes");
} elseif (size <1024*1024) {
size /=1024;
unit = tr("kB");
} else {
size /=1024*1024;
unit = tr("MB");
}
returnQString(QLatin1String("%1 %2")).arg(size).arg(unit);
}
bool DownloadWidget::downloading() const
{
return (progressBar->isVisible());
}
bool DownloadWidget::downloadedSuccessfully() const
{
bool completed = m_download
&& m_download->isFinished()
&& m_download->state() ==QWebEngineDownloadItem::DownloadCompleted;
return completed ||!stopButton->isVisible();
}
void DownloadWidget::finished()
{
if (m_download) {
QWebEngineDownloadItem::DownloadState state = m_download->state();
QString message;
bool interrupted =false;
switch (state) {
caseQWebEngineDownloadItem::DownloadRequested: // Fall-through.caseQWebEngineDownloadItem::DownloadInProgress:
Q_UNREACHABLE();
break;
caseQWebEngineDownloadItem::DownloadCompleted:
break;
caseQWebEngineDownloadItem::DownloadCancelled:
message =QStringLiteral("Download cancelled");
interrupted =true;
break;
caseQWebEngineDownloadItem::DownloadInterrupted:
message =QStringLiteral("Download interrupted");
interrupted =true;
break;
}
if (interrupted) {
downloadInfoLabel->setText(message);
return;
}
}
progressBar->hide();
stopButton->setEnabled(false);
stopButton->hide();
updateInfoLabel();
emit statusChanged();
}
/*!
DownloadManager is a Dialog that contains a list of DownloadWidgets
It is a basic download manager. It only downloads the file, doesn't do BitTorrent,
extract zipped files or anything fancy.
*/
DownloadManager::DownloadManager(QWidget*parent)
: QDialog(parent)
, m_autoSaver(new AutoSaver(this))
, 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();
}
DownloadManager::~DownloadManager()
{
m_autoSaver->changeOccurred();
m_autoSaver->saveIfNeccessary();
if (m_iconProvider)
delete m_iconProvider;
}
int DownloadManager::activeDownloads() const
{
int count =0;
for (int i =0; i < m_downloads.count(); ++i) {
if (m_downloads.at(i)->stopButton->isEnabled())
++count;
}
return count;
}
void DownloadManager::download(QWebEngineDownloadItem*download)
{
DownloadWidget *widget =new DownloadWidget(download,this);
addItem(widget);
}
void DownloadManager::addItem(DownloadWidget *widget)
{
connect(widget, SIGNAL(statusChanged()),this, SLOT(updateRow()));
int row = m_downloads.count();
m_model->beginInsertRows(QModelIndex(), row, row);
m_downloads.append(widget);
m_model->endInsertRows();
updateItemCount();
if (row ==0)
show();
downloadsView->setIndexWidget(m_model->index(row,0), widget);
QIcon icon = style()->standardIcon(QStyle::SP_FileIcon);
widget->fileIcon->setPixmap(icon.pixmap(48,48));
downloadsView->setRowHeight(row, widget->sizeHint().height());
}
void DownloadManager::updateRow()
{
DownloadWidget *widget = qobject_cast<DownloadWidget*>(sender());
int row = m_downloads.indexOf(widget);
if (-1== row)
return;
if (!m_iconProvider)
m_iconProvider =newQFileIconProvider();
QIcon icon = m_iconProvider->icon(widget->m_file);
if (icon.isNull())
icon = style()->standardIcon(QStyle::SP_FileIcon);
widget->fileIcon->setPixmap(icon.pixmap(48,48));
downloadsView->setRowHeight(row, widget->minimumSizeHint().height());
bool remove =false;
if (!widget->downloading()
&& BrowserApplication::instance()->privateBrowsing())
remove =true;
if (widget->downloadedSuccessfully()
&& removePolicy() == DownloadManager::SuccessFullDownload) {
remove =true;
}
if (remove)
m_model->removeRow(row);
cleanupButton->setEnabled(m_downloads.count() - activeDownloads() >0);
}
DownloadManager::RemovePolicy DownloadManager::removePolicy() const
{
return m_removePolicy;
}
void DownloadManager::setRemovePolicy(RemovePolicy policy)
{
if (policy == m_removePolicy)
return;
m_removePolicy = policy;
m_autoSaver->changeOccurred();
}
void DownloadManager::save() const
{
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"), m_downloads[i]->m_file.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 DownloadManager::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 (done &&!url.isEmpty() &&!fileName.isEmpty()) {
DownloadWidget *widget =new DownloadWidget(0,this);
widget->m_file.setFile(fileName);
widget->fileNameLabel->setText(widget->m_file.fileName());
widget->m_url = url;
widget->stopButton->setVisible(false);
widget->stopButton->setEnabled(false);
widget->progressBar->hide();
addItem(widget);
}
key =QString(QLatin1String("download_%1_")).arg(++i);
}
cleanupButton->setEnabled(m_downloads.count() - activeDownloads() >0);
}
void DownloadManager::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();
}
void DownloadManager::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)
{
}
QVariant DownloadModel::data(constQModelIndex&index,int role) const
{
if (index.row() <0|| index.row() >= rowCount(index.parent()))
returnQVariant();
if (role ==Qt::ToolTipRole)
if (!m_downloadManager->m_downloads.at(index.row())->downloadedSuccessfully())
return m_downloadManager->m_downloads.at(index.row())->downloadInfoLabel->text();
returnQVariant();
}
int DownloadModel::rowCount(constQModelIndex&parent) const
{
return (parent.isValid()) ?0 : m_downloadManager->m_downloads.count();
}
bool DownloadModel::removeRows(int row,int count,constQModelIndex&parent)
{
if (parent.isValid())
returnfalse;
int lastRow = row + count -1;
for (int i = lastRow; i >= row; --i) {
if (m_downloadManager->m_downloads.at(i)->downloadedSuccessfully()) {
beginRemoveRows(parent, i, i);
m_downloadManager->m_downloads.takeAt(i)->deleteLater();
endRemoveRows();
}
}
m_downloadManager->m_autoSaver->changeOccurred();
returntrue;
}