mirror of
https://github.com/QuasarApp/installer-framework.git
synced 2025-04-29 15:04:32 +00:00
803 lines
34 KiB
C++
803 lines
34 KiB
C++
|
/**************************************************************************
|
||
|
**
|
||
|
** This file is part of Qt SDK**
|
||
|
**
|
||
|
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).*
|
||
|
**
|
||
|
** Contact: Nokia Corporation qt-info@nokia.com**
|
||
|
**
|
||
|
** No Commercial Usage
|
||
|
**
|
||
|
** This file contains pre-release code and may not be distributed.
|
||
|
** You may use this file in accordance with the terms and conditions
|
||
|
** contained in the Technology Preview License Agreement accompanying
|
||
|
** this package.
|
||
|
**
|
||
|
** 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.
|
||
|
**
|
||
|
** If you are unsure which license is appropriate for your use, please contact
|
||
|
** (qt-info@nokia.com).
|
||
|
**
|
||
|
**************************************************************************/
|
||
|
#include "repositorygen.h"
|
||
|
|
||
|
#include <common/fileutils.h>
|
||
|
#include <common/errors.h>
|
||
|
#include <common/utils.h>
|
||
|
#include <common/consolepasswordprovider.h>
|
||
|
#include <common/installersettings.h>
|
||
|
|
||
|
#include <KDUpdater/KDUpdater>
|
||
|
|
||
|
#include <QCryptographicHash>
|
||
|
#include <QDir>
|
||
|
#include <QDirIterator>
|
||
|
#include <QDomAttr>
|
||
|
#include <QDomDocument>
|
||
|
#include <QTemporaryFile>
|
||
|
|
||
|
#include "lib7z_facade.h"
|
||
|
|
||
|
#include <cassert>
|
||
|
|
||
|
using namespace QInstaller;
|
||
|
|
||
|
static bool operator==(const PackageInfo& lhs, const PackageInfo& rhs)
|
||
|
{
|
||
|
return lhs.name == rhs.name && lhs.version == rhs.version;
|
||
|
}
|
||
|
|
||
|
static QVector<PackageInfo> collectAvailablePackages(const QString& packagesDirectory)
|
||
|
{
|
||
|
verbose() << "Collecting information about available packages..." << std::endl;
|
||
|
|
||
|
QVector< PackageInfo > dict;
|
||
|
const QFileInfoList entries = QDir(packagesDirectory)
|
||
|
.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||
|
for (QFileInfoList::const_iterator it = entries.begin(); it != entries.end(); ++it) {
|
||
|
verbose() << " found subdirectory \"" << it->fileName() << "\"";
|
||
|
QFile file(QString::fromLatin1("%1/meta/package.xml").arg(it->filePath()));
|
||
|
if (!file.exists()) {
|
||
|
verbose() << ", but it contains no package information (meta/package.xml missing)"
|
||
|
<< std::endl;
|
||
|
throw QInstaller::Error(QObject::tr("Component %1 does not contain a package "
|
||
|
"description.").arg(it->fileName()));
|
||
|
}
|
||
|
|
||
|
file.open(QIODevice::ReadOnly);
|
||
|
|
||
|
QDomDocument doc;
|
||
|
QString errorMessage;
|
||
|
int errorLine = 0;
|
||
|
int errorColumn = 0;
|
||
|
if (!doc.setContent(&file, &errorMessage, &errorLine, &errorColumn)) {
|
||
|
verbose() << ", but it's package description is invalid. Error at " << errorLine
|
||
|
<< ", " << errorColumn << ": " << errorMessage << std::endl;
|
||
|
throw QInstaller::Error(QObject::tr("Component package description for %1 is invalid. "
|
||
|
"Error at %2, %3 : %4").arg(it->fileName(), QString::number(errorLine),
|
||
|
QString::number(errorColumn), errorMessage));
|
||
|
}
|
||
|
|
||
|
const QString name = doc.firstChildElement(QString::fromLatin1("Package"))
|
||
|
.firstChildElement(QLatin1String("Name")).text();
|
||
|
if (name != it->fileName()) {
|
||
|
throw QInstaller::Error(QObject::tr("Component folder name must match component name: "
|
||
|
"\"%1\" in %2/").arg(name, it->fileName()));
|
||
|
}
|
||
|
|
||
|
PackageInfo info;
|
||
|
info.name = name;
|
||
|
info.version = doc.firstChildElement(QString::fromLatin1("Package")).
|
||
|
firstChildElement(QString::fromLatin1("Version")).text();
|
||
|
info.dependencies = doc.firstChildElement(QString::fromLatin1("Package")).
|
||
|
firstChildElement(QString::fromLatin1("Dependencies")).text().split(QLatin1String(","),
|
||
|
QString::SkipEmptyParts);
|
||
|
info.directory = it->filePath();
|
||
|
dict.push_back(info);
|
||
|
|
||
|
verbose() << ", it provides the package " <<name;
|
||
|
if (!info.version.isEmpty())
|
||
|
verbose() << "-" << info.version;
|
||
|
verbose() << std::endl;
|
||
|
}
|
||
|
|
||
|
if (dict.isEmpty())
|
||
|
verbose() << "No available packages found at the specified location." << std::endl;
|
||
|
|
||
|
return dict;
|
||
|
}
|
||
|
|
||
|
static PackageInfo findMatchingPackage(const QString& name, const QVector< PackageInfo >& available)
|
||
|
{
|
||
|
const QString id = name.contains(QChar::fromLatin1('-'))
|
||
|
? name.section(QChar::fromLatin1('-'), 0, 0) : name;
|
||
|
QString version = name.contains(QChar::fromLatin1('-'))
|
||
|
? name.section(QChar::fromLatin1('-'), 1, -1) : QString();
|
||
|
|
||
|
QRegExp compEx(QLatin1String("([<=>]+)(.*)"));
|
||
|
const QString comparator = compEx.exactMatch(version)
|
||
|
? compEx.cap(1) : QString::fromLatin1("=");
|
||
|
version = compEx.exactMatch(version) ? compEx.cap(2) : version;
|
||
|
|
||
|
const bool allowEqual = comparator.contains(QLatin1Char('='));
|
||
|
const bool allowLess = comparator.contains(QLatin1Char('<'));
|
||
|
const bool allowMore = comparator.contains(QLatin1Char('>'));
|
||
|
|
||
|
for (QVector< PackageInfo >::const_iterator it = available.begin(); it != available.end(); ++it) {
|
||
|
if (it->name != id)
|
||
|
continue;
|
||
|
|
||
|
if (allowEqual && (version.isEmpty() || it->version == version))
|
||
|
return *it;
|
||
|
else if (allowLess && KDUpdater::compareVersion(version, it->version) > 0)
|
||
|
return *it;
|
||
|
else if (allowMore && KDUpdater::compareVersion(version, it->version) < 0)
|
||
|
return *it;
|
||
|
}
|
||
|
|
||
|
return PackageInfo();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true, when the \a package's identifier starts with, but not equals \a prefix.
|
||
|
*/
|
||
|
static bool packageHasPrefix(const PackageInfo& package, const QString& prefix)
|
||
|
{
|
||
|
return package.name.startsWith(prefix) && package.name.mid(prefix.length(), 1)
|
||
|
== QString::fromLatin1(".");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true, whel all \a packages start with \a prefix
|
||
|
*/
|
||
|
static bool allPackagesHavePrefix(const QVector< PackageInfo >& packages, const QString& prefix)
|
||
|
{
|
||
|
for (QVector< PackageInfo >::const_iterator it = packages.begin(); it != packages.end(); ++it) {
|
||
|
if (!packageHasPrefix(*it, prefix))
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns all packages out of \a all starting with \a prefix.
|
||
|
*/
|
||
|
static QVector< PackageInfo > packagesWithPrefix(const QVector< PackageInfo >& all,
|
||
|
const QString& prefix)
|
||
|
{
|
||
|
QVector< PackageInfo > result;
|
||
|
for (QVector< PackageInfo >::const_iterator it = all.begin(); it != all.end(); ++it) {
|
||
|
if (packageHasPrefix(*it, prefix))
|
||
|
result.push_back(*it);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static QVector< PackageInfo > calculateNeededPackages(const QStringList& components,
|
||
|
const QVector< PackageInfo >& available, bool addDependencies = true)
|
||
|
{
|
||
|
QVector< PackageInfo > result;
|
||
|
|
||
|
for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) {
|
||
|
static bool recursion = false;
|
||
|
static QStringList hitComponents;
|
||
|
|
||
|
if (!recursion)
|
||
|
hitComponents.clear();
|
||
|
|
||
|
if (hitComponents.contains(*it))
|
||
|
throw Error(QObject::tr("Circular dependencies detected").arg(*it));
|
||
|
hitComponents.push_back(*it);
|
||
|
|
||
|
recursion = true;
|
||
|
|
||
|
verbose() << "Trying to find a package for name " << *it << "... ";
|
||
|
const PackageInfo info = findMatchingPackage(*it, available);
|
||
|
if (info.name.isEmpty()) {
|
||
|
verbose() << "Not found :-o" << std::endl;
|
||
|
verbose() << " Couldn't find package for component " << *it << " bailing out..."
|
||
|
<< std::endl;
|
||
|
throw Error(QObject::tr("Couldn't find package for component %1").arg(*it));
|
||
|
}
|
||
|
verbose() << "Found." << std::endl;
|
||
|
if (!result.contains(info)) {
|
||
|
result.push_back(info);
|
||
|
|
||
|
if (addDependencies) {
|
||
|
QVector< PackageInfo > dependencies;
|
||
|
|
||
|
if (!info.dependencies.isEmpty()) {
|
||
|
verbose() << " It depends on:" << std::endl;
|
||
|
for (QStringList::const_iterator dep = info.dependencies.begin();
|
||
|
dep != info.dependencies.end(); ++dep)
|
||
|
verbose() << " " << *dep << std::endl;
|
||
|
dependencies += calculateNeededPackages(info.dependencies, available);
|
||
|
}
|
||
|
// append all child items, as this package was requested explicitely
|
||
|
dependencies += packagesWithPrefix(available, info.name);
|
||
|
|
||
|
for (QVector< PackageInfo >::const_iterator dep = dependencies.begin();
|
||
|
dep != dependencies.end(); ++dep) {
|
||
|
if (result.contains(*dep))
|
||
|
continue;
|
||
|
|
||
|
result += *dep;
|
||
|
const QVector< PackageInfo > depdeps = calculateNeededPackages(QStringList()
|
||
|
<< dep->name, available);
|
||
|
for (QVector< PackageInfo >::const_iterator dep2 = depdeps.begin();
|
||
|
dep2 != depdeps.end(); ++dep2)
|
||
|
if (!result.contains(*dep2))
|
||
|
result += *dep2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
recursion = false;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
struct ArchiveFile {
|
||
|
ArchiveFile() : uncompressedSize(0) {}
|
||
|
quint64 uncompressedSize;
|
||
|
QByteArray sha1sum;
|
||
|
QString fileName;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
void QInstaller::compressDirectory(const QString& path, const QString& archivePath)
|
||
|
{
|
||
|
if (!QFileInfo(path).exists())
|
||
|
throw QInstaller::Error(QObject::tr("Folder %1 does not exist").arg(path));
|
||
|
|
||
|
if (!QFileInfo(path).isDir())
|
||
|
throw QInstaller::Error(QObject::tr("%1 is not a folder").arg(path));
|
||
|
|
||
|
QFile archive(archivePath);
|
||
|
openForWrite(&archive, archivePath);
|
||
|
Lib7z::createArchive(&archive, path);
|
||
|
}
|
||
|
|
||
|
void QInstaller::compressMetaDirectories(const QString& configDir, const QString& repoDir)
|
||
|
{
|
||
|
const QString configfile = QFileInfo(configDir, QLatin1String("config.xml")).absoluteFilePath();
|
||
|
const QInstaller::InstallerSettings settings =
|
||
|
QInstaller::InstallerSettings::fromFileAndPrefix(configfile, configDir);
|
||
|
|
||
|
KDUpdaterCrypto crypto;
|
||
|
crypto.setPrivateKey(settings.privateKey());
|
||
|
ConsolePasswordProvider passwordProvider;
|
||
|
crypto.setPrivatePasswordProvider(&passwordProvider);
|
||
|
|
||
|
QDir dir(repoDir);
|
||
|
const QStringList sub = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||
|
Q_FOREACH (const QString& i, sub) {
|
||
|
QDir sd(dir);
|
||
|
sd.cd(i);
|
||
|
const QString absPath = sd.absolutePath();
|
||
|
const QString fn = QLatin1String("meta.7z");
|
||
|
const QString tmpTarget = repoDir + QLatin1String("/") +fn;
|
||
|
compressDirectory(absPath, tmpTarget);
|
||
|
QFile tmp(tmpTarget);
|
||
|
const QString finalTarget = absPath + QLatin1String("/") + fn;
|
||
|
if (!tmp.rename(finalTarget)) {
|
||
|
throw QInstaller::Error(QObject::tr("Could not move %1 to %2").arg(tmpTarget,
|
||
|
finalTarget));
|
||
|
}
|
||
|
|
||
|
// if we have a private key, sign the meta.7z file
|
||
|
if (!settings.privateKey().isEmpty()) {
|
||
|
verbose() << "Adding a RSA signature to " << finalTarget << std::endl;
|
||
|
const QByteArray signature = crypto.sign(finalTarget);
|
||
|
QFile sigFile(finalTarget + QLatin1String(".sig"));
|
||
|
if (!sigFile.open(QIODevice::WriteOnly)) {
|
||
|
throw QInstaller::Error(QObject::tr("Could not open %1 for writing")
|
||
|
.arg(finalTarget));
|
||
|
}
|
||
|
sigFile.write(signature);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void QInstaller::generateMetaDataDirectory(const QString& metapath_, const QString& dataDir,
|
||
|
const QVector< PackageInfo >& packages, const QString& appName, const QString& appVersion)
|
||
|
{
|
||
|
QString metapath = metapath_;
|
||
|
if (QFileInfo(metapath).isRelative())
|
||
|
metapath = QDir::cleanPath(QDir::current().absoluteFilePath(metapath));
|
||
|
verbose() << "Generating meta data..." << std::endl;
|
||
|
|
||
|
if (!QFile::exists(metapath))
|
||
|
QInstaller::mkpath(metapath);
|
||
|
|
||
|
QDomDocument doc;
|
||
|
QDomElement root;
|
||
|
// use existing Updates.xml, if any
|
||
|
QFile existingUpdatesXml(QFileInfo(dataDir, QLatin1String("Updates.xml")).absoluteFilePath());
|
||
|
if (!existingUpdatesXml.open(QIODevice::ReadOnly) || !doc.setContent(&existingUpdatesXml)) {
|
||
|
root = doc.createElement("Updates");
|
||
|
root.appendChild(doc.createElement("ApplicationName")).appendChild(
|
||
|
doc.createTextNode(appName));
|
||
|
root.appendChild(doc.createElement("ApplicationVersion")).appendChild(
|
||
|
doc.createTextNode(appVersion));
|
||
|
root.appendChild(doc.createElement("Checksum")).appendChild(
|
||
|
doc.createTextNode(QLatin1String("true")));
|
||
|
} else {
|
||
|
root = doc.documentElement();
|
||
|
}
|
||
|
|
||
|
for (QVector< PackageInfo >::const_iterator it = packages.begin(); it != packages.end(); ++it) {
|
||
|
const QString packageXmlPath = QString::fromLatin1("%1/meta/package.xml").arg(it->directory);
|
||
|
verbose() << " Generating meta data for package " << it->name << " using "
|
||
|
<< packageXmlPath << std::endl;;
|
||
|
|
||
|
// remove existing entry for thes component from existing Updates.xml
|
||
|
const QDomNodeList packageNodes = root.childNodes();
|
||
|
for (int i = 0; i < packageNodes.count(); ++i) {
|
||
|
const QDomNode node = packageNodes.at(i);
|
||
|
if (node.nodeName() != QLatin1String("PackageUpdate"))
|
||
|
continue;
|
||
|
if (node.firstChildElement(QLatin1String("Name")).text() != it->name)
|
||
|
continue;
|
||
|
root.removeChild(node);
|
||
|
--i;
|
||
|
}
|
||
|
|
||
|
QDomDocument packageXml;
|
||
|
QFile file(packageXmlPath);
|
||
|
openForRead(&file, packageXmlPath);
|
||
|
QString errMsg;
|
||
|
int col = 0;
|
||
|
int line = 0;
|
||
|
if (!packageXml.setContent(&file, &errMsg, &line, &col)) {
|
||
|
throw Error(QObject::tr("Could not parse %1: %2:%3: %4 (%5)").arg(packageXmlPath,
|
||
|
QString::number(line), QString::number(col), errMsg, it->name));
|
||
|
}
|
||
|
const QDomNode package = packageXml.firstChildElement("Package");
|
||
|
|
||
|
QDomElement update = doc.createElement("PackageUpdate");
|
||
|
|
||
|
const QDomNodeList childNodes = package.childNodes();
|
||
|
for (int i = 0; i < childNodes.count(); ++i) {
|
||
|
const QDomNode node = childNodes.at(i);
|
||
|
// just skip the comments...
|
||
|
if (node.isComment())
|
||
|
continue;
|
||
|
const QString key = node.nodeName();
|
||
|
if (key == QString::fromLatin1("UserInterfaces"))
|
||
|
continue;
|
||
|
if (key == QString::fromLatin1("Translations"))
|
||
|
continue;
|
||
|
if (key == QString::fromLatin1("Licenses"))
|
||
|
continue;
|
||
|
const QString value = node.toElement().text();
|
||
|
update.appendChild(doc.createElement(key)).appendChild(doc.createTextNode(value));
|
||
|
}
|
||
|
|
||
|
// get the size of the data
|
||
|
quint64 componentSize = 0;
|
||
|
quint64 compressedComponentSize = 0;
|
||
|
|
||
|
const QString cmpDataDir = QString::fromLatin1("%1/%2").arg(dataDir, it->name);
|
||
|
const QFileInfoList entries = !QDir(cmpDataDir + QLatin1String("/data")).exists()
|
||
|
? QDir(cmpDataDir).entryInfoList(QDir::Files | QDir::NoDotAndDotDot)
|
||
|
: QDir(cmpDataDir + QLatin1String("/data")).entryInfoList(QDir::Files
|
||
|
| QDir::Dirs | QDir::NoDotAndDotDot);
|
||
|
QVector<ArchiveFile> archiveFiles;
|
||
|
|
||
|
Q_FOREACH (const QFileInfo& fi, entries) {
|
||
|
if (fi.isHidden())
|
||
|
continue;
|
||
|
|
||
|
try {
|
||
|
if (fi.isDir()) {
|
||
|
QDirIterator recursDirIt(fi.filePath(), QDirIterator::Subdirectories);
|
||
|
while (recursDirIt.hasNext()) {
|
||
|
componentSize += QFile(recursDirIt.next()).size();
|
||
|
compressedComponentSize += QFile(recursDirIt.next()).size();
|
||
|
}
|
||
|
} else if (Lib7z::isSupportedArchive(fi.filePath())) {
|
||
|
// if it's an archive already, list it's files and sum the uncompressed sizes
|
||
|
QFile archive(fi.filePath());
|
||
|
compressedComponentSize += archive.size();
|
||
|
archive.open(QIODevice::ReadOnly);
|
||
|
const QVector< Lib7z::File > files = Lib7z::listArchive(&archive);
|
||
|
for (QVector< Lib7z::File >::const_iterator fileIt = files.begin();
|
||
|
fileIt != files.end(); ++fileIt) {
|
||
|
componentSize += fileIt->uncompressedSize;
|
||
|
}
|
||
|
} else {
|
||
|
// otherwise just add it's size
|
||
|
componentSize += fi.size();
|
||
|
compressedComponentSize += fi.size();
|
||
|
}
|
||
|
} catch(...) {
|
||
|
// ignore, that's just about the sizes - and size doesn't matter, you know?
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add fake update files
|
||
|
const QStringList platforms = QStringList() << "Windows" << "MacOSX" << "Linux";
|
||
|
Q_FOREACH (const QString& platform, platforms) {
|
||
|
QDomElement file = doc.createElement("UpdateFile");
|
||
|
file.setAttribute("OS", platform);
|
||
|
file.setAttribute("UncompressedSize", componentSize);
|
||
|
file.setAttribute("CompressedSize", compressedComponentSize);
|
||
|
file.appendChild(doc.createTextNode(QLatin1String("(null)")));
|
||
|
update.appendChild(file);
|
||
|
}
|
||
|
|
||
|
root.appendChild(update);
|
||
|
|
||
|
if (!QDir(metapath).mkpath(it->name))
|
||
|
throw Error(QObject::tr("Could not create directory %1").arg(it->name));
|
||
|
|
||
|
// copy scripts
|
||
|
const QString script = package.firstChildElement("Script").text();
|
||
|
if (!script.isEmpty()) {
|
||
|
verbose() << " Copying associated script " << script << " into the meta package...";
|
||
|
if (!QFile::copy(QString::fromLatin1("%1/meta/%2").arg(it->directory, script),
|
||
|
QString::fromLatin1("%1/%2/%3").arg(metapath, it->name, script))) {
|
||
|
verbose() << "failed!" << std::endl;
|
||
|
throw Error(QObject::tr("Could not copy the scriot %1 to its target location (%2)")
|
||
|
.arg(script, it->name));
|
||
|
} else {
|
||
|
verbose() << std::endl;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// copy user interfaces
|
||
|
const QDomNodeList uiNodes = package.firstChildElement("UserInterfaces").childNodes();
|
||
|
QStringList userinterfaces;
|
||
|
for (int i = 0; i < uiNodes.count(); ++i) {
|
||
|
const QDomNode node = uiNodes.at(i);
|
||
|
if (node.nodeName() != QString::fromLatin1("UserInterface"))
|
||
|
continue;
|
||
|
|
||
|
const QDir dir(QString::fromLatin1("%1/meta").arg(it->directory));
|
||
|
const QStringList uis = dir.entryList(QStringList(node.toElement().text()), QDir::Files);
|
||
|
if (uis.isEmpty()) {
|
||
|
throw Error(QObject::tr("Couldn't find any user interface matching %1 while copying "
|
||
|
"user interfaces of %2").arg(node.toElement().text(), it->name));
|
||
|
}
|
||
|
|
||
|
for (QStringList::const_iterator ui = uis.begin(); ui != uis.end(); ++ui) {
|
||
|
verbose() << " Copying associated user interface " << *ui << " into the meta "
|
||
|
"package...";
|
||
|
userinterfaces.push_back(*ui);
|
||
|
if (!QFile::copy(QString::fromLatin1("%1/meta/%2").arg(it->directory, *ui),
|
||
|
QString::fromLatin1("%1/%2/%3").arg(metapath, it->name, *ui))) {
|
||
|
verbose() << "failed!" << std::endl;
|
||
|
throw Error(QObject::tr("Could not copy the UI file %1 to its target location "
|
||
|
"(%2)").arg(*ui, it->name));
|
||
|
} else {
|
||
|
verbose() << std::endl;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!userinterfaces.isEmpty()) {
|
||
|
update.appendChild(doc.createElement(QString::fromLatin1("UserInterfaces")))
|
||
|
.appendChild(doc.createTextNode(userinterfaces.join(QChar::fromLatin1(','))));
|
||
|
}
|
||
|
|
||
|
// copy translations
|
||
|
const QDomNodeList qmNodes = package.firstChildElement("Translations").childNodes();
|
||
|
QStringList translations;
|
||
|
for (int i = 0; i < qmNodes.count(); ++i) {
|
||
|
const QDomNode node = qmNodes.at(i);
|
||
|
if (node.nodeName() != QString::fromLatin1("Translation"))
|
||
|
continue;
|
||
|
|
||
|
const QDir dir(QString::fromLatin1("%1/meta").arg(it->directory));
|
||
|
const QStringList qms = dir.entryList(QStringList(node.toElement().text()), QDir::Files);
|
||
|
if (qms.isEmpty()) {
|
||
|
throw Error(QObject::tr("Could not find any user interface matching %1 while "
|
||
|
"copying user interfaces of %2").arg(node.toElement().text(), it->name));
|
||
|
}
|
||
|
|
||
|
for (QStringList::const_iterator qm = qms.begin(); qm != qms.end(); ++qm) {
|
||
|
verbose() << " Copying associated translation " << *qm << " into the meta "
|
||
|
"package...";
|
||
|
translations.push_back(*qm);
|
||
|
if (!QFile::copy(QString::fromLatin1("%1/meta/%2").arg(it->directory, *qm),
|
||
|
QString::fromLatin1("%1/%2/%3").arg(metapath, it->name, *qm))) {
|
||
|
verbose() << "failed!" << std::endl;
|
||
|
throw Error(QObject::tr("Could not copy the translation %1 to its target "
|
||
|
"location (%2)").arg(*qm, it->name));
|
||
|
} else {
|
||
|
verbose() << std::endl;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!translations.isEmpty()) {
|
||
|
update.appendChild(doc.createElement(QString::fromLatin1("Translations")))
|
||
|
.appendChild(doc.createTextNode(translations.join(QChar::fromLatin1(','))));
|
||
|
}
|
||
|
|
||
|
// copy license files
|
||
|
const QDomNodeList licenseNodes = package.firstChildElement("Licenses").childNodes();
|
||
|
for (int i = 0; i < licenseNodes.count(); ++i) {
|
||
|
const QDomNode licenseNode = licenseNodes.at(i);
|
||
|
if (licenseNode.nodeName() == QLatin1String("License")) {
|
||
|
const QString &licenseFile =
|
||
|
licenseNode.toElement().attributeNode(QLatin1String("file")).value();
|
||
|
const QString &sourceFile =
|
||
|
QString::fromLatin1("%1/meta/%2").arg(it->directory).arg(licenseFile);
|
||
|
if (!QFile::exists(sourceFile)) {
|
||
|
throw Error(QObject::tr("Could not find any license matching %1 while "
|
||
|
"copying license files of %2").arg(licenseFile, it->name));
|
||
|
}
|
||
|
|
||
|
verbose() << " Copying associated license file " << licenseFile << " into "
|
||
|
"the meta package...";
|
||
|
if (!QFile::copy(sourceFile, QString::fromLatin1("%1/%2/%3")
|
||
|
.arg(metapath, it->name, licenseFile))) {
|
||
|
verbose() << "failed!" << std::endl;
|
||
|
throw Error(QObject::tr("Could not copy the license file %1 to its "
|
||
|
"target location (%2)").arg(licenseFile, it->name));
|
||
|
} else {
|
||
|
verbose() << std::endl;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (licenseNodes.count() > 0)
|
||
|
update.appendChild(package.firstChildElement("Licenses").cloneNode());
|
||
|
}
|
||
|
|
||
|
doc.appendChild(root);
|
||
|
|
||
|
const QString updatesXmlFile = QFileInfo(metapath, "Updates.xml").absoluteFilePath();
|
||
|
QFile updatesXml(updatesXmlFile);
|
||
|
|
||
|
openForWrite(&updatesXml, updatesXmlFile);
|
||
|
blockingWrite(&updatesXml, doc.toByteArray());
|
||
|
}
|
||
|
|
||
|
QVector<PackageInfo> QInstaller::createListOfPackages(const QStringList& components,
|
||
|
const QString& packagesDirectory, bool addDependencies)
|
||
|
{
|
||
|
const QVector< PackageInfo > available = collectAvailablePackages(packagesDirectory);
|
||
|
verbose() << "Calculating dependencies for selected packages..." << std::endl;
|
||
|
QVector<PackageInfo> needed = calculateNeededPackages(components, available, addDependencies);
|
||
|
|
||
|
verbose() << "The following packages will be placed in the installer:" << std::endl;
|
||
|
Q_FOREACH (const PackageInfo& i, needed) {
|
||
|
verbose() << " " << i.name;
|
||
|
if (!i.version.isEmpty())
|
||
|
verbose() << "-" << i.version;
|
||
|
verbose() << std::endl;
|
||
|
}
|
||
|
|
||
|
// now just append the virtual parents (not including all their descendants!)
|
||
|
// like... if com.nokia.sdk.qt.qtcore was passed, even com.nokia.sdk.qt will show up in the tree
|
||
|
if (addDependencies) {
|
||
|
for (int i = 0; i < needed.count(); ++i) {
|
||
|
const PackageInfo& package = needed[ i ];
|
||
|
const QString name = package.name;
|
||
|
const QString version = package.version;
|
||
|
QString id = name.section(QChar::fromLatin1('.'), 0, -2);
|
||
|
while (!id.isEmpty()) {
|
||
|
PackageInfo info;
|
||
|
if (!version.isEmpty())
|
||
|
info = findMatchingPackage(QString::fromLatin1("%1-%2").arg(id, version), available);
|
||
|
if (info.name.isEmpty())
|
||
|
info = findMatchingPackage(id, available);
|
||
|
if (!info.name.isEmpty() && !allPackagesHavePrefix(needed, id) && !needed.contains(info)) {
|
||
|
verbose() << "Adding " << info.name << " as it is the virtual parent item of "
|
||
|
<< name << std::endl;
|
||
|
needed.push_back(info);
|
||
|
}
|
||
|
id = id.section(QChar::fromLatin1('.'), 0, -2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return needed;
|
||
|
}
|
||
|
|
||
|
QMap<QString, QString> QInstaller::buildPathToVersionMap(const QVector<PackageInfo>& info)
|
||
|
{
|
||
|
QMap<QString, QString> map;
|
||
|
Q_FOREACH (PackageInfo inf, info) {
|
||
|
map[inf.name] = inf.version;
|
||
|
}
|
||
|
return map;
|
||
|
}
|
||
|
|
||
|
static void writeSHA1ToNodeWithName(QDomDocument& doc, QDomNodeList& list, const QByteArray& sha1sum,
|
||
|
const QString& nodename)
|
||
|
{
|
||
|
verbose() << "searching sha1sum node for " << nodename << std::endl;
|
||
|
for (int i = 0; i < list.size(); ++i) {
|
||
|
QDomNode curNode = list.at(i);
|
||
|
QDomNode nameTag = curNode.firstChildElement(QLatin1String("Name"));
|
||
|
if (!nameTag.isNull() && nameTag.toElement().text() == nodename) {
|
||
|
QDomNode sha1Node = doc.createElement(QLatin1String("SHA1"));
|
||
|
sha1Node.appendChild(doc.createTextNode(QString::fromLatin1(sha1sum.toHex().constData())));
|
||
|
curNode.appendChild(sha1Node);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void QInstaller::compressMetaDirectories(const QString& configDir, const QString& repoDir,
|
||
|
const QString& baseDir, const QMap<QString, QString>& versionMapping)
|
||
|
{
|
||
|
const QString configfile = QFileInfo(configDir, QLatin1String("config.xml")).absoluteFilePath();
|
||
|
const QInstaller::InstallerSettings settings =
|
||
|
QInstaller::InstallerSettings::fromFileAndPrefix(configfile, configDir);
|
||
|
|
||
|
KDUpdaterCrypto crypto;
|
||
|
crypto.setPrivateKey(settings.privateKey());
|
||
|
ConsolePasswordProvider passwordProvider;
|
||
|
crypto.setPrivatePasswordProvider(&passwordProvider);
|
||
|
QDomDocument doc;
|
||
|
QDomElement root;
|
||
|
// use existing Updates.xml, if any
|
||
|
QFile existingUpdatesXml(QFileInfo(QDir(repoDir), QLatin1String("Updates.xml")).absoluteFilePath());
|
||
|
if (!existingUpdatesXml.open(QIODevice::ReadOnly) || !doc.setContent(&existingUpdatesXml)) {
|
||
|
verbose() << "Could not find Updates.xml" << std::endl;
|
||
|
} else {
|
||
|
root = doc.documentElement();
|
||
|
}
|
||
|
existingUpdatesXml.close();
|
||
|
|
||
|
QDir dir(repoDir);
|
||
|
const QStringList sub = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||
|
QDomNodeList elements = doc.elementsByTagName(QLatin1String("PackageUpdate"));
|
||
|
Q_FOREACH (const QString& i, sub) {
|
||
|
QDir sd(dir);
|
||
|
sd.cd(i);
|
||
|
const QString path = QString(i).remove(baseDir);
|
||
|
const QString versionPrefix = versionMapping[path];
|
||
|
if (path.isNull())
|
||
|
continue;
|
||
|
const QString absPath = sd.absolutePath();
|
||
|
const QString fn = QLatin1String(versionPrefix.toLatin1() + "meta.7z");
|
||
|
const QString tmpTarget = repoDir + QLatin1String("/") +fn;
|
||
|
compressDirectory(absPath, tmpTarget);
|
||
|
QFile tmp(tmpTarget);
|
||
|
tmp.open(QFile::ReadOnly);
|
||
|
QByteArray fileToCheck = tmp.readAll();
|
||
|
QByteArray sha1Sum = QCryptographicHash::hash(fileToCheck, QCryptographicHash::Sha1);
|
||
|
writeSHA1ToNodeWithName(doc, elements, sha1Sum, path);
|
||
|
const QString finalTarget = absPath + QLatin1String("/") + fn;
|
||
|
if (!tmp.rename(finalTarget))
|
||
|
throw QInstaller::Error(QObject::tr("Could not move %1 to %2").arg(tmpTarget, finalTarget));
|
||
|
|
||
|
// if we have a private key, sign the meta.7z file
|
||
|
if (!settings.privateKey().isEmpty()) {
|
||
|
verbose() << "Adding a RSA signature to " << finalTarget << std::endl;
|
||
|
const QByteArray signature = crypto.sign(finalTarget);
|
||
|
QFile sigFile(finalTarget + QLatin1String(".sig"));
|
||
|
if (!sigFile.open(QIODevice::WriteOnly))
|
||
|
throw QInstaller::Error(QObject::tr("Could not open %1 for writing").arg(finalTarget));
|
||
|
|
||
|
sigFile.write(signature);
|
||
|
}
|
||
|
}
|
||
|
openForWrite(&existingUpdatesXml, existingUpdatesXml.fileName());
|
||
|
blockingWrite(&existingUpdatesXml, doc.toByteArray());
|
||
|
existingUpdatesXml.close();
|
||
|
}
|
||
|
|
||
|
void QInstaller::copyComponentData(const QString& packageDir, const QString& configDir,
|
||
|
const QString& repoDir, const QVector<PackageInfo>& infos)
|
||
|
{
|
||
|
const QString configfile = QFileInfo(configDir, QLatin1String("config.xml")).absoluteFilePath();
|
||
|
const QInstaller::InstallerSettings settings =
|
||
|
QInstaller::InstallerSettings::fromFileAndPrefix(configfile, configDir);
|
||
|
|
||
|
KDUpdaterCrypto crypto;
|
||
|
crypto.setPrivateKey(settings.privateKey());
|
||
|
ConsolePasswordProvider passwordProvider;
|
||
|
crypto.setPrivatePasswordProvider(&passwordProvider);
|
||
|
|
||
|
Q_FOREACH (const PackageInfo& info, infos) {
|
||
|
const QString i = info.name;
|
||
|
verbose() << "Copying component data for " << i << std::endl;
|
||
|
const QString dataDirPath = QString::fromLatin1("%1/%2/data").arg(packageDir, i);
|
||
|
const QDir dataDir(dataDirPath);
|
||
|
if (!QDir().mkpath(QString::fromLatin1("%1/%2").arg(repoDir, i))) {
|
||
|
throw QInstaller::Error(QObject::tr("Could not create repository folder for "
|
||
|
"component %1").arg(i));
|
||
|
}
|
||
|
|
||
|
const QStringList files = dataDir.entryList(QDir::Files);
|
||
|
Q_FOREACH (const QString& file, files) {
|
||
|
QFile tmp(dataDir.absoluteFilePath(file));
|
||
|
openForRead(&tmp, tmp.fileName());
|
||
|
|
||
|
const QString target = QString::fromLatin1("%1/%2/%4%3").arg(repoDir, i, file,
|
||
|
info.version);
|
||
|
verbose() << QString::fromLatin1("Copying archive from %1 to %2").arg(tmp.fileName(),
|
||
|
target) << std::endl;
|
||
|
if (!tmp.copy(target)) {
|
||
|
throw QInstaller::Error(QObject::tr("Could not copy %1 to %2: %3")
|
||
|
.arg(tmp.fileName(), target, tmp.errorString()));
|
||
|
}
|
||
|
QFile archiveFile(target);
|
||
|
QString archiveHashFileName = archiveFile.fileName();
|
||
|
archiveHashFileName += QLatin1String(".sha1");
|
||
|
verbose() << "Hash is stored in "<< archiveHashFileName << std::endl;
|
||
|
QFile archiveHashFile(archiveHashFileName);
|
||
|
try {
|
||
|
openForRead(&archiveFile, archiveFile.fileName());
|
||
|
openForWrite(&archiveHashFile, archiveHashFile.fileName());
|
||
|
const QByteArray archiveData = archiveFile.readAll();
|
||
|
archiveFile.close();
|
||
|
const QByteArray hashOfArchiveData = QCryptographicHash::hash(archiveData,
|
||
|
QCryptographicHash::Sha1).toHex();
|
||
|
archiveHashFile.write(hashOfArchiveData);
|
||
|
archiveHashFile.close();
|
||
|
|
||
|
} catch(const Error& /*e*/) {
|
||
|
//verbose() << e.message() << std::endl;
|
||
|
archiveHashFile.close();
|
||
|
archiveFile.close();
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const QStringList dirs = dataDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||
|
Q_FOREACH (const QString& dir, dirs) {
|
||
|
verbose() << "Compressing data directory " << dir << std::endl;
|
||
|
const QString archiveName = QString::fromLatin1("%1/%2/%4%3.7z").arg(repoDir, i, dir,
|
||
|
info.version);
|
||
|
compressDirectory(dataDir.absoluteFilePath(dir), archiveName);
|
||
|
verbose() << "Creating hash of archive "<< archiveName << std::endl;
|
||
|
QFile archiveFile(archiveName);
|
||
|
QString archiveHashFileName = archiveFile.fileName();
|
||
|
archiveHashFileName += QLatin1String(".sha1");
|
||
|
verbose() << "Hash is stored in "<< archiveHashFileName << std::endl;
|
||
|
QFile archiveHashFile(archiveHashFileName);
|
||
|
try {
|
||
|
openForRead(&archiveFile, archiveFile.fileName());
|
||
|
openForWrite(&archiveHashFile, archiveHashFile.fileName());
|
||
|
const QByteArray archiveData = archiveFile.readAll();
|
||
|
archiveFile.close();
|
||
|
const QByteArray hashOfArchiveData = QCryptographicHash::hash(archiveData,
|
||
|
QCryptographicHash::Sha1).toHex();
|
||
|
archiveHashFile.write(hashOfArchiveData);
|
||
|
archiveHashFile.close();
|
||
|
} catch(const Error& /*e*/) {
|
||
|
//std::cerr << e.message() << std::endl;
|
||
|
archiveHashFile.close();
|
||
|
archiveFile.close();
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we have a private key, sign all target files - including those we compressed ourself
|
||
|
if (!settings.privateKey().isEmpty()) {
|
||
|
const QDir compDataDir(QString::fromLatin1("%1/%2").arg(repoDir, i));
|
||
|
const QStringList targetFiles = compDataDir.entryList(QDir::Files);
|
||
|
for (QStringList::const_iterator it = targetFiles.begin(); it != targetFiles.end(); ++it) {
|
||
|
verbose() << "Adding a RSA signature to " << *it << std::endl;
|
||
|
const QByteArray signature = crypto.sign(compDataDir.absoluteFilePath(*it));
|
||
|
QFile sigFile(compDataDir.absoluteFilePath(*it) + QLatin1String(".sig"));
|
||
|
if (!sigFile.open(QIODevice::WriteOnly)) {
|
||
|
throw QInstaller::Error(QObject::tr("Could not open %1 for writing: %2")
|
||
|
.arg(sigFile.fileName(), sigFile.errorString()));
|
||
|
}
|
||
|
sigFile.write(signature);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|