installer-framework/tools/common/repositorygen.cpp
Katja Marttila fff2c98f4a Merge remote-tracking branch 'origin/3.0' into master
Change-Id: I8d0c8b9faa9537b50e989ec264ed7bfe8d2e358b
2018-02-08 12:55:47 +02:00

801 lines
39 KiB
C++

/**************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
**************************************************************************/
#include "repositorygen.h"
#include <constants.h>
#include <fileio.h>
#include <fileutils.h>
#include <errors.h>
#include <globals.h>
#include <lib7z_create.h>
#include <lib7z_extract.h>
#include <lib7z_facade.h>
#include <lib7z_list.h>
#include <settings.h>
#include <qinstallerglobal.h>
#include <utils.h>
#include <scriptengine.h>
#include <updater.h>
#include <QtCore/QDirIterator>
#include <QtCore/QRegExp>
#include <QtXml/QDomDocument>
#include <iostream>
using namespace QInstaller;
using namespace QInstallerTools;
void QInstallerTools::printRepositoryGenOptions()
{
std::cout << " -p|--packages dir The directory containing the available packages." << std::endl;
std::cout << " This entry can be given multiple times." << std::endl;
std::cout << " --repository dir The directory containing the available repository." << std::endl;
std::cout << " This entry can be given multiple times." << std::endl;
std::cout << " -e|--exclude p1,...,pn Exclude the given packages." << std::endl;
std::cout << " -i|--include p1,...,pn Include the given packages and their dependencies" << std::endl;
std::cout << " from the repository." << std::endl;
std::cout << " --ignore-translations Do not use any translation" << std::endl;
std::cout << " --ignore-invalid-packages Ignore all invalid packages instead of aborting." << std::endl;
std::cout << " --ignore-invalid-repositories Ignore all invalid repositories instead of aborting." << std::endl;
}
QString QInstallerTools::makePathAbsolute(const QString &path)
{
if (QFileInfo(path).isRelative())
return QDir::current().absoluteFilePath(path);
return path;
}
void QInstallerTools::copyWithException(const QString &source, const QString &target, const QString &kind)
{
qDebug() << "Copying associated" << kind << "file" << source;
const QFileInfo targetFileInfo(target);
if (!targetFileInfo.dir().exists())
QInstaller::mkpath(targetFileInfo.absolutePath());
QFile sourceFile(source);
if (!sourceFile.copy(target)) {
qDebug() << "failed!\n";
throw QInstaller::Error(QString::fromLatin1("Cannot copy the %1 file from \"%2\" to \"%3\": "
"%4").arg(kind, QDir::toNativeSeparators(source), QDir::toNativeSeparators(target),
/* in case of an existing target the error String does not show the file */
(targetFileInfo.exists() ? QLatin1String("Target already exist.") : sourceFile.errorString())));
}
qDebug() << "done.\n";
}
static QStringList copyFilesFromNode(const QString &parentNode, const QString &childNode, const QString &attr,
const QString &kind, const QDomNode &package, const PackageInfo &info, const QString &targetDir)
{
QStringList copiedFiles;
const QDomNodeList nodes = package.firstChildElement(parentNode).childNodes();
for (int i = 0; i < nodes.count(); ++i) {
const QDomNode node = nodes.at(i);
if (node.nodeName() != childNode)
continue;
const QDir dir(QString::fromLatin1("%1/meta").arg(info.directory));
const QString filter = attr.isEmpty() ? node.toElement().text() : node.toElement().attribute(attr);
const QStringList files = dir.entryList(QStringList(filter), QDir::Files);
if (files.isEmpty()) {
throw QInstaller::Error(QString::fromLatin1("Cannot find any %1 matching \"%2\" "
"while copying %1 of \"%3\".").arg(kind, filter, info.name));
}
foreach (const QString &file, files) {
const QString source(QString::fromLatin1("%1/meta/%2").arg(info.directory, file));
const QString target(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, file));
copyWithException(source, target, kind);
copiedFiles.append(file);
}
}
return copiedFiles;
}
void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &metaDataDir,
const PackageInfoVector &packages, const QString &appName, const QString &appVersion)
{
const QString targetDir = makePathAbsolute(_targetDir);
if (!QFile::exists(targetDir))
QInstaller::mkpath(targetDir);
QDomDocument doc;
QDomElement root;
QFile existingUpdatesXml(QFileInfo(metaDataDir, QLatin1String("Updates.xml")).absoluteFilePath());
if (existingUpdatesXml.open(QIODevice::ReadOnly) && doc.setContent(&existingUpdatesXml)) {
root = doc.documentElement();
// remove entry for this component from existing Updates.xml, if found
foreach (const PackageInfo &info, packages) {
const QDomNodeList packageNodes = root.childNodes();
for (int i = packageNodes.count() - 1; i >= 0; --i) {
const QDomNode node = packageNodes.at(i);
if (node.nodeName() != QLatin1String("PackageUpdate"))
continue;
if (node.firstChildElement(QLatin1String("Name")).text() != info.name)
continue;
root.removeChild(node);
}
}
existingUpdatesXml.close();
} else {
root = doc.createElement(QLatin1String("Updates"));
root.appendChild(doc.createElement(QLatin1String("ApplicationName"))).appendChild(doc
.createTextNode(appName));
root.appendChild(doc.createElement(QLatin1String("ApplicationVersion"))).appendChild(doc
.createTextNode(appVersion));
root.appendChild(doc.createElement(QLatin1String("Checksum"))).appendChild(doc
.createTextNode(QLatin1String("true")));
}
foreach (const PackageInfo &info, packages) {
if (info.metaFile.isEmpty() && info.metaNode.isEmpty()) {
if (!QDir(targetDir).mkpath(info.name))
throw QInstaller::Error(QString::fromLatin1("Cannot create directory \"%1\".").arg(info.name));
const QString packageXmlPath = QString::fromLatin1("%1/meta/package.xml").arg(info.directory);
qDebug() << "Copy meta data for package" << info.name << "using" << packageXmlPath;
QFile file(packageXmlPath);
QInstaller::openForRead(&file);
QString errMsg;
int line = 0;
int column = 0;
QDomDocument packageXml;
if (!packageXml.setContent(&file, &errMsg, &line, &column)) {
throw QInstaller::Error(QString::fromLatin1("Cannot parse \"%1\": line: %2, column: %3: %4 (%5)")
.arg(QDir::toNativeSeparators(packageXmlPath)).arg(line).arg(column).arg(errMsg, info.name));
}
QDomElement update = doc.createElement(QLatin1String("PackageUpdate"));
QDomNode nameElement = update.appendChild(doc.createElement(QLatin1String("Name")));
nameElement.appendChild(doc.createTextNode(info.name));
// list of current unused or later transformed tags
QStringList blackList;
blackList << QLatin1String("UserInterfaces") << QLatin1String("Translations") <<
QLatin1String("Licenses") << QLatin1String("Name");
bool foundDefault = false;
bool foundVirtual = false;
bool foundDisplayName = false;
bool foundDownloadableArchives = false;
bool foundCheckable = false;
const QDomNode package = packageXml.firstChildElement(QLatin1String("Package"));
const QDomNodeList childNodes = package.childNodes();
for (int i = 0; i < childNodes.count(); ++i) {
const QDomNode node = childNodes.at(i);
const QString key = node.nodeName();
if (key == QLatin1String("Default"))
foundDefault = true;
if (key == QLatin1String("Virtual"))
foundVirtual = true;
if (key == QLatin1String("DisplayName"))
foundDisplayName = true;
if (key == QLatin1String("DownloadableArchives"))
foundDownloadableArchives = true;
if (key == QLatin1String("Checkable"))
foundCheckable = true;
if (node.isComment() || blackList.contains(key))
continue; // just skip comments and some tags...
QDomElement element = doc.createElement(key);
for (int j = 0; j < node.attributes().size(); ++j) {
element.setAttribute(node.attributes().item(j).toAttr().name(),
node.attributes().item(j).toAttr().value());
}
update.appendChild(element).appendChild(doc.createTextNode(node.toElement().text()));
}
if (foundDefault && foundVirtual) {
throw QInstaller::Error(QString::fromLatin1("Error: <Default> and <Virtual> elements are "
"mutually exclusive in file \"%1\".").arg(QDir::toNativeSeparators(packageXmlPath)));
}
if (foundDefault && foundCheckable) {
throw QInstaller::Error(QString::fromLatin1("Error: <Default> and <Checkable>"
"elements are mutually exclusive in file \"%1\".")
.arg(QDir::toNativeSeparators(packageXmlPath)));
}
if (!foundDisplayName) {
qWarning() << "No DisplayName tag found at" << info.name << ", using component Name instead.";
QDomElement displayNameElement = doc.createElement(QLatin1String("DisplayName"));
update.appendChild(displayNameElement).appendChild(doc.createTextNode(info.name));
}
// get the size of the data
quint64 componentSize = 0;
quint64 compressedComponentSize = 0;
const QDir::Filters filters = QDir::Files | QDir::NoDotAndDotDot;
const QDir dataDir = QString::fromLatin1("%1/%2/data").arg(metaDataDir, info.name);
const QFileInfoList entries = dataDir.exists() ? dataDir.entryInfoList(filters | QDir::Dirs)
: QDir(QString::fromLatin1("%1/%2").arg(metaDataDir, info.name)).entryInfoList(filters);
qDebug() << "calculate size of directory" << dataDir.absolutePath();
foreach (const QFileInfo &fi, entries) {
try {
if (fi.isDir()) {
QDirIterator recursDirIt(fi.filePath(), QDirIterator::Subdirectories);
while (recursDirIt.hasNext()) {
recursDirIt.next();
const quint64 size = QInstaller::fileSize(recursDirIt.fileInfo());
componentSize += size;
compressedComponentSize += size;
}
} else if (Lib7z::isSupportedArchive(fi.filePath())) {
// if it's an archive already, list its files and sum the uncompressed sizes
QFile archive(fi.filePath());
compressedComponentSize += archive.size();
QInstaller::openForRead(&archive);
QVector<Lib7z::File>::const_iterator fileIt;
const QVector<Lib7z::File> files = Lib7z::listArchive(&archive);
for (fileIt = files.begin(); fileIt != files.end(); ++fileIt)
componentSize += fileIt->uncompressedSize;
} else {
// otherwise just add its size
const quint64 size = QInstaller::fileSize(fi);
componentSize += size;
compressedComponentSize += size;
}
} catch (const QInstaller::Error &error) {
qDebug().noquote() << error.message();
} catch(...) {
// ignore, that's just about the sizes - and size doesn't matter, you know?
}
}
QDomElement fileElement = doc.createElement(QLatin1String("UpdateFile"));
fileElement.setAttribute(QLatin1String("UncompressedSize"), componentSize);
fileElement.setAttribute(QLatin1String("CompressedSize"), compressedComponentSize);
// adding the OS attribute to be compatible with old sdks
fileElement.setAttribute(QLatin1String("OS"), QLatin1String("Any"));
update.appendChild(fileElement);
root.appendChild(update);
// copy script file
const QString script = package.firstChildElement(QLatin1String("Script")).text();
if (!script.isEmpty()) {
QFile scriptFile(QString::fromLatin1("%1/meta/%2").arg(info.directory, script));
if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
throw QInstaller::Error(QString::fromLatin1("Cannot open component script at \"%1\".")
.arg(QDir::toNativeSeparators(scriptFile.fileName())));
}
const QString scriptContent = QLatin1String("(function() {")
+ QString::fromUtf8(scriptFile.readAll())
+ QLatin1String(";"
" if (typeof Component == \"undefined\")"
" throw \"Missing Component constructor. Please check your script.\";"
"})();");
// if the user isn't aware of the downloadable archives value we will add it automatically later
foundDownloadableArchives |= scriptContent.contains(QLatin1String("addDownloadableArchive"))
|| scriptContent.contains(QLatin1String("removeDownloadableArchive"));
static QInstaller::ScriptEngine testScriptEngine;
const QJSValue value = testScriptEngine.evaluate(scriptContent, scriptFile.fileName());
if (value.isError()) {
throw QInstaller::Error(QString::fromLatin1("Exception while loading component "
"script at \"%1\": %2").arg(QDir::toNativeSeparators(scriptFile.fileName()),
value.toString().isEmpty() ? QString::fromLatin1("Unknown error.") :
value.toString() + QStringLiteral(" on line number: ") +
value.property(QStringLiteral("lineNumber")).toString()));
}
const QString toLocation(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, script));
copyWithException(scriptFile.fileName(), toLocation, QInstaller::scScript);
}
// write DownloadableArchives tag if that is missed by the user
if (!foundDownloadableArchives && !info.copiedFiles.isEmpty()) {
QStringList realContentFiles;
foreach (const QString &filePath, info.copiedFiles) {
if (!filePath.endsWith(QLatin1String(".sha1"), Qt::CaseInsensitive)) {
const QString fileName = QFileInfo(filePath).fileName();
// remove unnecessary version string from filename and add it to the list
realContentFiles.append(fileName.mid(info.version.count()));
}
}
update.appendChild(doc.createElement(QLatin1String("DownloadableArchives"))).appendChild(doc
.createTextNode(realContentFiles.join(QChar::fromLatin1(','))));
}
// copy user interfaces
const QStringList uiFiles = copyFilesFromNode(QLatin1String("UserInterfaces"),
QLatin1String("UserInterface"), QString(), QLatin1String("user interface"), package, info,
targetDir);
if (!uiFiles.isEmpty()) {
update.appendChild(doc.createElement(QLatin1String("UserInterfaces"))).appendChild(doc
.createTextNode(uiFiles.join(QChar::fromLatin1(','))));
}
// copy translations
QStringList trFiles;
if (!qApp->arguments().contains(QString::fromLatin1("--ignore-translations"))) {
trFiles = copyFilesFromNode(QLatin1String("Translations"), QLatin1String("Translation"),
QString(), QLatin1String("translation"), package, info, targetDir);
if (!trFiles.isEmpty()) {
update.appendChild(doc.createElement(QLatin1String("Translations"))).appendChild(doc
.createTextNode(trFiles.join(QChar::fromLatin1(','))));
}
}
// copy license files
const QStringList licenses = copyFilesFromNode(QLatin1String("Licenses"), QLatin1String("License"),
QLatin1String("file"), QLatin1String("license"), package, info, targetDir);
if (!licenses.isEmpty()) {
foreach (const QString &trFile, trFiles) {
// Copy translated license file based on the assumption that it will have the same base name
// as the original license plus the file name of an existing translation file without suffix.
foreach (const QString &license, licenses) {
const QFileInfo untranslated(license);
const QString translatedLicense = QString::fromLatin1("%2_%3.%4").arg(untranslated
.baseName(), QFileInfo(trFile).baseName(), untranslated.completeSuffix());
// ignore copy failure, that's just about the translations
QFile::copy(QString::fromLatin1("%1/meta/%2").arg(info.directory).arg(translatedLicense),
QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, translatedLicense));
}
}
update.appendChild(package.firstChildElement(QLatin1String("Licenses")).cloneNode());
}
} else {
// Extract metadata from archive
QFile metaFile(info.metaFile);
QInstaller::openForRead(&metaFile);
Lib7z::extractArchive(&metaFile, targetDir);
// Restore "PackageUpdate" node;
QDomDocument update;
if (!update.setContent(info.metaNode)) {
throw QInstaller::Error(QString::fromLatin1("Cannot restore \"PackageUpdate\" description for node %1").arg(info.name));
}
root.appendChild(update.documentElement());
}
}
doc.appendChild(root);
QFile targetUpdatesXml(targetDir + QLatin1String("/Updates.xml"));
QInstaller::openForWrite(&targetUpdatesXml);
QInstaller::blockingWrite(&targetUpdatesXml, doc.toByteArray());
}
PackageInfoVector QInstallerTools::createListOfPackages(const QStringList &packagesDirectories,
QStringList *packagesToFilter, FilterType filterType)
{
qDebug() << "\nCollecting information about available packages...";
bool ignoreInvalidPackages = qApp->arguments().contains(QString::fromLatin1("--ignore-invalid-packages"));
PackageInfoVector dict;
QFileInfoList entries;
foreach (const QString &packagesDirectory, packagesDirectories)
entries.append(QDir(packagesDirectory).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot));
for (QFileInfoList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it) {
if (filterType == Exclude) {
// Check for current file in exclude list, if found, skip it and remove it from exclude list
if (packagesToFilter->contains(it->fileName())) {
packagesToFilter->removeAll(it->fileName());
continue;
}
} else {
// Check for current file in include list, if not found, skip it; if found, remove it from include list
if (!packagesToFilter->contains(it->fileName()))
continue;
packagesToFilter->removeAll(it->fileName());
}
qDebug() << "Found subdirectory" << it->fileName();
// because the filter is QDir::Dirs - filename means the name of the subdirectory
if (it->fileName().contains(QLatin1Char('-'))) {
qDebug("When using the component \"%s\" as a dependency, "
"to ensure backward compatibility, you must add a colon symbol at the end, "
"even if you do not specify a version.",
qUtf8Printable(it->fileName()));
}
QFile file(QString::fromLatin1("%1/meta/package.xml").arg(it->filePath()));
QFileInfo fileInfo(file);
if (!fileInfo.exists()) {
if (ignoreInvalidPackages)
continue;
throw QInstaller::Error(QString::fromLatin1("Component \"%1\" does not contain a package "
"description (meta/package.xml is missing).").arg(QDir::toNativeSeparators(it->fileName())));
}
file.open(QIODevice::ReadOnly);
QDomDocument doc;
QString error;
int errorLine = 0;
int errorColumn = 0;
if (!doc.setContent(&file, &error, &errorLine, &errorColumn)) {
if (ignoreInvalidPackages)
continue;
throw QInstaller::Error(QString::fromLatin1("Component package description in \"%1\" is invalid. "
"Error at line: %2, column: %3 -> %4").arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()),
QString::number(errorLine),
QString::number(errorColumn), error));
}
const QDomElement packageElement = doc.firstChildElement(QLatin1String("Package"));
const QString name = packageElement.firstChildElement(QLatin1String("Name")).text();
if (!name.isEmpty() && name != it->fileName()) {
qWarning().nospace() << "The <Name> tag in the file " << fileInfo.absoluteFilePath()
<< " is ignored - the installer uses the path element right before the 'meta'"
<< " (" << it->fileName() << ")";
}
QString releaseDate = packageElement.firstChildElement(QLatin1String("ReleaseDate")).text();
if (releaseDate.isEmpty()) {
qWarning("Release date for \"%s\" is empty! Using the current date instead.",
qPrintable(fileInfo.absoluteFilePath()));
releaseDate = QDate::currentDate().toString(Qt::ISODate);
}
if (!QDate::fromString(releaseDate, Qt::ISODate).isValid()) {
if (ignoreInvalidPackages)
continue;
throw QInstaller::Error(QString::fromLatin1("Release date for \"%1\" is invalid! <ReleaseDate>%2"
"</ReleaseDate>. Supported format: YYYY-MM-DD").arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()),
releaseDate));
}
PackageInfo info;
info.name = it->fileName();
info.version = packageElement.firstChildElement(QLatin1String("Version")).text();
// Version cannot start with comparison characters, be an empty string
// or have whitespaces at the beginning or at the end
if (!QRegExp(QLatin1String("(?![<=>\\s]+)(.+)")).exactMatch(info.version) ||
(info.version != info.version.trimmed())) {
if (ignoreInvalidPackages)
continue;
throw QInstaller::Error(QString::fromLatin1("Component version for \"%1\" is invalid! <Version>%2</Version>")
.arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()), info.version));
}
info.dependencies = packageElement.firstChildElement(QLatin1String("Dependencies")).text()
.split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
info.directory = it->filePath();
dict.push_back(info);
qDebug() << "- it provides the package" << info.name << " - " << info.version;
}
if (!packagesToFilter->isEmpty() && packagesToFilter->at(0) != QString::fromLatin1(
"X_fake_filter_component_for_online_only_installer_X")) {
qWarning() << "The following explicitly given packages could not be found\n in package directory:" << *packagesToFilter;
}
if (dict.isEmpty())
qDebug() << "No available packages found at the specified location.";
return dict;
}
PackageInfoVector QInstallerTools::createListOfRepositoryPackages(const QStringList &repositoryDirectories,
QStringList *packagesToFilter, FilterType filterType)
{
qDebug() << "Collecting information about available repository packages...";
bool ignoreInvalidRepositories = qApp->arguments().contains(QString::fromLatin1("--ignore-invalid-repositories"));
PackageInfoVector dict;
QFileInfoList entries;
foreach (const QString &repositoryDirectory, repositoryDirectories)
entries.append(QFileInfo(repositoryDirectory));
for (QFileInfoList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it) {
qDebug() << "Process repository" << it->fileName();
QFile file(QString::fromLatin1("%1/Updates.xml").arg(it->filePath()));
QFileInfo fileInfo(file);
if (!fileInfo.exists()) {
if (ignoreInvalidRepositories) {
qDebug() << "- skip invalid repository";
continue;
}
throw QInstaller::Error(QString::fromLatin1("Repository \"%1\" does not contain a update "
"description (Updates.xml is missing).").arg(QDir::toNativeSeparators(it->fileName())));
}
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open Updates.xml for reading:" << file.errorString();
continue;
}
QString error;
QDomDocument doc;
if (!doc.setContent(&file, &error)) {
qDebug().nospace() << "Cannot fetch a valid version of Updates.xml from repository "
<< it->fileName() << ": " << error;
continue;
}
file.close();
const QDomElement root = doc.documentElement();
if (root.tagName() != QLatin1String("Updates")) {
throw QInstaller::Error(QCoreApplication::translate("QInstaller",
"Invalid content in \"%1\".").arg(QDir::toNativeSeparators(file.fileName())));
}
const QDomNodeList children = root.childNodes();
for (int i = 0; i < children.count(); ++i) {
const QDomElement el = children.at(i).toElement();
if ((!el.isNull()) && (el.tagName() == QLatin1String("PackageUpdate"))) {
QInstallerTools::PackageInfo info;
QDomElement c1 = el.firstChildElement(QInstaller::scName);
if (!c1.isNull())
info.name = c1.text();
else
continue;
if (filterType == Exclude) {
// Check for current package in exclude list, if found, skip it
if (packagesToFilter->contains(info.name)) {
continue;
}
} else {
// Check for current package in include list, if not found, skip it
if (!packagesToFilter->contains(info.name))
continue;
}
c1 = el.firstChildElement(QInstaller::scVersion);
if (!c1.isNull())
info.version = c1.text();
else
continue;
info.directory = QString::fromLatin1("%1/%2").arg(it->filePath(), info.name);
info.metaFile = QString::fromLatin1("%1/%3%2").arg(info.directory,
QString::fromLatin1("meta.7z"), info.version);
const QDomNodeList c2 = el.childNodes();
for (int j = 0; j < c2.count(); ++j) {
if (c2.at(j).toElement().tagName() == QInstaller::scDependencies)
info.dependencies = c2.at(j).toElement().text()
.split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
else if (c2.at(j).toElement().tagName() == QInstaller::scDownloadableArchives) {
QStringList names = c2.at(j).toElement().text()
.split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
foreach (const QString &name, names) {
info.copiedFiles.append(QString::fromLatin1("%1/%3%2").arg(info.directory,
name, info.version));
info.copiedFiles.append(QString::fromLatin1("%1/%3%2.sha1").arg(info.directory,
name, info.version));
}
}
}
QString metaString;
{
QTextStream metaStream(&metaString);
el.save(metaStream, 0);
}
info.metaNode = metaString;
dict.push_back(info);
qDebug() << "- it provides the package" << info.name << " - " << info.version;
}
}
}
return dict;
}
QHash<QString, QString> QInstallerTools::buildPathToVersionMapping(const PackageInfoVector &info)
{
QHash<QString, QString> map;
foreach (const PackageInfo &inf, info)
map[inf.name] = inf.version;
return map;
}
static void writeSHA1ToNodeWithName(QDomDocument &doc, QDomNodeList &list, const QByteArray &sha1sum,
const QString &nodename)
{
qDebug() << "Searching sha1sum node for" << nodename;
QString sha1Value = QString::fromLatin1(sha1sum.toHex().constData());
for (int i = 0; i < list.size(); ++i) {
QDomNode curNode = list.at(i);
QDomNode nameTag = curNode.firstChildElement(scName);
if (!nameTag.isNull() && nameTag.toElement().text() == nodename) {
QDomNode sha1Node = curNode.firstChildElement(scSHA1);
if (!sha1Node.isNull() && sha1Node.hasChildNodes()) {
QDomNode sha1NodeChild = sha1Node.firstChild();
QString sha1OldValue = sha1NodeChild.nodeValue();
if (sha1Value == sha1OldValue) {
qDebug() << "- keeping the existing sha1sum" << sha1OldValue;
continue;
} else {
qDebug() << "- clearing the old sha1sum" << sha1OldValue;
sha1Node.removeChild(sha1NodeChild);
}
} else {
sha1Node = doc.createElement(scSHA1);
}
qDebug() << "- writing the sha1sum" << sha1Value;
sha1Node.appendChild(doc.createTextNode(sha1Value));
curNode.appendChild(sha1Node);
}
}
}
void QInstallerTools::compressMetaDirectories(const QString &repoDir, const QString &baseDir,
const QHash<QString, QString> &versionMapping)
{
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)) {
qDebug() << "Cannot find Updates.xml";
} else {
root = doc.documentElement();
}
existingUpdatesXml.close();
QDir dir(repoDir);
const QStringList sub = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QDomNodeList elements = doc.elementsByTagName(QLatin1String("PackageUpdate"));
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;
Lib7z::createArchive(tmpTarget, QStringList() << absPath, Lib7z::QTmpFile::No);
// remove the files that got compressed
QInstaller::removeFiles(absPath, true);
QFile tmp(tmpTarget);
tmp.open(QFile::ReadOnly);
const QByteArray sha1Sum = QInstaller::calculateHash(&tmp, QCryptographicHash::Sha1);
writeSHA1ToNodeWithName(doc, elements, sha1Sum, path);
const QString finalTarget = absPath + QLatin1String("/") + fn;
if (!tmp.rename(finalTarget)) {
throw QInstaller::Error(QString::fromLatin1("Cannot move file \"%1\" to \"%2\".").arg(
QDir::toNativeSeparators(tmpTarget), QDir::toNativeSeparators(finalTarget)));
}
}
QInstaller::openForWrite(&existingUpdatesXml);
QInstaller::blockingWrite(&existingUpdatesXml, doc.toByteArray());
existingUpdatesXml.close();
}
void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QString &repoDir,
PackageInfoVector *const infos)
{
for (int i = 0; i < infos->count(); ++i) {
const PackageInfo info = infos->at(i);
const QString name = info.name;
qDebug() << "Copying component data for" << name;
const QString namedRepoDir = QString::fromLatin1("%1/%2").arg(repoDir, name);
if (!QDir().mkpath(namedRepoDir)) {
throw QInstaller::Error(QString::fromLatin1("Cannot create repository directory for component \"%1\".")
.arg(name));
}
if (info.copiedFiles.isEmpty()) {
QStringList compressedFiles;
QStringList filesToCompress;
foreach (const QString &packageDir, packageDirs) {
const QDir dataDir(QString::fromLatin1("%1/%2/data").arg(packageDir, name));
foreach (const QString &entry, dataDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files)) {
QFileInfo fileInfo(dataDir.absoluteFilePath(entry));
if (fileInfo.isFile() && !fileInfo.isSymLink()) {
const QString absoluteEntryFilePath = dataDir.absoluteFilePath(entry);
if (Lib7z::isSupportedArchive(absoluteEntryFilePath)) {
QFile tmp(absoluteEntryFilePath);
QString target = QString::fromLatin1("%1/%3%2").arg(namedRepoDir, entry, info.version);
qDebug() << "Copying archive from" << tmp.fileName() << "to" << target;
if (!tmp.copy(target)) {
throw QInstaller::Error(QString::fromLatin1("Cannot copy file \"%1\" to \"%2\": %3")
.arg(QDir::toNativeSeparators(tmp.fileName()), QDir::toNativeSeparators(target), tmp.errorString()));
}
compressedFiles.append(target);
} else {
filesToCompress.append(absoluteEntryFilePath);
}
} else if (fileInfo.isDir()) {
qDebug() << "Compressing data directory" << entry;
QString target = QString::fromLatin1("%1/%3%2.7z").arg(namedRepoDir, entry, info.version);
Lib7z::createArchive(target, QStringList() << dataDir.absoluteFilePath(entry),
Lib7z::QTmpFile::No);
compressedFiles.append(target);
} else if (fileInfo.isSymLink()) {
filesToCompress.append(dataDir.absoluteFilePath(entry));
}
}
}
if (!filesToCompress.isEmpty()) {
qDebug() << "Compressing files found in data directory:" << filesToCompress;
QString target = QString::fromLatin1("%1/%3%2").arg(namedRepoDir, QLatin1String("content.7z"),
info.version);
Lib7z::createArchive(target, filesToCompress, Lib7z::QTmpFile::No);
compressedFiles.append(target);
}
foreach (const QString &target, compressedFiles) {
(*infos)[i].copiedFiles.append(target);
QFile archiveFile(target);
QFile archiveHashFile(archiveFile.fileName() + QLatin1String(".sha1"));
qDebug() << "Hash is stored in" << archiveHashFile.fileName();
qDebug() << "Creating hash of archive" << archiveFile.fileName();
try {
QInstaller::openForRead(&archiveFile);
const QByteArray hashOfArchiveData = QInstaller::calculateHash(&archiveFile,
QCryptographicHash::Sha1).toHex();
archiveFile.close();
QInstaller::openForWrite(&archiveHashFile);
archiveHashFile.write(hashOfArchiveData);
qDebug() << "Generated sha1 hash:" << hashOfArchiveData;
(*infos)[i].copiedFiles.append(archiveHashFile.fileName());
archiveHashFile.close();
} catch (const QInstaller::Error &/*e*/) {
archiveFile.close();
archiveHashFile.close();
throw;
}
}
} else {
foreach (const QString &file, (*infos)[i].copiedFiles) {
QFileInfo fromInfo(file);
QFile from(file);
QString target = QString::fromLatin1("%1/%2").arg(namedRepoDir, fromInfo.fileName());
qDebug() << "Copying file from" << from.fileName() << "to" << target;
if (!from.copy(target)) {
throw QInstaller::Error(QString::fromLatin1("Cannot copy file \"%1\" to \"%2\": %3")
.arg(QDir::toNativeSeparators(from.fileName()), QDir::toNativeSeparators(target), from.errorString()));
}
}
}
}
}