summaryrefslogtreecommitdiffstats
path: root/src/libs/kdtools/kdupdaterfiledownloader.cpp
diff options
context:
space:
mode:
authorkh1 <karsten.heimrich@nokia.com>2012-03-15 14:53:47 +0100
committerKarsten Heimrich <karsten.heimrich@nokia.com>2012-03-19 16:14:04 +0100
commitbe3b47d0d504a3409ce66bd77bb8c0acff87c4f5 (patch)
tree09dfb02d484a4f395991972b828da71400fb761a /src/libs/kdtools/kdupdaterfiledownloader.cpp
parent9fd62353cf7f973d78cd2093328ac15b5c4980b6 (diff)
Reorganize the tree, have better ifw.pri. Shadow build support.
Change-Id: I01fb12537f863ed0744979973c7e4153889cc5cb Reviewed-by: Tim Jenssen <tim.jenssen@nokia.com>
Diffstat (limited to 'src/libs/kdtools/kdupdaterfiledownloader.cpp')
-rw-r--r--src/libs/kdtools/kdupdaterfiledownloader.cpp1294
1 files changed, 1294 insertions, 0 deletions
diff --git a/src/libs/kdtools/kdupdaterfiledownloader.cpp b/src/libs/kdtools/kdupdaterfiledownloader.cpp
new file mode 100644
index 000000000..c36b78402
--- /dev/null
+++ b/src/libs/kdtools/kdupdaterfiledownloader.cpp
@@ -0,0 +1,1294 @@
+/****************************************************************************
+** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved.
+**
+** This file is part of the KD Tools library.
+**
+** Licensees holding valid commercial KD Tools licenses may use this file in
+** accordance with the KD Tools Commercial License Agreement provided with
+** the Software.
+**
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU Lesser General Public License version 2 and version 3 as published by the
+** Free Software Foundation and appearing in the file LICENSE.LGPL included.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** Contact info@kdab.com if any conditions of this licensing are not
+** clear to you.
+**
+**********************************************************************/
+
+#include "kdupdaterfiledownloader_p.h"
+#include "kdupdaterfiledownloaderfactory.h"
+
+#include <QFile>
+#include <QFtp>
+#include <QNetworkAccessManager>
+#include <QNetworkProxyFactory>
+#include <QPointer>
+#include <QUrl>
+#include <QTemporaryFile>
+#include <QFileInfo>
+#include <QCryptographicHash>
+#include <QThreadPool>
+#include <QDebug>
+
+#include <QBasicTimer>
+#include <QTimerEvent>
+
+using namespace KDUpdater;
+
+static double calcProgress(qint32 done, qint32 total)
+{
+ return total ? (double(done) / double(total)) : 0;
+}
+
+static QString format(double data)
+{
+ if (data < 1024.0)
+ return KDUpdater::FileDownloader::tr("%L1 B").arg(data);
+ data /= 1024.0;
+ if (data < 1024.0)
+ return KDUpdater::FileDownloader::tr("%L1 KB").arg(data, 0, 'f', 2);
+ data /= 1024.0;
+ if (data < 1024.0)
+ return KDUpdater::FileDownloader::tr("%L1 MB").arg(data, 0, 'f', 2);
+ data /= 1024.0;
+ return KDUpdater::FileDownloader::tr("%L1 GB").arg(data, 0, 'f', 2);
+}
+
+QByteArray KDUpdater::calculateHash(QIODevice* device, QCryptographicHash::Algorithm algo)
+{
+ Q_ASSERT(device);
+ QCryptographicHash hash(algo);
+ QByteArray buffer;
+ buffer.resize(512 * 1024);
+ while (true) {
+ const qint64 numRead = device->read(buffer.data(), buffer.size());
+ if (numRead <= 0)
+ return hash.result();
+ hash.addData(buffer.constData(), numRead);
+ }
+ return QByteArray(); // never reached
+}
+
+QByteArray KDUpdater::calculateHash(const QString &path, QCryptographicHash::Algorithm algo)
+{
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ return QByteArray();
+ return calculateHash(&file, algo);
+}
+
+
+// -- HashVerificationJob
+
+class HashVerificationJob::Private
+{
+public:
+ Private()
+ : hash(QCryptographicHash::Sha1)
+ , error(HashVerificationJob::ReadError)
+ , timerId(-1) { }
+
+ QPointer<QIODevice> device;
+ QByteArray sha1Sum;
+ QCryptographicHash hash;
+ HashVerificationJob::Error error;
+ int timerId;
+};
+
+HashVerificationJob::HashVerificationJob(QObject* parent)
+ : QObject(parent)
+ , d(new Private)
+{
+}
+
+HashVerificationJob::~HashVerificationJob()
+{
+ delete d;
+}
+
+void HashVerificationJob::setDevice(QIODevice* dev)
+{
+ d->device = dev;
+}
+
+void HashVerificationJob::setSha1Sum(const QByteArray &sum)
+{
+ d->sha1Sum = sum;
+}
+
+int HashVerificationJob::error() const
+{
+ return d->error;
+}
+
+bool HashVerificationJob::hasError() const
+{
+ return d->error != NoError;
+}
+
+void HashVerificationJob::start()
+{
+ Q_ASSERT(d->device);
+ d->timerId = startTimer(0);
+}
+
+void HashVerificationJob::emitFinished()
+{
+ emit finished(this);
+ deleteLater();
+}
+
+void HashVerificationJob::timerEvent(QTimerEvent*)
+{
+ Q_ASSERT(d->timerId >= 0);
+ if (d->sha1Sum.isEmpty()) {
+ killTimer(d->timerId);
+ d->timerId = -1;
+ d->error = NoError;
+ d->device->close();
+ emitFinished();
+ return;
+ }
+
+ QByteArray buf;
+ buf.resize(128 * 1024);
+ const qint64 read = d->device->read(buf.data(), buf.size());
+ if (read > 0) {
+ d->hash.addData(buf.constData(), read);
+ return;
+ }
+
+ d->error = d->hash.result() == d->sha1Sum ? NoError : SumsDifferError;
+ killTimer(d->timerId);
+ d->timerId = -1;
+ emitFinished();
+}
+
+
+// -- KDUpdater::FileDownloader
+
+/*!
+ \internal
+ \ingroup kdupdater
+ \class KDUpdater::FileDownloader kdupdaterfiledownloader.h
+
+ Base class for file downloaders used in KDUpdater. File downloaders are used by
+ the KDUpdater::Update class to download update files. Each subclass of FileDownloader
+ can download file from a specific category of sources (e.g. local, ftp, http etc).
+
+ This is an internal class, not a part of the public API. Currently we have three
+ subclasses of FileDownloader
+ \li LocalFileDownloader - downloads from the local file system
+ \li FtpDownloader - downloads from a FTP site
+ \li HttpDownloader - downloads from a HTTP site
+
+ Usage
+
+ \code
+ KDUpdater::FileDownloader* downloader = new KDUpdater::(some subclass name)
+
+ downloader->setUrl(url);
+ downloader->download();
+
+ // wait for downloadCompleted() signal
+
+ QString downloadedFile = downloader->downloadedFileName();
+ \endcode
+*/
+
+struct KDUpdater::FileDownloader::Private
+{
+ Private()
+ : autoRemove(true)
+ , m_speedTimerInterval(100)
+ , m_bytesReceived(0)
+ , m_bytesToReceive(0)
+ , m_currentSpeedBin(0)
+ , m_sampleIndex(0)
+ , m_downloadSpeed(0)
+ , m_factory(0)
+ {
+ memset(m_samples, 0, sizeof(m_samples));
+ }
+
+ ~Private()
+ {
+ delete m_factory;
+ }
+
+ QUrl url;
+ QString scheme;
+ QByteArray sha1Sum;
+ QString errorString;
+ bool autoRemove;
+ bool followRedirect;
+
+ QBasicTimer m_timer;
+ int m_speedTimerInterval;
+
+ qint64 m_bytesReceived;
+ qint64 m_bytesToReceive;
+
+ mutable qint64 m_samples[50];
+ mutable qint64 m_currentSpeedBin;
+ mutable quint32 m_sampleIndex;
+ mutable qint64 m_downloadSpeed;
+
+ QAuthenticator m_authenticator;
+ FileDownloaderProxyFactory *m_factory;
+};
+
+KDUpdater::FileDownloader::FileDownloader(const QString &scheme, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->scheme = scheme;
+ d->followRedirect = false;
+}
+
+KDUpdater::FileDownloader::~FileDownloader()
+{
+ delete d;
+}
+
+void KDUpdater::FileDownloader::setUrl(const QUrl &url)
+{
+ d->url = url;
+}
+
+QUrl KDUpdater::FileDownloader::url() const
+{
+ return d->url;
+}
+
+void KDUpdater::FileDownloader::setSha1Sum(const QByteArray &sum)
+{
+ d->sha1Sum = sum;
+}
+
+QByteArray KDUpdater::FileDownloader::sha1Sum() const
+{
+ return d->sha1Sum;
+}
+
+QString FileDownloader::errorString() const
+{
+ return d->errorString;
+}
+
+void FileDownloader::setDownloadAborted(const QString &error)
+{
+ d->errorString = error;
+ emit downloadStatus(error);
+ emit downloadAborted(error);
+}
+
+void KDUpdater::FileDownloader::setDownloadCompleted(const QString &path)
+{
+ HashVerificationJob *job = new HashVerificationJob;
+ QFile *file = new QFile(path, job);
+ if (!file->open(QIODevice::ReadOnly)) {
+ emit downloadProgress(1);
+ onError();
+ setDownloadAborted(tr("Could not reopen downloaded file %1 for reading: %2").arg(path,
+ file->errorString()));
+ delete job;
+ return;
+ }
+
+ job->setDevice(file);
+ job->setSha1Sum(d->sha1Sum);
+ connect(job, SIGNAL(finished(KDUpdater::HashVerificationJob*)), this,
+ SLOT(sha1SumVerified(KDUpdater::HashVerificationJob*)));
+ job->start();
+}
+
+void KDUpdater::FileDownloader::setDownloadCanceled()
+{
+ emit downloadCanceled();
+ emit downloadStatus(tr("Download canceled."));
+}
+
+void KDUpdater::FileDownloader::sha1SumVerified(KDUpdater::HashVerificationJob *job)
+{
+ if (job->hasError()) {
+ onError();
+ setDownloadAborted(tr("Cryptographic hashes do not match."));
+ } else {
+ onSuccess();
+ emit downloadCompleted();
+ emit downloadStatus(tr("Download finished."));
+ }
+}
+
+QString KDUpdater::FileDownloader::scheme() const
+{
+ return d->scheme;
+}
+
+void KDUpdater::FileDownloader::setAutoRemoveDownloadedFile(bool val)
+{
+ d->autoRemove = val;
+}
+
+void KDUpdater::FileDownloader::setFollowRedirects(bool val)
+{
+ d->followRedirect = val;
+}
+
+bool KDUpdater::FileDownloader::followRedirects() const
+{
+ return d->followRedirect;
+}
+
+bool KDUpdater::FileDownloader::isAutoRemoveDownloadedFile() const
+{
+ return d->autoRemove;
+}
+
+void KDUpdater::FileDownloader::download()
+{
+ QMetaObject::invokeMethod(this, "doDownload", Qt::QueuedConnection);
+}
+
+void KDUpdater::FileDownloader::cancelDownload()
+{
+ // Do nothing
+}
+
+void KDUpdater::FileDownloader::runDownloadSpeedTimer()
+{
+ if (!d->m_timer.isActive())
+ d->m_timer.start(d->m_speedTimerInterval, this);
+}
+
+void KDUpdater::FileDownloader::stopDownloadSpeedTimer()
+{
+ d->m_timer.stop();
+}
+
+void KDUpdater::FileDownloader::addSample(qint64 sample)
+{
+ d->m_currentSpeedBin += sample;
+}
+
+int KDUpdater::FileDownloader::downloadSpeedTimerId() const
+{
+ return d->m_timer.timerId();
+}
+
+void KDUpdater::FileDownloader::setProgress(qint64 bytesReceived, qint64 bytesToReceive)
+{
+ d->m_bytesReceived = bytesReceived;
+ d->m_bytesToReceive = bytesToReceive;
+}
+
+void KDUpdater::FileDownloader::emitDownloadSpeed()
+{
+ unsigned int windowSize = sizeof(d->m_samples) / sizeof(qint64);
+
+ // add speed of last time bin to the window
+ d->m_samples[d->m_sampleIndex % windowSize] = d->m_currentSpeedBin;
+ d->m_currentSpeedBin = 0; // reset bin for next time interval
+
+ // advance the sample index
+ d->m_sampleIndex++;
+ d->m_downloadSpeed = 0;
+
+ // dynamic window size until the window is completely filled
+ if (d->m_sampleIndex < windowSize)
+ windowSize = d->m_sampleIndex;
+
+ for (unsigned int i = 0; i < windowSize; ++i)
+ d->m_downloadSpeed += d->m_samples[i];
+
+ d->m_downloadSpeed /= windowSize; // computer average
+ d->m_downloadSpeed *= 1000.0 / d->m_speedTimerInterval; // rescale to bytes/second
+
+ emit downloadSpeed(d->m_downloadSpeed);
+}
+
+void KDUpdater::FileDownloader::emitDownloadStatus()
+{
+ QString status;
+ if (d->m_bytesToReceive > 0) {
+ QString bytesReceived = format(d->m_bytesReceived);
+ const QString bytesToReceive = format(d->m_bytesToReceive);
+
+ const QString tmp = bytesToReceive.mid(bytesToReceive.indexOf(QLatin1Char(' ')));
+ if (bytesReceived.endsWith(tmp))
+ bytesReceived.chop(tmp.length());
+
+ status = bytesReceived + tr(" of ") + bytesToReceive;
+ } else {
+ if (d->m_bytesReceived > 0)
+ status = format(d->m_bytesReceived) + tr(" downloaded.");
+ }
+
+ status += QLatin1String(" (") + format(d->m_downloadSpeed) + tr("/sec") + QLatin1Char(')');
+ if (d->m_bytesToReceive > 0 && d->m_downloadSpeed > 0) {
+ const qint64 time = (d->m_bytesToReceive - d->m_bytesReceived) / d->m_downloadSpeed;
+
+ int s = time % 60;
+ const int d = time / 86400;
+ const int h = (time / 3600) - (d * 24);
+ const int m = (time / 60) - (d * 1440) - (h * 60);
+
+ QString days;
+ if (d > 0)
+ days = QString::number(d) + (d < 2 ? tr(" day") : tr(" days")) + QLatin1String(", ");
+
+ QString hours;
+ if (h > 0)
+ hours = QString::number(h) + (h < 2 ? tr(" hour") : tr(" hours")) + QLatin1String(", ");
+
+ QString minutes;
+ if (m > 0)
+ minutes = QString::number(m) + (m < 2 ? tr(" minute") : tr(" minutes"));
+
+ QString seconds;
+ if (s >= 0 && minutes.isEmpty()) {
+ s = (s <= 0 ? 1 : s);
+ seconds = QString::number(s) + (s < 2 ? tr(" second") : tr(" seconds"));
+ }
+ status += tr(" - ") + days + hours + minutes + seconds + tr(" remaining.");
+ } else {
+ status += tr(" - unknown time remaining.");
+ }
+
+ emit downloadStatus(status);
+}
+
+void KDUpdater::FileDownloader::emitDownloadProgress()
+{
+ emit downloadProgress(d->m_bytesReceived, d->m_bytesToReceive);
+}
+
+void KDUpdater::FileDownloader::emitEstimatedDownloadTime()
+{
+ if (d->m_bytesToReceive <= 0 || d->m_downloadSpeed <= 0) {
+ emit estimatedDownloadTime(-1);
+ return;
+ }
+ emit estimatedDownloadTime((d->m_bytesToReceive - d->m_bytesReceived) / d->m_downloadSpeed);
+}
+
+/*!
+ Returns a copy of the proxy factory that this FileDownloader object is using to determine the proxies to
+ be used for requests.
+*/
+FileDownloaderProxyFactory *KDUpdater::FileDownloader::proxyFactory() const
+{
+ if (d->m_factory)
+ return d->m_factory->clone();
+ return 0;
+}
+
+/*!
+ Sets the proxy factory for this class to be \a factory. A proxy factory is used to determine a more
+ specific list of proxies to be used for a given request, instead of trying to use the same proxy value
+ for all requests. This might only be of use for http or ftp requests.
+*/
+void KDUpdater::FileDownloader::setProxyFactory(FileDownloaderProxyFactory *factory)
+{
+ delete d->m_factory;
+ d->m_factory = factory;
+}
+
+/*!
+ Returns a copy of the authenticator that this FileDownloader object is using to set the username and
+ password for download request.
+*/
+QAuthenticator KDUpdater::FileDownloader::authenticator() const
+{
+ return d->m_authenticator;
+}
+
+/*!
+ Sets the authenticator object for this class to be \a authenticator. A authenticator is used to
+ pass on the required authentication information. This might only be of use for http or ftp requests.
+*/
+void KDUpdater::FileDownloader::setAuthenticator(const QAuthenticator &authenticator)
+{
+ d->m_authenticator = authenticator;
+}
+
+// -- KDUpdater::LocalFileDownloader
+
+/*
+ Even though QFile::copy() does the task of copying local files from one place
+ to another, I prefer to use the timer and copy one block of data per unit time.
+
+ This is because, it is possible that the user of KDUpdater is simultaneously
+ downloading several files. Sometimes in tandem with other file downloaders.
+ If the local file that is being downloaded takes a long time; then that will
+ hang the other downloads.
+
+ On the other hand, local downloads need not actually download the file. It can
+ simply pass on the source file as destination file. At this moment however,
+ I think the user of LocalFileDownloader will assume that the downloaded file
+ can be fiddled around with without worrying about whether it would mess up
+ the original source or not.
+*/
+
+struct KDUpdater::LocalFileDownloader::Private
+{
+ Private()
+ : source(0)
+ , destination(0)
+ , downloaded(false)
+ , timerId(-1)
+ {}
+
+ QFile *source;
+ QFile *destination;
+ QString destFileName;
+ bool downloaded;
+ int timerId;
+};
+
+KDUpdater::LocalFileDownloader::LocalFileDownloader(QObject *parent)
+ : KDUpdater::FileDownloader(QLatin1String("file"), parent)
+ , d (new Private)
+{
+}
+
+KDUpdater::LocalFileDownloader::~LocalFileDownloader()
+{
+ if (this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty())
+ QFile::remove(d->destFileName);
+
+ delete d;
+}
+
+bool KDUpdater::LocalFileDownloader::canDownload() const
+{
+ QFileInfo fi(url().toLocalFile());
+ return fi.exists() && fi.isReadable();
+}
+
+bool KDUpdater::LocalFileDownloader::isDownloaded() const
+{
+ return d->downloaded;
+}
+
+void KDUpdater::LocalFileDownloader::doDownload()
+{
+ // Already downloaded
+ if (d->downloaded)
+ return;
+
+ // Already started downloading
+ if (d->timerId >= 0)
+ return;
+
+ // Open source and destination files
+ QString localFile = this->url().toLocalFile();
+ d->source = new QFile(localFile, this);
+ if (!d->source->open(QFile::ReadOnly)) {
+ onError();
+ setDownloadAborted(tr("Cannot open source file '%1' for reading.").arg(QFileInfo(localFile)
+ .fileName()));
+ return;
+ }
+
+ if (d->destFileName.isEmpty()) {
+ QTemporaryFile *file = new QTemporaryFile(this);
+ file->open();
+ d->destination = file;
+ } else {
+ d->destination = new QFile(d->destFileName, this);
+ d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate);
+ }
+
+ if (!d->destination->isOpen()) {
+ onError();
+ setDownloadAborted(tr("Cannot open destination file '%1' for writing.")
+ .arg(QFileInfo(d->destination->fileName()).fileName()));
+ return;
+ }
+
+ runDownloadSpeedTimer();
+ // Start a timer and kickoff the copy process
+ d->timerId = startTimer(0); // as fast as possible
+
+ emit downloadStarted();
+ emit downloadProgress(0);
+}
+
+QString KDUpdater::LocalFileDownloader::downloadedFileName() const
+{
+ return d->destFileName;
+}
+
+void KDUpdater::LocalFileDownloader::setDownloadedFileName(const QString &name)
+{
+ d->destFileName = name;
+}
+
+KDUpdater::LocalFileDownloader *KDUpdater::LocalFileDownloader::clone(QObject *parent) const
+{
+ return new LocalFileDownloader(parent);
+}
+
+void KDUpdater::LocalFileDownloader::cancelDownload()
+{
+ if (d->timerId < 0)
+ return;
+
+ killTimer(d->timerId);
+ d->timerId = -1;
+
+ onError();
+ setDownloadCanceled();
+}
+
+void KDUpdater::LocalFileDownloader::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == d->timerId) {
+ if (!d->source || !d->destination)
+ return;
+
+ const qint64 blockSize = 32768;
+ QByteArray buffer;
+ buffer.resize(blockSize);
+ const qint64 numRead = d->source->read(buffer.data(), buffer.size());
+ qint64 toWrite = numRead;
+ while (toWrite > 0) {
+ const qint64 numWritten = d->destination->write(buffer.constData() + numRead - toWrite, toWrite);
+ if (numWritten < 0) {
+ killTimer(d->timerId);
+ d->timerId = -1;
+ onError();
+ setDownloadAborted(tr("Writing to %1 failed: %2").arg(d->destination->fileName(),
+ d->destination->errorString()));
+ return;
+ }
+ toWrite -= numWritten;
+ }
+ addSample(numRead);
+
+ if (numRead > 0) {
+ setProgress(d->source->pos(), d->source->size());
+ emit downloadProgress(calcProgress(d->source->pos(), d->source->size()));
+ return;
+ }
+
+ d->destination->flush();
+
+ killTimer(d->timerId);
+ d->timerId = -1;
+
+ setDownloadCompleted(d->destination->fileName());
+ } else if (event->timerId() == downloadSpeedTimerId()) {
+ emitDownloadSpeed();
+ emitDownloadStatus();
+ emitDownloadProgress();
+ emitEstimatedDownloadTime();
+ }
+}
+
+void LocalFileDownloader::onSuccess()
+{
+ d->downloaded = true;
+ d->destFileName = d->destination->fileName();
+ if (QTemporaryFile *file = dynamic_cast<QTemporaryFile *>(d->destination))
+ file->setAutoRemove(false);
+ d->destination->close();
+ delete d->destination;
+ d->destination = 0;
+ delete d->source;
+ d->source = 0;
+ stopDownloadSpeedTimer();
+}
+
+void LocalFileDownloader::onError()
+{
+ d->downloaded = false;
+ d->destFileName.clear();
+ delete d->destination;
+ d->destination = 0;
+ delete d->source;
+ d->source = 0;
+ stopDownloadSpeedTimer();
+}
+
+
+// -- ResourceFileDownloader
+
+struct KDUpdater::ResourceFileDownloader::Private
+{
+ Private()
+ : downloaded(false),
+ timerId(-1)
+ {}
+
+ QString destFileName;
+ bool downloaded;
+ int timerId;
+};
+
+KDUpdater::ResourceFileDownloader::ResourceFileDownloader(QObject *parent)
+ : KDUpdater::FileDownloader(QLatin1String("resource"), parent)
+ , d(new Private)
+{
+}
+
+KDUpdater::ResourceFileDownloader::~ResourceFileDownloader()
+{
+ delete d;
+}
+
+bool KDUpdater::ResourceFileDownloader::canDownload() const
+{
+ QUrl url = this->url();
+ url.setScheme(QString::fromLatin1("file"));
+ QString localFile = QString::fromLatin1(":%1").arg(url.toLocalFile());
+ QFileInfo fi(localFile);
+ return fi.exists() && fi.isReadable();
+}
+
+bool KDUpdater::ResourceFileDownloader::isDownloaded() const
+{
+ return d->downloaded;
+}
+
+void KDUpdater::ResourceFileDownloader::doDownload()
+{
+ // Already downloaded
+ if (d->downloaded)
+ return;
+
+ // Already started downloading
+ if (d->timerId >= 0)
+ return;
+
+ // Open source and destination files
+ QUrl url = this->url();
+ url.setScheme(QString::fromLatin1("file"));
+ d->destFileName = QString::fromLatin1(":%1").arg(url.toLocalFile());
+
+ // Start a timer and kickoff the copy process
+ d->timerId = startTimer(0); // as fast as possible
+ emit downloadStarted();
+ emit downloadProgress(0);
+}
+
+QString KDUpdater::ResourceFileDownloader::downloadedFileName() const
+{
+ return d->destFileName;
+}
+
+void KDUpdater::ResourceFileDownloader::setDownloadedFileName(const QString &/*name*/)
+{
+ Q_ASSERT_X(false, "KDUpdater::ResourceFileDownloader::setDownloadedFileName", "Not supported!");
+}
+
+KDUpdater::ResourceFileDownloader *KDUpdater::ResourceFileDownloader::clone(QObject *parent) const
+{
+ return new ResourceFileDownloader(parent);
+}
+
+void KDUpdater::ResourceFileDownloader::cancelDownload()
+{
+ if (d->timerId < 0)
+ return;
+
+ killTimer(d->timerId);
+ d->timerId = -1;
+
+ setDownloadCanceled();
+}
+
+void KDUpdater::ResourceFileDownloader::timerEvent(QTimerEvent *)
+{
+ killTimer(d->timerId);
+ d->timerId = -1;
+ setDownloadCompleted(d->destFileName);
+}
+
+void KDUpdater::ResourceFileDownloader::onSuccess()
+{
+ d->downloaded = true;
+}
+
+void KDUpdater::ResourceFileDownloader::onError()
+{
+ d->downloaded = false;
+}
+
+
+// -- KDUpdater::FtpFileDownloader
+
+struct KDUpdater::FtpDownloader::Private
+{
+ Private()
+ : ftp(0)
+ , destination(0)
+ , downloaded(false)
+ , ftpCmdId(-1)
+ , aborted(false)
+ {}
+
+ QFtp *ftp;
+ QFile *destination;
+ QString destFileName;
+ bool downloaded;
+ int ftpCmdId;
+ bool aborted;
+};
+
+KDUpdater::FtpDownloader::FtpDownloader(QObject *parent)
+ : KDUpdater::FileDownloader(QLatin1String("ftp"), parent)
+ , d(new Private)
+{
+}
+
+KDUpdater::FtpDownloader::~FtpDownloader()
+{
+ if (this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty())
+ QFile::remove(d->destFileName);
+
+ delete d;
+}
+
+bool KDUpdater::FtpDownloader::canDownload() const
+{
+ // TODO: Check whether the ftp file actually exists or not.
+ return true;
+}
+
+bool KDUpdater::FtpDownloader::isDownloaded() const
+{
+ return d->downloaded;
+}
+
+void KDUpdater::FtpDownloader::doDownload()
+{
+ if (d->downloaded)
+ return;
+
+ if (d->ftp)
+ return;
+
+ d->ftp = new QFtp(this);
+ connect(d->ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
+ connect(d->ftp, SIGNAL(commandStarted(int)), this, SLOT(ftpCmdStarted(int)));
+ connect(d->ftp, SIGNAL(commandFinished(int, bool)), this, SLOT(ftpCmdFinished(int, bool)));
+ connect(d->ftp, SIGNAL(stateChanged(int)), this, SLOT(ftpStateChanged(int)));
+ connect(d->ftp, SIGNAL(dataTransferProgress(qint64, qint64)), this,
+ SLOT(ftpDataTransferProgress(qint64, qint64)));
+ connect(d->ftp, SIGNAL(readyRead()), this, SLOT(ftpReadyRead()));
+
+ if (FileDownloaderProxyFactory *factory = proxyFactory()) {
+ const QList<QNetworkProxy> proxies = factory->queryProxy(QNetworkProxyQuery(url()));
+ if (!proxies.isEmpty())
+ d->ftp->setProxy(proxies.at(0).hostName(), proxies.at(0).port());
+ delete factory;
+ }
+
+ d->ftp->connectToHost(url().host(), url().port(21));
+ d->ftp->login(authenticator().user(), authenticator().password());
+}
+
+QString KDUpdater::FtpDownloader::downloadedFileName() const
+{
+ return d->destFileName;
+}
+
+void KDUpdater::FtpDownloader::setDownloadedFileName(const QString &name)
+{
+ d->destFileName = name;
+}
+
+KDUpdater::FtpDownloader *KDUpdater::FtpDownloader::clone(QObject *parent) const
+{
+ return new FtpDownloader(parent);
+}
+
+void KDUpdater::FtpDownloader::cancelDownload()
+{
+ if (d->ftp) {
+ d->aborted = true;
+ d->ftp->abort();
+ }
+}
+
+void KDUpdater::FtpDownloader::ftpDone(bool error)
+{
+ if (error) {
+ QString errorString;
+ if (d->ftp) {
+ errorString = d->ftp->errorString();
+ d->ftp->deleteLater();
+ d->ftp = 0;
+ d->ftpCmdId = -1;
+ }
+
+ onError();
+
+ if (d->aborted) {
+ d->aborted = false;
+ setDownloadCanceled();
+ } else {
+ setDownloadAborted(errorString);
+ }
+ }
+ //PENDING what about the non-error case??
+}
+
+void KDUpdater::FtpDownloader::ftpCmdStarted(int id)
+{
+ if (id != d->ftpCmdId)
+ return;
+
+ emit downloadStarted();
+ emit downloadProgress(0);
+}
+
+void KDUpdater::FtpDownloader::ftpCmdFinished(int id, bool error)
+{
+ if (id != d->ftpCmdId || error) // PENDING why error -> return??
+ return;
+
+ disconnect(d->ftp, 0, this, 0);
+ d->ftp->deleteLater();
+ d->ftp = 0;
+ d->ftpCmdId = -1;
+ d->destination->flush();
+
+ setDownloadCompleted(d->destination->fileName());
+}
+
+void FtpDownloader::onSuccess()
+{
+ d->downloaded = true;
+ d->destFileName = d->destination->fileName();
+ if (QTemporaryFile *file = dynamic_cast<QTemporaryFile *>(d->destination))
+ file->setAutoRemove(false);
+ delete d->destination;
+ d->destination = 0;
+ stopDownloadSpeedTimer();
+
+}
+
+void FtpDownloader::onError()
+{
+ d->downloaded = false;
+ d->destFileName.clear();
+ delete d->destination;
+ d->destination = 0;
+ stopDownloadSpeedTimer();
+}
+
+void KDUpdater::FtpDownloader::ftpStateChanged(int state)
+{
+ switch(state) {
+ case QFtp::Connected: {
+ // begin the download
+ if (d->destFileName.isEmpty()) {
+ QTemporaryFile *file = new QTemporaryFile(this);
+ file->open(); //PENDING handle error
+ d->destination = file;
+ } else {
+ d->destination = new QFile(d->destFileName, this);
+ d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate);
+ }
+ runDownloadSpeedTimer();
+ d->ftpCmdId = d->ftp->get(url().path());
+ } break;
+
+ case QFtp::Unconnected: {
+ // download was unconditionally aborted
+ disconnect(d->ftp, 0, this, 0);
+ d->ftp->deleteLater();
+ d->ftp = 0;
+ d->ftpCmdId = -1;
+ onError();
+ setDownloadAborted(tr("Download was aborted due to network errors."));
+ } break;
+ }
+}
+
+void KDUpdater::FtpDownloader::ftpDataTransferProgress(qint64 done, qint64 total)
+{
+ setProgress(done, total);
+ emit downloadProgress(calcProgress(done, total));
+}
+
+void KDUpdater::FtpDownloader::ftpReadyRead()
+{
+ static QByteArray buffer(16384, '\0');
+ while (d->ftp->bytesAvailable()) {
+ const qint64 read = d->ftp->read(buffer.data(), buffer.size());
+ qint64 written = 0;
+ while (written < read) {
+ const qint64 numWritten = d->destination->write(buffer.data() + written, read - written);
+ if (numWritten < 0) {
+ onError();
+ setDownloadAborted(tr("Cannot download %1: Writing to temporary file failed: %2")
+ .arg(url().toString(), d->destination->errorString()));
+ return;
+ }
+ written += numWritten;
+ }
+ addSample(written);
+ }
+}
+
+void KDUpdater::FtpDownloader::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == downloadSpeedTimerId()) {
+ emitDownloadSpeed();
+ emitDownloadStatus();
+ emitDownloadProgress();
+ emitEstimatedDownloadTime();
+ }
+}
+
+
+// -- KDUpdater::HttpDownloader
+
+struct KDUpdater::HttpDownloader::Private
+{
+ explicit Private(HttpDownloader *qq)
+ : q(qq)
+ , http(0)
+ , destination(0)
+ , downloaded(false)
+ , aborted(false)
+ , retrying(false)
+ , m_authenticationDone(false)
+ {}
+
+ HttpDownloader *const q;
+ QNetworkAccessManager manager;
+ QNetworkReply *http;
+ QFile *destination;
+ QString destFileName;
+ bool downloaded;
+ bool aborted;
+ bool retrying;
+ bool m_authenticationDone;
+
+ void shutDown()
+ {
+ disconnect(http, SIGNAL(finished()), q, SLOT(httpReqFinished()));
+ http->deleteLater();
+ http = 0;
+ destination->close();
+ destination->deleteLater();
+ destination = 0;
+ }
+};
+
+KDUpdater::HttpDownloader::HttpDownloader(QObject *parent)
+ : KDUpdater::FileDownloader(QLatin1String("http"), parent)
+ , d(new Private(this))
+{
+ connect(&d->manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this,
+ SLOT(onAuthenticationRequired(QNetworkReply*, QAuthenticator*)));
+}
+
+KDUpdater::HttpDownloader::~HttpDownloader()
+{
+ if (this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty())
+ QFile::remove(d->destFileName);
+ delete d;
+}
+
+bool KDUpdater::HttpDownloader::canDownload() const
+{
+ // TODO: Check whether the http file actually exists or not.
+ return true;
+}
+
+bool KDUpdater::HttpDownloader::isDownloaded() const
+{
+ return d->downloaded;
+}
+
+void KDUpdater::HttpDownloader::doDownload()
+{
+ if (d->downloaded)
+ return;
+
+ if (d->http)
+ return;
+
+ startDownload(url());
+ runDownloadSpeedTimer();
+}
+
+QString KDUpdater::HttpDownloader::downloadedFileName() const
+{
+ return d->destFileName;
+}
+
+void KDUpdater::HttpDownloader::setDownloadedFileName(const QString &name)
+{
+ d->destFileName = name;
+}
+
+KDUpdater::HttpDownloader *KDUpdater::HttpDownloader::clone(QObject *parent) const
+{
+ return new HttpDownloader(parent);
+}
+
+void KDUpdater::HttpDownloader::httpReadyRead()
+{
+ static QByteArray buffer(16384, '\0');
+ while (d->http->bytesAvailable()) {
+ const qint64 read = d->http->read(buffer.data(), buffer.size());
+ qint64 written = 0;
+ while (written < read) {
+ const qint64 numWritten = d->destination->write(buffer.data() + written, read - written);
+ if (numWritten < 0) {
+ const QString err = d->destination->errorString();
+ d->shutDown();
+ setDownloadAborted(tr("Cannot download %1: Writing to temporary file failed: %2")
+ .arg(url().toString(), err));
+ return;
+ }
+ written += numWritten;
+ }
+ addSample(written);
+ }
+}
+
+void KDUpdater::HttpDownloader::httpError(QNetworkReply::NetworkError)
+{
+ if (!d->aborted)
+ httpDone(true);
+}
+
+void KDUpdater::HttpDownloader::cancelDownload()
+{
+ d->aborted = true;
+ if (d->http) {
+ d->http->abort();
+ httpDone(true);
+ }
+}
+
+void KDUpdater::HttpDownloader::httpDone(bool error)
+{
+ if (error) {
+ QString err;
+ if (d->http) {
+ err = d->http->errorString();
+ d->http->deleteLater();
+ d->http = 0;
+ onError();
+ }
+
+ if (d->aborted) {
+ d->aborted = false;
+ setDownloadCanceled();
+ } else {
+ setDownloadAborted(err);
+ }
+ }
+ //PENDING: what about the non-error case??
+}
+
+void KDUpdater::HttpDownloader::onError()
+{
+ d->downloaded = false;
+ d->destFileName.clear();
+ delete d->destination;
+ d->destination = 0;
+ stopDownloadSpeedTimer();
+}
+
+void KDUpdater::HttpDownloader::onSuccess()
+{
+ d->downloaded = true;
+ d->destFileName = d->destination->fileName();
+ if (QTemporaryFile *file = dynamic_cast<QTemporaryFile *>(d->destination))
+ file->setAutoRemove(false);
+ delete d->destination;
+ d->destination = 0;
+ stopDownloadSpeedTimer();
+}
+
+void KDUpdater::HttpDownloader::httpReqFinished()
+{
+ const QVariant redirect = d->http == 0 ? QVariant()
+ : d->http->attribute(QNetworkRequest::RedirectionTargetAttribute);
+
+ const QUrl redirectUrl = redirect.toUrl();
+ if (followRedirects() && redirectUrl.isValid()) {
+ d->shutDown(); // clean the previous download
+ startDownload(redirectUrl);
+ } else {
+ if (d->http == 0)
+ return;
+
+ httpReadyRead();
+ d->destination->flush();
+ setDownloadCompleted(d->destination->fileName());
+ d->http->deleteLater();
+ d->http = 0;
+ }
+}
+
+void KDUpdater::HttpDownloader::httpReadProgress(qint64 done, qint64 total)
+{
+ setProgress(done, total);
+ emit downloadProgress(calcProgress(done, total));
+}
+
+void KDUpdater::HttpDownloader::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == downloadSpeedTimerId()) {
+ emitDownloadSpeed();
+ emitDownloadStatus();
+ emitDownloadProgress();
+ emitEstimatedDownloadTime();
+ }
+}
+
+void KDUpdater::HttpDownloader::startDownload(const QUrl &url)
+{
+ d->m_authenticationDone = false;
+ d->manager.setProxyFactory(proxyFactory());
+ d->http = d->manager.get(QNetworkRequest(url));
+
+ connect(d->http, SIGNAL(readyRead()), this, SLOT(httpReadyRead()));
+ connect(d->http, SIGNAL(downloadProgress(qint64, qint64)), this,
+ SLOT(httpReadProgress(qint64, qint64)));
+ connect(d->http, SIGNAL(finished()), this, SLOT(httpReqFinished()));
+ connect(d->http, SIGNAL(error(QNetworkReply::NetworkError)), this,
+ SLOT(httpError(QNetworkReply::NetworkError)));
+
+ if (d->destFileName.isEmpty()) {
+ QTemporaryFile *file = new QTemporaryFile(this);
+ file->open();
+ d->destination = file;
+ } else {
+ d->destination = new QFile(d->destFileName, this);
+ d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate);
+ }
+
+ if (!d->destination->isOpen()) {
+ d->shutDown();
+ setDownloadAborted(tr("Cannot download %1: Could not create temporary file: %2").arg(url.toString(),
+ d->destination->errorString()));
+ }
+}
+
+void KDUpdater::HttpDownloader::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
+{
+ qDebug() << reply->readAll();
+ if (!d->m_authenticationDone) {
+ d->m_authenticationDone = true;
+ authenticator->setUser(this->authenticator().user());
+ authenticator->setPassword(this->authenticator().password());
+ }
+}