summaryrefslogtreecommitdiffstats
path: root/src/libs/installer/getrepositorymetainfojob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/installer/getrepositorymetainfojob.cpp')
-rw-r--r--src/libs/installer/getrepositorymetainfojob.cpp512
1 files changed, 512 insertions, 0 deletions
diff --git a/src/libs/installer/getrepositorymetainfojob.cpp b/src/libs/installer/getrepositorymetainfojob.cpp
new file mode 100644
index 000000000..a72a861a0
--- /dev/null
+++ b/src/libs/installer/getrepositorymetainfojob.cpp
@@ -0,0 +1,512 @@
+/**************************************************************************
+**
+** This file is part of Installer Framework
+**
+** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+#include "getrepositorymetainfojob.h"
+
+#include "constants.h"
+#include "errors.h"
+#include "lib7z_facade.h"
+#include "messageboxhandler.h"
+#include "packagemanagercore_p.h"
+#include "qinstallerglobal.h"
+
+#include "kdupdaterfiledownloader.h"
+#include "kdupdaterfiledownloaderfactory.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QTimer>
+#include <QtCore/QUrl>
+
+#include <QtGui/QMessageBox>
+
+#include <QtNetwork/QAuthenticator>
+
+#include <QtXml/QDomDocument>
+#include <QtXml/QDomElement>
+
+using namespace KDUpdater;
+using namespace QInstaller;
+
+
+// -- GetRepositoryMetaInfoJob::ZipRunnable
+
+class GetRepositoryMetaInfoJob::ZipRunnable : public QObject, public QRunnable
+{
+ Q_OBJECT
+
+public:
+ ZipRunnable(const QString &archive, const QString &targetDir, QPointer<FileDownloader> downloader)
+ : QObject()
+ , QRunnable()
+ , m_archive(archive)
+ , m_targetDir(targetDir)
+ , m_downloader(downloader)
+ {}
+
+ ~ZipRunnable()
+ {
+ if (m_downloader)
+ m_downloader->deleteLater();
+ }
+
+ void run()
+ {
+ QFile archive(m_archive);
+ if (archive.open(QIODevice::ReadOnly)) {
+ try {
+ Lib7z::extractArchive(&archive, m_targetDir);
+ if (!archive.remove()) {
+ qWarning("Could not delete file %s: %s", qPrintable(m_archive),
+ qPrintable(archive.errorString()));
+ }
+ emit finished(true, QString());
+ } catch (const Lib7z::SevenZipException& e) {
+ emit finished(false, tr("Error while extracting %1. Error: %2").arg(m_archive, e.message()));
+ } catch (...) {
+ emit finished(false, tr("Unknown exception caught while extracting %1.").arg(m_archive));
+ }
+ } else {
+ emit finished(false, tr("Could not open %1 for reading. Error: %2").arg(m_archive,
+ archive.errorString()));
+ }
+ }
+
+Q_SIGNALS:
+ void finished(bool success, const QString &errorString);
+
+private:
+ const QString m_archive;
+ const QString m_targetDir;
+ QPointer<FileDownloader> m_downloader;
+};
+
+
+// -- GetRepositoryMetaInfoJob
+
+GetRepositoryMetaInfoJob::GetRepositoryMetaInfoJob(PackageManagerCorePrivate *corePrivate, QObject *parent)
+ : KDJob(parent),
+ m_canceled(false),
+ m_silentRetries(3),
+ m_retriesLeft(m_silentRetries),
+ m_downloader(0),
+ m_waitForDone(false),
+ m_corePrivate(corePrivate)
+{
+ setCapabilities(Cancelable);
+}
+
+GetRepositoryMetaInfoJob::~GetRepositoryMetaInfoJob()
+{
+ if (m_downloader)
+ m_downloader->deleteLater();
+}
+
+Repository GetRepositoryMetaInfoJob::repository() const
+{
+ return m_repository;
+}
+
+void GetRepositoryMetaInfoJob::setRepository(const Repository &r)
+{
+ m_repository = r;
+ qDebug() << "Setting repository with URL:" << r.url().toString();
+}
+
+int GetRepositoryMetaInfoJob::silentRetries() const
+{
+ return m_silentRetries;
+}
+
+void GetRepositoryMetaInfoJob::setSilentRetries(int retries)
+{
+ m_silentRetries = retries;
+}
+
+void GetRepositoryMetaInfoJob::doStart()
+{
+ m_retriesLeft = m_silentRetries;
+ startUpdatesXmlDownload();
+}
+
+void GetRepositoryMetaInfoJob::doCancel()
+{
+ m_canceled = true;
+ if (m_downloader)
+ m_downloader->cancelDownload();
+}
+
+void GetRepositoryMetaInfoJob::finished(int error, const QString &errorString)
+{
+ m_waitForDone = true;
+ m_threadPool.waitForDone();
+ (error > KDJob::NoError) ? emitFinishedWithError(error, errorString) : emitFinished();
+}
+
+QString GetRepositoryMetaInfoJob::temporaryDirectory() const
+{
+ return m_temporaryDirectory;
+}
+
+QString GetRepositoryMetaInfoJob::releaseTemporaryDirectory() const
+{
+ m_tempDirDeleter.releaseAll();
+ return m_temporaryDirectory;
+}
+
+// Updates.xml download
+
+void GetRepositoryMetaInfoJob::startUpdatesXmlDownload()
+{
+ if (m_downloader) {
+ m_downloader->deleteLater();
+ m_downloader = 0;
+ }
+
+ const QUrl url = m_repository.url();
+ if (url.isEmpty()) {
+ finished(QInstaller::InvalidUrl, tr("Empty repository URL."));
+ return;
+ }
+
+ if (!url.isValid()) {
+ finished(QInstaller::InvalidUrl, tr("Invalid repository URL: %1").arg(url.toString()));
+ return;
+ }
+
+ m_downloader = FileDownloaderFactory::instance().create(url.scheme(), this);
+ if (!m_downloader) {
+ finished(QInstaller::InvalidUrl, tr("URL scheme not supported: %1 (%2)").arg(url.scheme(),
+ url.toString()));
+ return;
+ }
+
+ // append a random string to avoid proxy caches
+ m_downloader->setUrl(QUrl(url.toString() + QString::fromLatin1("/Updates.xml?")
+ .append(QString::number(qrand() * qrand()))));
+
+ QAuthenticator auth;
+ auth.setUser(m_repository.username());
+ auth.setPassword(m_repository.password());
+ m_downloader->setAuthenticator(auth);
+
+ m_downloader->setAutoRemoveDownloadedFile(false);
+ connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(updatesXmlDownloadFinished()));
+ connect(m_downloader, SIGNAL(downloadCanceled()), this, SLOT(updatesXmlDownloadCanceled()));
+ connect(m_downloader, SIGNAL(downloadAborted(QString)), this,
+ SLOT(updatesXmlDownloadError(QString)), Qt::QueuedConnection);
+ m_downloader->download();
+}
+
+void GetRepositoryMetaInfoJob::updatesXmlDownloadCanceled()
+{
+ finished(KDJob::Canceled, m_downloader->errorString());
+}
+
+void GetRepositoryMetaInfoJob::updatesXmlDownloadFinished()
+{
+ emit infoMessage(this, tr("Retrieving component meta information..."));
+
+ const QString fn = m_downloader->downloadedFileName();
+ Q_ASSERT(!fn.isEmpty());
+ Q_ASSERT(QFile::exists(fn));
+
+ try {
+ m_temporaryDirectory = createTemporaryDirectory(QLatin1String("remoterepo"));
+ m_tempDirDeleter.add(m_temporaryDirectory);
+ } catch (const QInstaller::Error& e) {
+ finished(QInstaller::ExtractionError, e.message());
+ return;
+ }
+
+ QFile updatesFile(fn);
+ if (!updatesFile.rename(m_temporaryDirectory + QLatin1String("/Updates.xml"))) {
+ finished(QInstaller::DownloadError, tr("Could not move Updates.xml to target location. Error: %1")
+ .arg(updatesFile.errorString()));
+ return;
+ }
+
+ if (!updatesFile.open(QIODevice::ReadOnly)) {
+ finished(QInstaller::DownloadError, tr("Could not open Updates.xml for reading. Error: %1")
+ .arg(updatesFile.errorString()));
+ return;
+ }
+
+ QString err;
+ QDomDocument doc;
+ if (!doc.setContent(&updatesFile, &err)) {
+ const QString msg = tr("Could not fetch a valid version of Updates.xml from repository: %1. "
+ "Error: %2").arg(m_repository.url().toString(), err);
+
+ const QMessageBox::StandardButton b =
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("updatesXmlDownloadError"), tr("Download Error"), msg, QMessageBox::Cancel);
+
+ if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) {
+ finished(KDJob::Canceled, msg);
+ return;
+ }
+ }
+
+ emit infoMessage(this, tr("Parsing component meta information..."));
+
+ const QDomElement root = doc.documentElement();
+ // search for additional repositories that we might need to check
+ const QDomNode repositoryUpdate = root.firstChildElement(QLatin1String("RepositoryUpdate"));
+ if (!repositoryUpdate.isNull()) {
+ QHash<QString, QPair<Repository, Repository> > repositoryUpdates;
+ const QDomNodeList children = repositoryUpdate.toElement().childNodes();
+ for (int i = 0; i < children.count(); ++i) {
+ const QDomElement el = children.at(i).toElement();
+ if (!el.isNull() && el.tagName() == QLatin1String("Repository")) {
+ const QString action = el.attribute(QLatin1String("action"));
+ if (action == QLatin1String("add")) {
+ // add a new repository to the defaults list
+ Repository repository(el.attribute(QLatin1String("url")), true);
+ repository.setUsername(el.attribute(QLatin1String("username")));
+ repository.setPassword(el.attribute(QLatin1String("password")));
+ repositoryUpdates.insertMulti(action, qMakePair(repository, Repository()));
+
+ qDebug() << "Repository to add:" << repository.url().toString();
+ } else if (action == QLatin1String("remove")) {
+ // remove possible default repositories using the given server url
+ Repository repository(el.attribute(QLatin1String("url")), true);
+ repositoryUpdates.insertMulti(action, qMakePair(repository, Repository()));
+
+ qDebug() << "Repository to remove:" << repository.url().toString();
+ } else if (action == QLatin1String("replace")) {
+ // replace possible default repositories using the given server url
+ Repository oldRepository(el.attribute(QLatin1String("oldUrl")), true);
+ Repository newRepository(el.attribute(QLatin1String("newUrl")), true);
+ newRepository.setUsername(el.attribute(QLatin1String("username")));
+ newRepository.setPassword(el.attribute(QLatin1String("password")));
+
+ // store the new repository and the one old it replaces
+ repositoryUpdates.insertMulti(action, qMakePair(newRepository, oldRepository));
+ qDebug() << "Replace repository:" << oldRepository.url().toString() << "with:"
+ << newRepository.url().toString();
+ } else {
+ qDebug() << "Invalid additional repositories action set in Updates.xml fetched from:"
+ << m_repository.url().toString() << "Line:" << el.lineNumber();
+ }
+ }
+ }
+
+ if (!repositoryUpdates.isEmpty()) {
+ if (m_corePrivate->m_settings.updateDefaultRepositories(repositoryUpdates)
+ == Settings::UpdatesApplied) {
+ if (m_corePrivate->isUpdater() || m_corePrivate->isPackageManager())
+ m_corePrivate->writeMaintenanceConfigFiles();
+ finished(QInstaller::RepositoryUpdatesReceived, tr("Repository updates received."));
+ return;
+ }
+ }
+ }
+
+ const QDomNodeList children = root.childNodes();
+ for (int i = 0; i < children.count(); ++i) {
+ const QDomElement el = children.at(i).toElement();
+ if (el.isNull())
+ continue;
+ if (el.tagName() == QLatin1String("PackageUpdate")) {
+ const QDomNodeList c2 = el.childNodes();
+ for (int j = 0; j < c2.count(); ++j) {
+ if (c2.at(j).toElement().tagName() == scName)
+ m_packageNames << c2.at(j).toElement().text();
+ else if (c2.at(j).toElement().tagName() == scRemoteVersion)
+ m_packageVersions << c2.at(j).toElement().text();
+ else if (c2.at(j).toElement().tagName() == QLatin1String("SHA1"))
+ m_packageHash << c2.at(j).toElement().text();
+ }
+ }
+ }
+
+ setTotalAmount(m_packageNames.count() + 1);
+ setProcessedAmount(1);
+ emit infoMessage(this, tr("Finished updating component meta information..."));
+
+ if (m_packageNames.isEmpty())
+ finished(KDJob::NoError);
+ else
+ fetchNextMetaInfo();
+}
+
+void GetRepositoryMetaInfoJob::updatesXmlDownloadError(const QString &err)
+{
+ if (m_retriesLeft <= 0) {
+ const QString msg = tr("Could not fetch Updates.xml from repository: %1. Error: %2")
+ .arg(m_repository.url().toString(), err);
+
+ QMessageBox::StandardButtons buttons = QMessageBox::Retry | QMessageBox::Cancel;
+ const QMessageBox::StandardButton b =
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("updatesXmlDownloadError"), tr("Download Error"), msg, buttons);
+
+ if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) {
+ finished(KDJob::Canceled, msg);
+ return;
+ }
+ }
+
+ m_retriesLeft--;
+ QTimer::singleShot(1500, this, SLOT(startUpdatesXmlDownload()));
+}
+
+// meta data download
+
+void GetRepositoryMetaInfoJob::fetchNextMetaInfo()
+{
+ emit infoMessage(this, tr("Retrieving component information from remote repository..."));
+
+ if (m_canceled) {
+ finished(KDJob::Canceled, m_downloader->errorString());
+ return;
+ }
+
+ if (m_packageNames.isEmpty() && m_currentPackageName.isEmpty()) {
+ finished(KDJob::NoError);
+ return;
+ }
+
+ QString next = m_currentPackageName;
+ QString nextVersion = m_currentPackageVersion;
+ if (next.isEmpty()) {
+ m_retriesLeft = m_silentRetries;
+ next = m_packageNames.takeLast();
+ nextVersion = m_packageVersions.takeLast();
+ }
+
+ qDebug() << "fetching metadata of" << next << "in version" << nextVersion;
+
+ bool online = true;
+ if (m_repository.url().scheme().isEmpty())
+ online = false;
+
+ const QString repoUrl = m_repository.url().toString();
+ const QUrl url = QString::fromLatin1("%1/%2/%3meta.7z").arg(repoUrl, next,
+ online ? nextVersion : QString());
+ m_downloader = FileDownloaderFactory::instance().create(url.scheme(), this);
+
+ if (!m_downloader) {
+ m_currentPackageName.clear();
+ m_currentPackageVersion.clear();
+ qWarning() << "Scheme not supported: " << url.toString();
+ QMetaObject::invokeMethod(this, "fetchNextMetaInfo", Qt::QueuedConnection);
+ return;
+ }
+
+ m_currentPackageName = next;
+ m_currentPackageVersion = nextVersion;
+ m_downloader->setUrl(url);
+ m_downloader->setAutoRemoveDownloadedFile(true);
+
+ QAuthenticator auth;
+ auth.setUser(m_repository.username());
+ auth.setPassword(m_repository.password());
+ m_downloader->setAuthenticator(auth);
+
+ connect(m_downloader, SIGNAL(downloadCanceled()), this, SLOT(metaDownloadCanceled()));
+ connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(metaDownloadFinished()));
+ connect(m_downloader, SIGNAL(downloadAborted(QString)), this, SLOT(metaDownloadError(QString)),
+ Qt::QueuedConnection);
+
+ m_downloader->download();
+}
+
+void GetRepositoryMetaInfoJob::metaDownloadCanceled()
+{
+ finished(KDJob::Canceled, m_downloader->errorString());
+}
+
+void GetRepositoryMetaInfoJob::metaDownloadFinished()
+{
+ const QString fn = m_downloader->downloadedFileName();
+ Q_ASSERT(!fn.isEmpty());
+
+ QFile arch(fn);
+ if (!arch.open(QIODevice::ReadOnly)) {
+ finished(QInstaller::ExtractionError, tr("Could not open meta info archive: %1. Error: %2").arg(fn,
+ arch.errorString()));
+ return;
+ }
+
+ if (!m_packageHash.isEmpty()) {
+ // verify file hash
+ QByteArray expectedFileHash = m_packageHash.back().toLatin1();
+ QByteArray archContent = arch.readAll();
+ QByteArray realFileHash = QString::fromLatin1(QCryptographicHash::hash(archContent,
+ QCryptographicHash::Sha1).toHex()).toLatin1();
+ if (expectedFileHash != realFileHash) {
+ emit infoMessage(this, tr("The hash of one component does not match the expected one."));
+ metaDownloadError(tr("Bad hash."));
+ return;
+ }
+ m_packageHash.removeLast();
+ }
+ arch.close();
+ m_currentPackageName.clear();
+
+ ZipRunnable *runnable = new ZipRunnable(fn, m_temporaryDirectory, m_downloader);
+ connect(runnable, SIGNAL(finished(bool,QString)), this, SLOT(unzipFinished(bool,QString)));
+ m_threadPool.start(runnable);
+
+ if (!m_waitForDone)
+ fetchNextMetaInfo();
+}
+
+void GetRepositoryMetaInfoJob::metaDownloadError(const QString &err)
+{
+ if (m_retriesLeft <= 0) {
+ const QString msg = tr("Could not download meta information for component: %1. Error: %2")
+ .arg(m_currentPackageName, err);
+
+ QMessageBox::StandardButtons buttons = QMessageBox::Retry | QMessageBox::Cancel;
+ const QMessageBox::StandardButton b =
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("updatesXmlDownloadError"), tr("Download Error"), msg, buttons);
+
+ if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) {
+ finished(KDJob::Canceled, msg);
+ return;
+ }
+ }
+
+ m_retriesLeft--;
+ QTimer::singleShot(1500, this, SLOT(fetchNextMetaInfo()));
+}
+
+void GetRepositoryMetaInfoJob::unzipFinished(bool ok, const QString &error)
+{
+ if (!ok)
+ finished(QInstaller::ExtractionError, error);
+}
+
+#include "getrepositorymetainfojob.moc"
+#include "moc_getrepositorymetainfojob.cpp"