diff --git a/src/libs/installer/binarycontent.cpp b/src/libs/installer/binarycontent.cpp index a60e9ebb..33856adf 100644 --- a/src/libs/installer/binarycontent.cpp +++ b/src/libs/installer/binarycontent.cpp @@ -526,4 +526,154 @@ void BinaryContent::registerAsDefaultQResource(const QString &path) } } +void BinaryContent::readBinaryContent(const QSharedPointer &in, + ResourceCollection *metaResources, QList *operations, + ResourceCollectionManager *manager, qint64 *magicMarker, quint64 magicCookie) +{ + const qint64 pos = BinaryContent::findMagicCookie(in.data(), magicCookie); + const qint64 endOfBinaryContent = pos + sizeof(qint64); + + const qint64 posOfMetaDataCount = endOfBinaryContent - (4 * sizeof(qint64)); + if (!in->seek(posOfMetaDataCount)) { + throw Error(QCoreApplication::translate("BinaryContent", + "Could not seek to %1 to read the embedded meta data count.").arg(posOfMetaDataCount)); + } + // read the meta resources count + const qint64 metaResourcesCount = QInstaller::retrieveInt64(in.data()); + + const qint64 posOfResourceCollectionsSegment = endOfBinaryContent + - ((metaResourcesCount * (2 * sizeof(qint64))) // minus the size of the meta data segments + + (8 * sizeof(qint64))); // meta count, offset/length component index, marker, cookie... + if (!in->seek(posOfResourceCollectionsSegment)) { + throw Error(QCoreApplication::translate("BinaryContent", + "Could not seek to %1 to read the resource collection segment.") + .arg(posOfResourceCollectionsSegment)); + } + // read the resource collection index offset and length + const Range resourceCollectionsSegment = QInstaller::retrieveInt64Range(in.data()); + + // read the meta data resource segments + QVector > metaDataResourceSegments; + for (int i = 0; i < metaResourcesCount; ++i) + metaDataResourceSegments.append(QInstaller::retrieveInt64Range(in.data())); + + // read the operations offset and length + const Range operationsSegment = QInstaller::retrieveInt64Range(in.data()); + + // resources count + Q_UNUSED(QInstaller::retrieveInt64(in.data())) // read it, but deliberately not used + + // read the binary content size + const qint64 binaryContentSize = QInstaller::retrieveInt64(in.data()); + const qint64 endOfBinary = endOfBinaryContent - binaryContentSize; // end of "compiled" binary + + // read the marker + const qint64 marker = QInstaller::retrieveInt64(in.data()); + if (magicMarker) + *magicMarker = marker; + + // the cookie + Q_UNUSED(QInstaller::retrieveInt64(in.data())) // read it, but deliberately not used + + // append the calculated resource segments + if (metaResources) { + foreach (const Range &segment, metaDataResourceSegments) { + metaResources->appendResource(QSharedPointer(new Resource(in, + segment.moved(endOfBinary)))); + } + } + + const qint64 posOfOperationsBlock = endOfBinary + operationsSegment.start(); + if (!in->seek(posOfOperationsBlock)) { + throw Error(QCoreApplication::translate("BinaryContent", + "Could not seek to %1 to read the operation data.").arg(posOfOperationsBlock)); + } + // read the operations count + qint64 operationsCount = QInstaller::retrieveInt64(in.data()); + // read the operations + for (int i = 0; i < operationsCount; ++i) { + const QString name = QInstaller::retrieveString(in.data()); + const QString xml = QInstaller::retrieveString(in.data()); + if (operations) + operations->append(OperationBlob(name, xml)); + } + // operations count + Q_UNUSED(QInstaller::retrieveInt64(in.data())) // read it, but deliberately not used + + // read the resource collections count + const qint64 collectionCount = QInstaller::retrieveInt64(in.data()); + + const qint64 posOfResourceCollectionBlock = endOfBinary + resourceCollectionsSegment.start(); + if (!in->seek(posOfResourceCollectionBlock)) { + throw Error(QCoreApplication::translate("BinaryContent", + "Could not seek to %1 to read the resource collection block.") + .arg(posOfResourceCollectionBlock)); + } + if (manager) { // read the component index and data + manager->read(in, endOfBinary); + if (manager->collectionCount() != collectionCount) { + throw Error(QCoreApplication::translate("BinaryContent", + "Unexpected mismatch of resource collections. Read %1, expected: %2.") + .arg(manager->collectionCount()).arg(collectionCount)); + } + } +} + +void BinaryContent::writeBinaryContent(const QSharedPointer &out, + const ResourceCollection &metaResources, const QList &operations, + const ResourceCollectionManager &manager, qint64 magicMarker, quint64 magicCookie) +{ + const qint64 endOfBinary = out->pos(); + + // resources + qint64 pos = out->pos(); + QVector > metaResourceSegments; + foreach (const QSharedPointer &resource, metaResources.resources()) { + const bool isOpen = resource->isOpen(); + if ((!isOpen) && (!resource->open())) { + throw Error(QCoreApplication::translate("BinaryContent", + "Could not open meta resource. Error: %1").arg(resource->errorString())); + } + + resource->seek(0); + resource->copyData(out.data()); + metaResourceSegments.append(Range::fromStartAndEnd(pos, out->pos()) + .moved(-endOfBinary)); + pos = out->pos(); + + if (!isOpen) // If we reach that point, either the resource was opened already... + resource->close(); // or we did open it and have to close it again. + } + + // operations + QInstaller::appendInt64(out.data(), operations.count()); + foreach (const OperationBlob &operation, operations) { + QInstaller::appendString(out.data(), operation.name); + QInstaller::appendString(out.data(), operation.xml); + } + QInstaller::appendInt64(out.data(), operations.count()); + const Range operationsSegment = Range::fromStartAndEnd(pos, out->pos()) + .moved(-endOfBinary); + + // resource collections data and index + const Range resourceCollectionsSegment = manager.write(out.data(), -endOfBinary) + .moved(-endOfBinary); + QInstaller::appendInt64Range(out.data(), resourceCollectionsSegment); + + // meta resource segments + foreach (const Range &segment, metaResourceSegments) + QInstaller::appendInt64Range(out.data(), segment); + + // operations segment + QInstaller::appendInt64Range(out.data(), operationsSegment); + + // resources count + QInstaller::appendInt64(out.data(), metaResourceSegments.count()); + + const qint64 binaryContentSize = (out->pos() + (3 * sizeof(qint64))) - endOfBinary; + QInstaller::appendInt64(out.data(), binaryContentSize); + QInstaller::appendInt64(out.data(), magicMarker); + QInstaller::appendInt64(out.data(), magicCookie); +} + } // namespace QInstaller diff --git a/src/libs/installer/binarycontent.h b/src/libs/installer/binarycontent.h index 9cd48e4c..f553b8b9 100644 --- a/src/libs/installer/binarycontent.h +++ b/src/libs/installer/binarycontent.h @@ -42,8 +42,8 @@ #ifndef BINARYCONTENT_H #define BINARYCONTENT_H +#include "binaryformat.h" #include "range.h" -#include "qinstallerglobal.h" #include @@ -97,6 +97,19 @@ public: int registerEmbeddedQResources(); void registerAsDefaultQResource(const QString &path); + static void readBinaryContent(const QSharedPointer &in, + ResourceCollection *metaResources, + QList *operations, + ResourceCollectionManager *manager, + qint64 *magicMarker, + quint64 magicCookie); + + static void writeBinaryContent(const QSharedPointer &out, + const ResourceCollection &metaResources, + const QList &operations, + const ResourceCollectionManager &manager, + qint64 magicMarker, + quint64 magicCookie); private: explicit BinaryContent(const QString &path); static void readBinaryData(BinaryContent &content, const QSharedPointer &file, diff --git a/src/libs/installer/binaryformat.h b/src/libs/installer/binaryformat.h index 1097b561..162966b5 100644 --- a/src/libs/installer/binaryformat.h +++ b/src/libs/installer/binaryformat.h @@ -50,6 +50,14 @@ namespace QInstaller { +struct OperationBlob { + OperationBlob(const QString &n, const QString &x) + : name(n), xml(x) {} + QString name; + QString xml; +}; + + class INSTALLER_EXPORT Resource : public QIODevice { Q_OBJECT diff --git a/tests/auto/installer/binaryformat/binaryformat.pro b/tests/auto/installer/binaryformat/binaryformat.pro index 24a7899c..c624b60f 100644 --- a/tests/auto/installer/binaryformat/binaryformat.pro +++ b/tests/auto/installer/binaryformat/binaryformat.pro @@ -1,5 +1,6 @@ include(../../qttest.pri) QT -= gui +QT += xml SOURCES += tst_binaryformat.cpp diff --git a/tests/auto/installer/binaryformat/tst_binaryformat.cpp b/tests/auto/installer/binaryformat/tst_binaryformat.cpp index a17226d4..d313bbbc 100644 --- a/tests/auto/installer/binaryformat/tst_binaryformat.cpp +++ b/tests/auto/installer/binaryformat/tst_binaryformat.cpp @@ -40,8 +40,10 @@ **************************************************************************/ #include +#include #include #include +#include #include #include @@ -50,11 +52,56 @@ static const qint64 scTinySize = 72704LL; static const qint64 scSmallSize = 524288LL; static const qint64 scLargeSize = 2097152LL; +using namespace QInstaller; + +struct Layout +{ + Range metaResourceSegment; + QVector > metaResourceSegments; + + qint64 operationsCount; + Range operationsSegment; + + qint64 collectionCount; + Range resourceCollectionIndexSegment; + + qint64 binaryContentSize; + qint64 magicMarker; + quint64 magicCookie; + + qint64 endOfBinary; +}; + +class TestOperation : public KDUpdater::UpdateOperation +{ +public: + TestOperation(const QString &name) { setName(name); } + + virtual void backup() {} + virtual bool performOperation() { return true; } + virtual bool undoOperation() { return true; } + virtual bool testOperation() { return true; } + virtual Operation *clone() const { return 0; } +}; + class tst_BinaryFormat : public QObject { Q_OBJECT private slots: + void initTestCase() + { + TestOperation op(QLatin1String("Operation 1")); + op.setValue(QLatin1String("key"), QLatin1String("Operation 1 value.")); + op.setArguments(QStringList() << QLatin1String("arg1") << QLatin1String("arg2")); + m_operations.append(OperationBlob(op.name(), op.toXml().toString())); + + op = TestOperation(QLatin1String("Operation 2")); + op.setValue(QLatin1String("key"), QLatin1String("Operation 2 value.")); + op.setArguments(QStringList() << QLatin1String("arg1") << QLatin1String("arg2")); + m_operations.append(OperationBlob(op.name(), op.toXml().toString())); + } + void findMagicCookieSmallFile() { QTemporaryFile file; @@ -92,7 +139,7 @@ private slots: } } - void testFindMagicCookieWithError() + void findMagicCookieWithError() { QTest::ignoreMessage(QtDebugMsg, "create Error-Exception: \"No marker found, stopped after 71.00 KiB.\" "); @@ -110,6 +157,282 @@ private slots: QFAIL("Unexpected error."); } } + + void writeBinaryContent() + { + QTemporaryFile binary; + QInstaller::openForWrite(&binary); + QInstaller::blockingWrite(&binary, QByteArray(scTinySize, '1')); + + Layout layout; + layout.endOfBinary = binary.pos(); + layout.magicMarker = BinaryContent::MagicInstallerMarker; + layout.magicCookie = BinaryContent::MagicCookie; + + qint64 start = binary.pos(); // write default resource (fake) + QInstaller::blockingWrite(&binary, QByteArray("Default resource data.")); + qint64 end = binary.pos(); + layout.metaResourceSegments.append(Range::fromStartAndEnd(start, end) + .moved(-layout.endOfBinary)); + + start = end; // // write additional resource (fake) + QInstaller::blockingWrite(&binary, QByteArray("Additional resource data.")); + end = binary.pos(); + layout.metaResourceSegments.append(Range::fromStartAndEnd(start, end) + .moved(-layout.endOfBinary)); + layout.metaResourceSegment = Range::fromStartAndEnd(layout.metaResourceSegments.first() + .start(), layout.metaResourceSegments.last().end()); + + start = end; + layout.operationsCount = m_operations.count(); + QInstaller::appendInt64(&binary, layout.operationsCount); + foreach (const OperationBlob &operation, m_operations) { + QInstaller::appendString(&binary, operation.name); + QInstaller::appendString(&binary, operation.xml); + } + QInstaller::appendInt64(&binary, layout.operationsCount); + end = binary.pos(); + layout.operationsSegment = Range::fromStartAndEnd(start, end).moved(-layout + .endOfBinary); + + QTemporaryFile data; + QTemporaryFile data2; + { // put into the scope to make the temporary file auto remove feature work + + ResourceCollectionManager manager; + + QInstaller::openForWrite(&data); + QInstaller::blockingWrite(&data, QByteArray("Collection 1, Resource 1.")); + data.close(); + + ResourceCollection collection; + collection.setName(QByteArray("Collection 1")); + + QSharedPointer resource(new Resource(data.fileName())); + resource->setName("Resource 1"); + collection.appendResource(resource); + manager.insertCollection(collection); + + QInstaller::openForWrite(&data2); + QInstaller::blockingWrite(&data2, QByteArray("Collection 2, Resource 2.")); + data2.close(); + + ResourceCollection collection2; + collection2.setName(QByteArray("Collection 2")); + + QSharedPointer resource2(new + Resource(data2.fileName())); + resource2->setName("Resource 2"); + collection2.appendResource(resource2); + manager.insertCollection(collection2); + + layout.collectionCount = manager.collectionCount(); + layout.resourceCollectionIndexSegment = manager.write(&binary, -layout.endOfBinary) + .moved(-layout.endOfBinary); + + resource->close(); + resource2->close(); + } + + QInstaller::appendInt64Range(&binary, layout.resourceCollectionIndexSegment); + foreach (const Range &segment, layout.metaResourceSegments) + QInstaller::appendInt64Range(&binary, segment); + QInstaller::appendInt64Range(&binary, layout.operationsSegment); + QInstaller::appendInt64(&binary, layout.metaResourceSegments.count()); + + layout.binaryContentSize = (binary.pos() + (3 * sizeof(qint64))) - layout.endOfBinary; + + QInstaller::appendInt64(&binary, layout.binaryContentSize); + QInstaller::appendInt64(&binary, layout.magicMarker); + QInstaller::appendInt64(&binary, layout.magicCookie); + + binary.close(); + binary.setAutoRemove(false); + + m_layout = layout; + m_binary = binary.fileName(); + } + + void readBinaryContent() + { + const QSharedPointer binary(new QFile(m_binary)); + QInstaller::openForRead(binary.data()); + QCOMPARE(QInstaller::retrieveData(binary.data(), scTinySize), QByteArray(scTinySize, '1')); + + Layout layout; + layout.endOfBinary = binary->pos(); + QCOMPARE(layout.endOfBinary, m_layout.endOfBinary); + + const qint64 pos = BinaryContent::findMagicCookie(binary.data(), BinaryContent::MagicCookie); + const qint64 endOfBinaryContent = pos + sizeof(qint64); + + binary->seek(endOfBinaryContent - (4 * sizeof(qint64))); + + qint64 metaSegmentsCount = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(metaSegmentsCount, m_layout.metaResourceSegments.count()); + + const qint64 offsetCollectionIndexSegments = endOfBinaryContent + - ((metaSegmentsCount * (2 * sizeof(qint64))) // minus the size of the meta segments + + (8 * sizeof(qint64))); // meta count, offset/length component index, marker, cookie... + + binary->seek(offsetCollectionIndexSegments); + + layout.resourceCollectionIndexSegment = QInstaller::retrieveInt64Range(binary.data()); + QCOMPARE(layout.resourceCollectionIndexSegment, m_layout.resourceCollectionIndexSegment); + + for (int i = 0; i < metaSegmentsCount; ++i) + layout.metaResourceSegments.append(QInstaller::retrieveInt64Range(binary.data())); + layout.metaResourceSegment = Range::fromStartAndEnd(layout.metaResourceSegments + .first().start(), layout.metaResourceSegments.last().end()); + QCOMPARE(layout.metaResourceSegment, m_layout.metaResourceSegment); + QCOMPARE(layout.metaResourceSegments, m_layout.metaResourceSegments); + + layout.operationsSegment = QInstaller::retrieveInt64Range(binary.data()); + QCOMPARE(layout.operationsSegment, m_layout.operationsSegment); + + QCOMPARE(metaSegmentsCount, QInstaller::retrieveInt64(binary.data())); + + layout.binaryContentSize = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(layout.binaryContentSize, m_layout.binaryContentSize); + QCOMPARE(layout.endOfBinary, endOfBinaryContent - layout.binaryContentSize); + + layout.magicMarker = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(layout.magicMarker, m_layout.magicMarker); + + layout.magicCookie = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(layout.magicCookie, m_layout.magicCookie); + + binary->seek(layout.endOfBinary + layout.operationsSegment.start()); + + layout.operationsCount = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(layout.operationsCount, m_layout.operationsCount); + + for (int i = 0; i < layout.operationsCount; ++i) { + QCOMPARE(m_operations.at(i).name, QInstaller::retrieveString(binary.data())); + QCOMPARE(m_operations.at(i).xml, QInstaller::retrieveString(binary.data())); + } + + layout.operationsCount = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(layout.operationsCount, m_layout.operationsCount); + + layout.collectionCount = QInstaller::retrieveInt64(binary.data()); + QCOMPARE(layout.collectionCount, m_layout.collectionCount); + + binary->seek(layout.endOfBinary + layout.resourceCollectionIndexSegment.start()); + m_manager.read(binary, layout.endOfBinary); + + const QList components = m_manager.collections(); + QCOMPARE(components.count(), m_layout.collectionCount); + + ResourceCollection component = m_manager.collectionByName("Collection 1"); + QCOMPARE(component.resources().count(), 1); + + QSharedPointer resource(component.resourceByName("Resource 1")); + QCOMPARE(resource.isNull(), false); + QCOMPARE(resource->isOpen(), false); + QCOMPARE(resource->open(), true); + QCOMPARE(resource->readAll(), QByteArray("Collection 1, Resource 1.")); + resource->close(); + + component = m_manager.collectionByName("Collection 2"); + QCOMPARE(component.resources().count(), 1); + + resource = component.resourceByName("Resource 2"); + QCOMPARE(resource.isNull(), false); + QCOMPARE(resource->isOpen(), false); + QCOMPARE(resource->open(), true); + QCOMPARE(resource->readAll(), QByteArray("Collection 2, Resource 2.")); + resource->close(); + } + + void testWriteBinaryContentFunction() + { + QSharedPointer existingBinary(new QFile(m_binary)); + QInstaller::openForRead(existingBinary.data()); + + QSharedPointer file(new QTemporaryFile); + QInstaller::openForWrite(file.data()); + QInstaller::blockingWrite(file.data(), QByteArray(scTinySize, '1')); + + ResourceCollection resources; + foreach (const Range &segment, m_layout.metaResourceSegments) { + resources.appendResource(QSharedPointer (new Resource(existingBinary, + segment.moved(m_layout.endOfBinary)))); + } + + QList operations; + foreach (const OperationBlob &operation, m_operations) + operations.append(operation); + + BinaryContent::writeBinaryContent(file, resources, operations, m_manager, + m_layout.magicMarker, m_layout.magicCookie); + file->close(); + existingBinary->close(); + + QInstaller::openForRead(file.data()); + QInstaller::openForRead(existingBinary.data()); + QCOMPARE(file->readAll(), existingBinary->readAll()); + } + + void testReadBinaryContentFunction() + { + QSharedPointer file(new QFile(m_binary)); + QInstaller::openForRead(file.data()); + + qint64 magicMarker; + ResourceCollection collection; + QList operations; + ResourceCollectionManager manager; + BinaryContent::readBinaryContent(file, &collection, &operations, &manager, &magicMarker, + m_layout.magicCookie); + + QCOMPARE(magicMarker, m_layout.magicMarker); + QCOMPARE(collection.resources().count(), m_layout.metaResourceSegments.count()); + for (int i = 0; i < collection.resources().count(); ++i) { + QCOMPARE(collection.resources().at(i)->segment(), m_layout.metaResourceSegments.at(i) + .moved(m_layout.endOfBinary)); + } + + QCOMPARE(operations.count(), m_operations.count()); + for (int i = 0; i < operations.count(); ++i) { + QCOMPARE(operations.at(i).name, m_operations.at(i).name); + QCOMPARE(operations.at(i).xml, m_operations.at(i).xml); + } + + QCOMPARE(manager.collectionCount(), m_manager.collectionCount()); + + ResourceCollection component = manager.collectionByName("Collection 1"); + QCOMPARE(component.resources().count(), 1); + + QSharedPointer resource(component.resourceByName("Resource 1")); + QCOMPARE(resource.isNull(), false); + QCOMPARE(resource->isOpen(), false); + QCOMPARE(resource->open(), true); + QCOMPARE(resource->readAll(), QByteArray("Collection 1, Resource 1.")); + resource->close(); + + component = manager.collectionByName("Collection 2"); + QCOMPARE(component.resources().count(), 1); + + resource = component.resourceByName("Resource 2"); + QCOMPARE(resource.isNull(), false); + QCOMPARE(resource->isOpen(), false); + QCOMPARE(resource->open(), true); + QCOMPARE(resource->readAll(), QByteArray("Collection 2, Resource 2.")); + resource->close(); + } + + void cleanupTestCase() + { + m_manager.reset(); + QFile::remove(m_binary); + } + +private: + Layout m_layout; + QString m_binary; + QList m_operations; + ResourceCollectionManager m_manager; }; QTEST_MAIN(tst_BinaryFormat)