/************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** **************************************************************************/ #include "binaryformat.h" #include "errors.h" #include "fileio.h" #include #include #include namespace QInstaller { /*! \class QInstaller::OperationBlob \inmodule QtInstallerFramework \brief The OperationBlob class is a textual representation of an operation that can be instantiated and executed by the Qt Installer Framework. */ /*! \fn OperationBlob::OperationBlob(const QString &n, const QString &x) Constructs the operation blob with the given arguments, while \a n stands for the name part and \a x for the XML representation of the operation. */ /*! \variable QInstaller::OperationBlob::name \brief The name of the operation. */ /*! \variable QInstaller::OperationBlob::xml \brief The XML representation of the operation. */ /*! \class QInstaller::Resource \inmodule QtInstallerFramework \brief The Resource class is an interface for wrapping a file as read only device. Resource is an interface for reading inside a file, but is not supposed to write to the file it wraps. The \c Resource class is created by passing a path to an existing binary (such as a zipped archive or a Qt resource file). The resource name can be set at any time using setName() or during construction. The segment supplied during construction represents the offset and size of the resource inside the file. */ /*! \fn Range Resource::segment() const Returns the range inside the file this resource represents. */ /*! \fn void Resource::setSegment(const Range &segment) Sets the range to the \a segment of the file that this resource represents. */ /*! Creates a resource providing the data in \a path. */ Resource::Resource(const QString &path) : m_file(path) , m_name(QFileInfo(path).fileName().toUtf8()) , m_segment(Range::fromStartAndLength(0, m_file.size())) { } /*! Creates a resource providing the data in \a path identified by \a name. */ Resource::Resource(const QString &path, const QByteArray &name) : m_file(path) , m_name(name) , m_segment(Range::fromStartAndLength(0, m_file.size())) { } /*! Creates a resource providing the data in \a path limited to \a segment. */ Resource::Resource(const QString &path, const Range &segment) : m_file(path) , m_name(QFileInfo(path).fileName().toUtf8()) , m_segment(segment) { } /*! Destroys the resource. Calls close() if necessary before destroying the resource. */ Resource::~Resource() { if (isOpen()) close(); } /*! \reimp */ bool Resource::seek(qint64 pos) { return QIODevice::seek(pos); } /*! Returns the name of the resource. */ QByteArray Resource::name() const { return m_name; } /*! Sets the name of the resource to \a name. */ void Resource::setName(const QByteArray &name) { m_name = name; } /*! Opens a resource in QIODevice::ReadOnly mode. The function returns \c true if successful. */ bool Resource::open() { if (isOpen()) return false; if (!m_file.open(QIODevice::ReadOnly)) { setErrorString(m_file.errorString()); return false; } if (!QIODevice::open(QIODevice::ReadOnly)) { setErrorString(tr("Could not open Resource '%1' read-only.").arg(QString::fromUtf8(m_name))); return false; } return true; } /*! \reimp */ void Resource::close() { m_file.close(); QIODevice::close(); } /*! \reimp */ qint64 Resource::size() const { return m_segment.length(); } /*! \reimp */ qint64 Resource::readData(char* data, qint64 maxSize) { // check if there is anything left to read maxSize = qMin(maxSize, m_segment.length() - pos()); if (maxSize <= 0) return 0; const qint64 p = m_file.pos(); m_file.seek(m_segment.start() + pos()); const qint64 amountRead = m_file.read(data, maxSize); m_file.seek(p); return amountRead; } /*! \reimp */ qint64 Resource::writeData(const char* data, qint64 maxSize) { Q_UNUSED(data); Q_UNUSED(maxSize); // should never be called, as we're read only return -1; } /*! \fn void Resource::copyData(QFileDevice *out) Copies the resource data to a file called \a out. Throws Error on failure. */ /*! \overload Copies the resource data of \a resource to a file called \a out. Throws Error on failure. */ void Resource::copyData(Resource *resource, QFileDevice *out) { qint64 left = resource->size(); char data[4096]; while (left > 0) { const qint64 len = qMin(left, 4096); const qint64 bytesRead = resource->read(data, len); if (bytesRead != len) { throw QInstaller::Error(tr("Read failed after %1 bytes: %2") .arg(QString::number(resource->size() - left), resource->errorString())); } const qint64 bytesWritten = out->write(data, len); if (bytesWritten != len) { throw QInstaller::Error(tr("Write failed after %1 bytes: %2") .arg(QString::number(resource->size() - left), out->errorString())); } left -= len; } } /*! \class QInstaller::ResourceCollection \inmodule QtInstallerFramework \brief The ResourceCollection class is an abstraction that groups together a number of resources. The resources are supposed to be sequential, so the collection keeps them ordered once a new resource is added. The name can be set at any time using setName(). */ /*! The class constructor creates an empty resource collection. By default the collection gets a unique name assigned using QUuid. */ ResourceCollection::ResourceCollection() : m_name(QUuid::createUuid().toByteArray()) { } /*! The class constructor creates an empty resource collection with a name set to \a name. */ ResourceCollection::ResourceCollection(const QByteArray &name) : m_name(name) {} /*! Returns the name of the resource collection. */ QByteArray ResourceCollection::name() const { return m_name; } /*! Sets the name of the resource collection to \a name. */ void ResourceCollection::setName(const QByteArray &name) { m_name = name; } /*! Appends \a resource to this collection. The collection takes ownership of \a resource. */ void ResourceCollection::appendResource(const QSharedPointer& resource) { Q_ASSERT(resource); resource->setParent(0); m_resources.append(resource); } /*! Appends a list of \a resources to this collection. The collection takes ownership of \a resources. */ void ResourceCollection::appendResources(const QList > &resources) { foreach (const QSharedPointer &resource, resources) appendResource(resource); } /*! Returns the resources associated with this collection. */ QList > ResourceCollection::resources() const { return m_resources; } /*! Returns the resource associated with the name \a name. */ QSharedPointer ResourceCollection::resourceByName(const QByteArray &name) const { foreach (const QSharedPointer& i, m_resources) { if (i->name() == name) return i; } return QSharedPointer(); } /*! \class QInstaller::ResourceCollectionManager \inmodule QtInstallerFramework \brief The ResourceCollectionManager class is an abstraction that groups together a number of resource collections. The resource collections it groups can be written to and read from a QFileDevice. */ /*! Reads the resource collection from the file \a dev. The \a offset argument is used to set the collection's resources segment information. */ void ResourceCollectionManager::read(QFileDevice *dev, qint64 offset) { const qint64 size = QInstaller::retrieveInt64(dev); for (int i = 0; i < size; ++i) { ResourceCollection collection(QInstaller::retrieveByteArray(dev)); const Range segment = QInstaller::retrieveInt64Range(dev).moved(offset); const qint64 pos = dev->pos(); dev->seek(segment.start()); const qint64 count = QInstaller::retrieveInt64(dev); for (int i = 0; i < count; ++i) { QSharedPointer resource(new Resource(dev->fileName())); resource->setName(QInstaller::retrieveByteArray(dev)); resource->setSegment(QInstaller::retrieveInt64Range(dev).moved(offset)); collection.appendResource(resource); } dev->seek(pos); insertCollection(collection); } } /*! Writes the resource collection to the file \a out. The \a offset argument is used to set the collection's segment information. */ Range ResourceCollectionManager::write(QFileDevice *out, qint64 offset) const { QHash < QByteArray, Range > table; QInstaller::appendInt64(out, collectionCount()); foreach (const ResourceCollection &collection, m_collections) { const qint64 dataBegin = out->pos(); QInstaller::appendInt64(out, collection.resources().count()); qint64 start = out->pos() + offset; foreach (const QSharedPointer &resource, collection.resources()) { start += (sizeof(qint64)) // the number of bytes that get written and the + resource->name().size() // resource name (see QInstaller::appendByteArray) + (2 * sizeof(qint64)); // the resource range (see QInstaller::appendInt64Range) } foreach (const QSharedPointer &resource, collection.resources()) { QInstaller::appendByteArray(out, resource->name()); QInstaller::appendInt64Range(out, Range::fromStartAndLength(start, resource->size())); // the actual range once the table has been written start += resource->size(); // adjust for next resource data } foreach (const QSharedPointer &resource, collection.resources()) { if (!resource->open()) { throw QInstaller::Error(tr("Could not open resource %1: %2") .arg(QString::fromUtf8(resource->name()), resource->errorString())); } resource->copyData(out); } table.insert(collection.name(), Range::fromStartAndEnd(dataBegin, out->pos()) .moved(offset)); } const qint64 start = out->pos(); // Q: why do we write the size twice? // A: for us to be able to read it beginning from the end of the file as well QInstaller::appendInt64(out, collectionCount()); foreach (const QByteArray &name, table.keys()) { QInstaller::appendByteArray(out, name); QInstaller::appendInt64Range(out, table.value(name)); } QInstaller::appendInt64(out, collectionCount()); return Range::fromStartAndEnd(start, out->pos()); } /*! Returns the collection associated with the name \a name. */ ResourceCollection ResourceCollectionManager::collectionByName(const QByteArray &name) const { return m_collections.value(name); } /*! Inserts the \a collection into the collection manager. */ void ResourceCollectionManager::insertCollection(const ResourceCollection& collection) { m_collections.insert(collection.name(), collection); } /*! Removes all occurrences of \a name from the collection manager. */ void ResourceCollectionManager::removeCollection(const QByteArray &name) { m_collections.remove(name); } /*! Returns the collections the collection manager contains. */ QList ResourceCollectionManager::collections() const { return m_collections.values(); } /*! Clears the contents of the collection manager. */ void ResourceCollectionManager::clear() { m_collections.clear(); } /*! Returns the number of collections in the collection manager. */ int ResourceCollectionManager::collectionCount() const { return m_collections.count(); } } // namespace QInstaller