Introduce two new functions to read and write binary content.

In addition update the auto test to test both new functions.

Change-Id: I85f1e28cf486fc381941e553dac6defa9d327117
Reviewed-by: Niels Weber <niels.weber@digia.com>
This commit is contained in:
kh1 2014-07-30 17:30:29 +02:00 committed by Karsten Heimrich
parent 2dfa49ac79
commit 554a7f108e
5 changed files with 497 additions and 2 deletions

View File

@ -526,4 +526,154 @@ void BinaryContent::registerAsDefaultQResource(const QString &path)
}
}
void BinaryContent::readBinaryContent(const QSharedPointer<QFile> &in,
ResourceCollection *metaResources, QList<OperationBlob> *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<qint64> resourceCollectionsSegment = QInstaller::retrieveInt64Range(in.data());
// read the meta data resource segments
QVector<Range<qint64> > metaDataResourceSegments;
for (int i = 0; i < metaResourcesCount; ++i)
metaDataResourceSegments.append(QInstaller::retrieveInt64Range(in.data()));
// read the operations offset and length
const Range<qint64> 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<qint64> &segment, metaDataResourceSegments) {
metaResources->appendResource(QSharedPointer<Resource>(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<QFile> &out,
const ResourceCollection &metaResources, const QList<OperationBlob> &operations,
const ResourceCollectionManager &manager, qint64 magicMarker, quint64 magicCookie)
{
const qint64 endOfBinary = out->pos();
// resources
qint64 pos = out->pos();
QVector<Range<qint64> > metaResourceSegments;
foreach (const QSharedPointer<Resource> &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<qint64>::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<qint64> operationsSegment = Range<qint64>::fromStartAndEnd(pos, out->pos())
.moved(-endOfBinary);
// resource collections data and index
const Range<qint64> resourceCollectionsSegment = manager.write(out.data(), -endOfBinary)
.moved(-endOfBinary);
QInstaller::appendInt64Range(out.data(), resourceCollectionsSegment);
// meta resource segments
foreach (const Range<qint64> &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

View File

@ -42,8 +42,8 @@
#ifndef BINARYCONTENT_H
#define BINARYCONTENT_H
#include "binaryformat.h"
#include "range.h"
#include "qinstallerglobal.h"
#include <QVector>
@ -97,6 +97,19 @@ public:
int registerEmbeddedQResources();
void registerAsDefaultQResource(const QString &path);
static void readBinaryContent(const QSharedPointer<QFile> &in,
ResourceCollection *metaResources,
QList<OperationBlob> *operations,
ResourceCollectionManager *manager,
qint64 *magicMarker,
quint64 magicCookie);
static void writeBinaryContent(const QSharedPointer<QFile> &out,
const ResourceCollection &metaResources,
const QList<OperationBlob> &operations,
const ResourceCollectionManager &manager,
qint64 magicMarker,
quint64 magicCookie);
private:
explicit BinaryContent(const QString &path);
static void readBinaryData(BinaryContent &content, const QSharedPointer<QFile> &file,

View File

@ -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

View File

@ -1,5 +1,6 @@
include(../../qttest.pri)
QT -= gui
QT += xml
SOURCES += tst_binaryformat.cpp

View File

@ -40,8 +40,10 @@
**************************************************************************/
#include <binarycontent.h>
#include <binaryformat.h>
#include <errors.h>
#include <fileio.h>
#include <kdupdaterupdateoperation.h>
#include <QTest>
#include <QTemporaryFile>
@ -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<qint64> metaResourceSegment;
QVector<Range<qint64> > metaResourceSegments;
qint64 operationsCount;
Range<qint64> operationsSegment;
qint64 collectionCount;
Range<qint64> 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<qint64>::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<qint64>::fromStartAndEnd(start, end)
.moved(-layout.endOfBinary));
layout.metaResourceSegment = Range<qint64>::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<qint64>::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> 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<Resource> 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<qint64> &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<QFile> 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<qint64>::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<ResourceCollection> components = m_manager.collections();
QCOMPARE(components.count(), m_layout.collectionCount);
ResourceCollection component = m_manager.collectionByName("Collection 1");
QCOMPARE(component.resources().count(), 1);
QSharedPointer<Resource> 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<QFile> existingBinary(new QFile(m_binary));
QInstaller::openForRead(existingBinary.data());
QSharedPointer<QFile> file(new QTemporaryFile);
QInstaller::openForWrite(file.data());
QInstaller::blockingWrite(file.data(), QByteArray(scTinySize, '1'));
ResourceCollection resources;
foreach (const Range<qint64> &segment, m_layout.metaResourceSegments) {
resources.appendResource(QSharedPointer<Resource> (new Resource(existingBinary,
segment.moved(m_layout.endOfBinary))));
}
QList<OperationBlob> 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<QFile> file(new QFile(m_binary));
QInstaller::openForRead(file.data());
qint64 magicMarker;
ResourceCollection collection;
QList<OperationBlob> 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> 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<OperationBlob> m_operations;
ResourceCollectionManager m_manager;
};
QTEST_MAIN(tst_BinaryFormat)