/************************************************************************** ** ** Copyright (C) 2021 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 "libarchivewrapper_p.h" #include "globals.h" #include namespace QInstaller { /*! \inmodule QtInstallerFramework \class QInstaller::LibArchiveWrapperPrivate \internal */ /*! \fn QInstaller::LibArchiveWrapperPrivate::dataBlockRequested() Emitted when the server process has requested another data block. */ /*! \fn QInstaller::LibArchiveWrapperPrivate::remoteWorkerFinished() Emitted when the server process has finished extracting an archive. */ /*! Constructs an archive object representing an archive file specified by \a filename. */ LibArchiveWrapperPrivate::LibArchiveWrapperPrivate(const QString &filename) : RemoteObject(QLatin1String(Protocol::AbstractArchive)) { init(); LibArchiveWrapperPrivate::setFilename(filename); } /*! Constructs the object. */ LibArchiveWrapperPrivate::LibArchiveWrapperPrivate() : RemoteObject(QLatin1String(Protocol::AbstractArchive)) { init(); } /*! Destroys the instance. */ LibArchiveWrapperPrivate::~LibArchiveWrapperPrivate() { } /*! Opens the file device using \a mode. Returns \c true if succesfull; otherwise \c false. */ bool LibArchiveWrapperPrivate::open(QIODevice::OpenMode mode) { return m_archive.open(mode); } /*! Closes the file device. */ void LibArchiveWrapperPrivate::close() { m_archive.close(); } /*! Sets the \a filename for the archive. If the remote connection is active, the same method is called by the server. */ void LibArchiveWrapperPrivate::setFilename(const QString &filename) { if (connectToServer()) { m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilename), filename, dummy); m_lock.unlock(); } m_archive.setFilename(filename); } /*! Returns a human-readable description of the last error that occurred. If the remote connection is active, the method is called by the server instead. */ QString LibArchiveWrapperPrivate::errorString() const { if ((const_cast(this))->connectToServer()) { m_lock.lockForWrite(); const QString errorString = callRemoteMethod(QLatin1String(Protocol::AbstractArchiveErrorString)); m_lock.unlock(); return errorString; } return m_archive.errorString(); } /*! Extracts the contents of this archive to \a dirPath with an optional precalculated count of \a totalFiles. Returns \c true on success; \c false otherwise. If the remote connection is active, the method is called by the server instead, with the client starting a new event loop waiting for the extraction to finish. */ bool LibArchiveWrapperPrivate::extract(const QString &dirPath, const quint64 totalFiles) { if (connectToServer()) { QTimer timer; connect(&timer, &QTimer::timeout, this, &LibArchiveWrapperPrivate::processSignals); timer.start(); m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveExtract), dirPath, totalFiles); m_lock.unlock(); { QEventLoop loop; connect(this, &LibArchiveWrapperPrivate::remoteWorkerFinished, &loop, &QEventLoop::quit); loop.exec(); } timer.stop(); return (workerStatus() == ExtractWorker::Success); } return m_archive.extract(dirPath, totalFiles); } /*! Packages the given \a data into the archive and creates the file on disk. Returns \c true on success; \c false otherwise. If the remote connection is active, the method is called by the server instead. */ bool LibArchiveWrapperPrivate::create(const QStringList &data) { if (connectToServer()) { m_lock.lockForWrite(); const bool success = callRemoteMethod(QLatin1String(Protocol::AbstractArchiveCreate), data); m_lock.unlock(); return success; } return m_archive.create(data); } /*! Returns the contents of this archive as an array of \c ArchiveEntry objects. On failure, returns an empty array. */ QVector LibArchiveWrapperPrivate::list() { return m_archive.list(); } /*! Returns \c true if the current archive is of a supported format; \c false otherwise. */ bool LibArchiveWrapperPrivate::isSupported() { return m_archive.isSupported(); } /*! Sets the compression level for new archives to \a level. If the remote connection is active, the method is called by the server instead. */ void LibArchiveWrapperPrivate::setCompressionLevel(const AbstractArchive::CompressionLevel level) { if (connectToServer()) { m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetCompressionLevel), level, dummy); m_lock.unlock(); return; } m_archive.setCompressionLevel(level); } /*! Cancels the extract operation in progress. If the remote connection is active, the method is called by the server instead. */ void LibArchiveWrapperPrivate::cancel() { if (connectToServer()) { m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveCancel)); m_lock.unlock(); return; } m_archive.cancel(); } /*! Calls a remote method to get the associated queued signals from the server. Signals are then processed and emitted client-side. */ void LibArchiveWrapperPrivate::processSignals() { if (!isConnectedToServer()) return; if (!m_lock.tryLockForRead()) return; QList receivedSignals = callRemoteMethod>(QString::fromLatin1(Protocol::GetAbstractArchiveSignals)); m_lock.unlock(); while (!receivedSignals.isEmpty()) { const QString name = receivedSignals.takeFirst().toString(); if (name == QLatin1String(Protocol::AbstractArchiveSignalCurrentEntryChanged)) { emit currentEntryChanged(receivedSignals.takeFirst().toString()); } else if (name == QLatin1String(Protocol::AbstractArchiveSignalCompletedChanged)) { const quint64 completed = receivedSignals.takeFirst().value(); const quint64 total = receivedSignals.takeFirst().value(); emit completedChanged(completed, total); } else if (name == QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested)) { emit dataBlockRequested(); } else if (name == QLatin1String(Protocol::AbstractArchiveSignalSeekRequested)) { const qint64 offset = receivedSignals.takeFirst().value(); const int whence = receivedSignals.takeFirst().value(); emit seekRequested(offset, whence); } else if (name == QLatin1String(Protocol::AbstractArchiveSignalWorkerFinished)) { emit remoteWorkerFinished(); } } } /*! Reads a block of data from the current position of the underlying file device. */ void LibArchiveWrapperPrivate::onDataBlockRequested() { constexpr quint64 blockSize = 1024 * 1024; // 1MB QFile *const file = &m_archive.m_data->file; if (!file->isOpen() || file->isSequential()) { qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString(); setClientDataAtEnd(); return; } if (file->atEnd() && file->seek(0)) { setClientDataAtEnd(); return; } QByteArray *const buff = &m_archive.m_data->buffer; if (!buff->isEmpty()) buff->clear(); if (buff->size() != blockSize) buff->resize(blockSize); const qint64 bytesRead = file->read(buff->data(), blockSize); if (bytesRead == -1) { qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString(); setClientDataAtEnd(); return; } // The read callback in ExtractWorker class expects the buffer size to // match the number of bytes read. Some formats will fail if the buffer // is larger than the actual data. if (buff->size() != bytesRead) buff->resize(bytesRead); addDataBlock(*buff); } /*! Seeks to specified \a offset in the underlying file device. Possible \a whence values are \c SEEK_SET, \c SEEK_CUR, and \c SEEK_END. */ void LibArchiveWrapperPrivate::onSeekRequested(qint64 offset, int whence) { QFile *const file = &m_archive.m_data->file; if (!file->isOpen() || file->isSequential()) { qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString(); setClientFilePosition(ARCHIVE_FATAL); return; } bool success = false; switch (whence) { case SEEK_SET: // moves file pointer position to the beginning of the file success = file->seek(offset); break; case SEEK_CUR: // moves file pointer position to given location success = file->seek(file->pos() + offset); break; case SEEK_END: // moves file pointer position to the end of file success = file->seek(file->size() + offset); break; default: break; } setClientFilePosition(success ? file->pos() : ARCHIVE_FATAL); } /*! Connects handler signals for the matching signals of the wrapper object. */ void LibArchiveWrapperPrivate::init() { QObject::connect(&m_archive, &LibArchiveArchive::currentEntryChanged, this, &LibArchiveWrapperPrivate::currentEntryChanged); QObject::connect(&m_archive, &LibArchiveArchive::completedChanged, this, &LibArchiveWrapperPrivate::completedChanged); QObject::connect(this, &LibArchiveWrapperPrivate::dataBlockRequested, this, &LibArchiveWrapperPrivate::onDataBlockRequested); QObject::connect(this, &LibArchiveWrapperPrivate::seekRequested, this, &LibArchiveWrapperPrivate::onSeekRequested); } /*! Calls a remote method to add a \a buffer for reading. */ void LibArchiveWrapperPrivate::addDataBlock(const QByteArray &buffer) { if (connectToServer()) { m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveAddDataBlock), buffer, dummy); m_lock.unlock(); } } /*! Calls a remote method to inform that the client has finished reading the current file. */ void LibArchiveWrapperPrivate::setClientDataAtEnd() { if (connectToServer()) { m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)); m_lock.unlock(); } } /*! Calls a remote method to set new file position \a pos. */ void LibArchiveWrapperPrivate::setClientFilePosition(qint64 pos) { if (connectToServer()) { m_lock.lockForWrite(); callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilePosition), pos, dummy); m_lock.unlock(); } } /*! Calls a remote method to retrieve and return the status of the extract worker on a server process. */ ExtractWorker::Status LibArchiveWrapperPrivate::workerStatus() const { ExtractWorker::Status status = ExtractWorker::Unfinished; if ((const_cast(this))->connectToServer()) { m_lock.lockForWrite(); status = static_cast( callRemoteMethod(QLatin1String(Protocol::AbstractArchiveWorkerStatus))); m_lock.unlock(); } return status; } } // namespace QInstaller