/************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 << " -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; } 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 (!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: and elements are " "mutually exclusive in file \"%1\".").arg(QDir::toNativeSeparators(packageXmlPath))); } if (foundDefault && foundCheckable) { throw QInstaller::Error(QString::fromLatin1("Error: and " "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::const_iterator fileIt; const QVector 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()); } } 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('-'))) { if (ignoreInvalidPackages) continue; throw QInstaller::Error(QString::fromLatin1("Component \"%1\" must not contain '-'. This is not " "allowed, because dashes are used as the separator between the component name and the " "version number internally.").arg(QDir::toNativeSeparators(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 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! %2" ". Supported format: YYYY-MM-DD").arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()), releaseDate)); } PackageInfo info; info.name = it->fileName(); info.version = packageElement.firstChildElement(QLatin1String("Version")).text(); if (!QRegExp(QLatin1String("[0-9]+((\\.|-)[0-9]+)*")).exactMatch(info.version)) { if (ignoreInvalidPackages) continue; throw QInstaller::Error(QString::fromLatin1("Component version for \"%1\" is invalid! %2") .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; } QHash QInstallerTools::buildPathToVersionMapping(const PackageInfoVector &info) { QHash 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; 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 QInstallerTools::compressMetaDirectories(const QString &repoDir, const QString &baseDir, const QHash &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)); } 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; } } } }