diff options
Diffstat (limited to 'installerbuilder/common/repositorygen.cpp')
-rw-r--r-- | installerbuilder/common/repositorygen.cpp | 802 |
1 files changed, 802 insertions, 0 deletions
diff --git a/installerbuilder/common/repositorygen.cpp b/installerbuilder/common/repositorygen.cpp new file mode 100644 index 000000000..3a1a533cd --- /dev/null +++ b/installerbuilder/common/repositorygen.cpp @@ -0,0 +1,802 @@ +/************************************************************************** +** +** 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); + } + } + } +} |