Adds initial intermediate qt6 porting state

This commit is contained in:
torsten committed 2026-08-17 17:54:56 +02:00
1 parent 99136e9460
commit d176b5e0d7
11247 files changed
+1891537

No files matched your search

@@ -0,0 +1,47 @@
#
# CMake Script
# $Id$
# $HeadURL$
# $Author$
#
FIND_PACKAGE(BZip2 REQUIRED)
SET(nglcomp_SRCS
nglcomp_bzip2iodevice.cpp
nglcomp_bzip2iodevice.h
nglcomp_compress.cpp
nglcomp_compress.h
nglcomp_export.h
nglcomp_tarfilter.cpp
nglcomp_tarfilter.h
)
NGL_ADD_LIBRARY(WS nglcompression SHARED ${nglcomp_SRCS})
NGL_TARGET_LINK_LIBRARIES(nglcompression
PUBLIC
Qt5::Core
PRIVATE
Qt5::Concurrent
NGLLogger
${BZIP2_LIBRARIES}
# BZip2::BZip2 # not available with cmake 3.5
)
NGL_TARGET_INCLUDE_DIRECTORIES(nglcompression
PRIVATE
${BZIP2_INCLUDE_DIR}
)
NGL_INSTALL_TARGET(nglcompression)
IF(BUILD_WS AND BUILD_TESTING)
REMOVE_DEFINITIONS(-DQT_GUI_LIB)
ADD_EXECUTABLE(nglcomp_test nglcomp_test.cpp nglcomp_test.h)
TARGET_LINK_LIBRARIES(nglcomp_test
NGLLogger
nglcoreapplication
ngltools
nglcompression
Qt5::Test
)
ADD_TEST(NAME nglcomp_test COMMAND $<TARGET_FILE:nglcomp_test>)
ENDIF()
@@ -0,0 +1,360 @@
#include "nglcomp_bzip2iodevice.h"
#include <QIODevice>
#include <QString>
#include <bzlib.h>
class BZip2IODevice::Private
{
public:
Private(BZip2IODevice *parent_, QIODevice *dev, int bs)
: parent(parent_)
, device(dev)
, iCurPos(0)
, iMaxBufSize(1024 * 1024)
, blocksize(bs)
, initialized(false)
, decomressStreamEnd(false)
{
buffer.resize(iMaxBufSize);
}
bool initializeCompress();
qint64 doCompress(const char *in, qint64 iMaxLen);
bool finishCompress();
bool initializeDecompress();
qint64 doDecompress(char *out, qint64 iMaxLen);
bool finishDecompress();
static QString bzipError2String(int errCode);
QAtomicInt aborted { 0 };
BZip2IODevice *parent;
QIODevice *device;
unsigned int iCurPos;
unsigned int iMaxBufSize;
unsigned int blocksize;
bool initialized;
bool decomressStreamEnd;
QByteArray buffer;
bz_stream stream;
};
bool BZip2IODevice::Private::initializeCompress()
{
if (initialized)
return true;
memset(&stream, 0, sizeof(stream));
int blockSize = (blocksize > 0 && blocksize < 10) ? blocksize : 10;
int ret = BZ2_bzCompressInit(&stream, blockSize, 0, 0);
if (ret != BZ_OK)
{
parent->setErrorString(QStringLiteral("Error initializing bzip2 %1").arg(bzipError2String(ret)));
initialized = false;
}
else
initialized = true;
return initialized;
}
qint64 BZip2IODevice::Private::doCompress(const char *in, qint64 iMaxLen)
{
if (!initialized)
{
if (parent->errorString().isEmpty())
parent->setErrorString(QLatin1String("Internal Error - bzip not initialized"));
finishCompress();
return -1;
}
if (iMaxLen > 2 * 1024 * 1024 * 1024LL)
{
finishCompress();
parent->setErrorString(QLatin1String("Currently can't handle more than 2GB!"));
return -1;
}
stream.next_in = const_cast<char *>(in);
stream.avail_in = iMaxLen;
do
{
if (aborted) return 0;
stream.next_out = buffer.data();
stream.avail_out = buffer.size();
const int ret = BZ2_bzCompress(&stream, BZ_RUN);
if (ret != BZ_RUN_OK)
{
finishCompress();
parent->setErrorString(QStringLiteral("Error executing BZ2_bzCompress: %1").arg(bzipError2String(ret)));
return -1;
}
int iBytesToWrite = buffer.size() - stream.avail_out;
if (iBytesToWrite)
{
if (device->write(buffer.constData(), iBytesToWrite) != iBytesToWrite)
{
finishCompress();
parent->setErrorString(QLatin1String("Error writing to QIODevice"));
return -1;
}
}
}
while (stream.avail_in != 0);
return stream.next_in - in;
}
bool BZip2IODevice::Private::finishCompress()
{
if (!initialized || aborted)
return true;
int ret = -1;
do
{
stream.next_out = buffer.data();
stream.avail_out = buffer.size();
ret = BZ2_bzCompress(&stream, BZ_FINISH);
if (ret != BZ_FINISH_OK && ret != BZ_STREAM_END)
{
finishCompress();
parent->setErrorString(QStringLiteral("Error executing BZ2_bzCompress: %1").arg(bzipError2String(ret)));
return false;
}
int iBytesToWrite = buffer.size() - stream.avail_out;
if (device->write(buffer.constData(), iBytesToWrite) != iBytesToWrite)
{
finishCompress();
parent->setErrorString(QLatin1String("Error writing to QIODevice"));
return false;
}
}
while (ret != BZ_STREAM_END);
BZ2_bzCompressEnd(&stream);
initialized = false;
return true;
}
bool BZip2IODevice::Private::initializeDecompress()
{
if (initialized)
return true;
memset(&stream, 0, sizeof(stream));
int ret = BZ2_bzDecompressInit(&stream, 0, 0);
if (ret != BZ_OK)
{
parent->setErrorString(QStringLiteral("Error initializing bzip2: %1").arg(bzipError2String(ret)));
initialized = false;
}
else
initialized = true;
decomressStreamEnd = false;
return initialized;
}
qint64 BZip2IODevice::Private::doDecompress(char *out, qint64 iMaxLen)
{
int ret = -1;
if (!initialized)
{
if (parent->errorString().isEmpty())
parent->setErrorString(QLatin1String("Internal Error - bzip not initialized"));
finishDecompress();
return -1;
}
if (decomressStreamEnd)
return 0;
if (iMaxLen > 2 * 1024 * 1024 * 1024LL)
{
finishCompress();
parent->setErrorString(QLatin1String("Currently can't handle more than 2GB!"));
return -1;
}
stream.next_out = out;
stream.avail_out = iMaxLen;
do
{
if (ret != BZ_STREAM_END && stream.avail_in == 0)
{
qint64 iReadCnt = device->read(buffer.data(), buffer.size());
if (iReadCnt <= 0)
return -1;
stream.next_in = buffer.data();
stream.avail_in = iReadCnt;
}
ret = BZ2_bzDecompress(&stream);
if (ret != BZ_OK && ret != BZ_STREAM_END)
{
parent->setErrorString(QStringLiteral("Error executing BZ2_bzCompress: %1").arg(bzipError2String(ret)));
finishDecompress();
return -1;
}
}
while (stream.avail_out != 0 && ret != BZ_STREAM_END);
decomressStreamEnd = (ret == BZ_STREAM_END);
return stream.next_out - out;
}
bool BZip2IODevice::Private::finishDecompress()
{
if (!initialized)
return true;
BZ2_bzDecompressEnd(&stream);
initialized = false;
return true;
}
QString BZip2IODevice::Private::bzipError2String(int errCode)
{
switch (errCode)
{
case BZ_SEQUENCE_ERROR:
return QLatin1String("Sequence error");
case BZ_PARAM_ERROR:
return QLatin1String("Parameter not correct");
case BZ_MEM_ERROR:
return QLatin1String("Memory allocation error");
case BZ_DATA_ERROR:
return QLatin1String("Invalid input data");
case BZ_DATA_ERROR_MAGIC:
return QLatin1String("Not a bzip2 file");
case BZ_IO_ERROR:
return QLatin1String("I/O error");
case BZ_UNEXPECTED_EOF:
return QLatin1String("Unexpected end of file");
case BZ_OUTBUFF_FULL:
return QLatin1String("Output buffer full");
case BZ_CONFIG_ERROR:
return QLatin1String("Wrong config parameter");
};
return QString::number(errCode);
}
BZip2IODevice::BZip2IODevice(QIODevice *dev, int blocksize)
: d(new Private(this, dev, blocksize))
{}
BZip2IODevice::~BZip2IODevice()
{
close();
delete d;
}
void BZip2IODevice::setBufferSize(unsigned int size)
{
d->iMaxBufSize = size > 1024 ? size : 1024;
d->buffer.resize(d->iMaxBufSize);
}
void BZip2IODevice::setBlockSize(unsigned int size)
{
if (isOpen())
return;
d->blocksize = size;
}
bool BZip2IODevice::isSequential() const
{
return true;
}
bool BZip2IODevice::open(OpenMode mode)
{
if ((mode & QIODevice::ReadWrite) == 0)
{
setErrorString(QStringLiteral("Unsupported OpenMode %1").arg(mode));
return false;
}
if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite)
{
setErrorString(QLatin1String("Can't read and write simultaniously"));
return false;
}
if ((mode & QIODevice::ReadOnly) == QIODevice::ReadOnly)
{
if (!d->device)
{
setErrorString(QLatin1String("No QIODevice for reading available"));
return false;
}
if ((d->device->openMode() & QIODevice::ReadOnly) == 0)
{
setErrorString(QLatin1String("QIODevice not opened for reading"));
return false;
}
if (!d->initializeDecompress())
return false;
}
else
{
if (!d->device)
{
setErrorString(QLatin1String("No QIODevice for writing available"));
return false;
}
if ((d->device->openMode() & QIODevice::WriteOnly) == 0)
{
setErrorString(QLatin1String("QIODevice not opened for writing"));
return false;
}
if (!d->initializeCompress())
return false;
}
setErrorString(QString());
setOpenMode(mode);
return QIODevice::open(mode);
}
void BZip2IODevice::abort()
{
d->aborted = 1;
}
void BZip2IODevice::close()
{
if (!isOpen())
return;
if ((openMode() & QIODevice::ReadOnly) == QIODevice::ReadOnly)
d->finishDecompress();
else
d->finishCompress();
QIODevice::close();
}
bool BZip2IODevice::atEnd() const
{
if (!d->device)
return true;
return d->device->atEnd() && d->decomressStreamEnd && QIODevice::atEnd();
}
qint64 BZip2IODevice::readData(char *data, qint64 maxSize)
{
return d->doDecompress(data, maxSize);
}
qint64 BZip2IODevice::writeData(const char *data, qint64 maxSize)
{
return d->doCompress(data, maxSize);
}
@@ -0,0 +1,42 @@
#ifndef BZIP2IODEVICE_H
#define BZIP2IODEVICE_H
#include <QString>
#include <QIODevice>
class BZip2IODevice : public QIODevice
{
public:
BZip2IODevice(QIODevice *dev, int blocksize = 5);
virtual ~BZip2IODevice();
void abort();
void setBufferSize(unsigned int size);
void setBlockSize(unsigned int size);
virtual bool isSequential() const;
virtual bool open(OpenMode mode);
virtual void close();
virtual bool atEnd() const;
protected:
virtual qint64 readData(char *data, qint64 maxSize);
virtual qint64 writeData(const char *data, qint64 maxSize);
private:
class Private;
friend class Private;
Private *const d;
};
#endif
@@ -0,0 +1,570 @@
#include "nglcomp_compress.h"
#include "nglcomp_bzip2iodevice.h"
#include "nglcomp_tarfilter.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QBuffer>
#include <QtConcurrent>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QFutureWatcher>
#include <QIODevice>
#include <QStringList>
#include <memory>
#include <utility>
#include <QDebug>
namespace NGL
{
Compress::Compress(QObject *parent)
: QObject(parent)
, m_u2_memFiles()
, m_aborted(0)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("nglcompress"))
{
DEBUG_LOG(m_logger) << "c_tor";
QTimer *t = new QTimer(this);
t->setInterval(1000);
connect(t, &QTimer::timeout, this, [](){qWarning() << "still alive ...";});
t->start();
}
Compress::~Compress()
{
qWarning() << "## COMPRESS DTOR!!!!";
DEBUG_LOG(m_logger) << "~";
abort();
m_future.waitForFinished();
DEBUG_LOG(m_logger) << "~ finished";
}
void Compress::abort()
{
DEBUG_LOG(m_logger) << "ABORT";
m_aborted = 1;
m_lastError = tr("Aborted");
if (m_currentTarFilter != nullptr)
{
DEBUG_LOG(m_logger) << "aborting tar";
m_currentTarFilter->abort();
}
if (m_currentBzipDevice != nullptr)
{
DEBUG_LOG(m_logger) << "aborting bzip";
m_currentBzipDevice->abort();
}
}
void Compress::compress(QIODevice *out,
const QString &filesRootDir,
const QVector<FileInformations> &files,
const QVector<FileInformations> &memFiles,
const QString &destRootDir)
{
DEBUG_LOG(m_logger) << "compress #2";
reset();
setupFutureWatcher(QtConcurrent::run([this, out, filesRootDir, files, memFiles, destRootDir]() {
return this->_compress(out, filesRootDir, files, memFiles, destRootDir);
}));
}
void Compress::compress(QIODevice *out, const QString &sourceDir, const QString &destRootDir)
{
DEBUG_LOG(m_logger) << "compress #1 source dir: " << sourceDir << ", destRootDir: " << destRootDir;
reset();
QVector<Compress::FileInformations> inFiles = directoryFileList(sourceDir, true);
int len = sourceDir.length();
if (!sourceDir.endsWith(QLatin1Char('/')))
len += 1;
for (auto &fi : inFiles)
{
fi.inputFileName = fi.inputFileName.mid(len);
fi.bAbsInputPath = false;
DEBUG_LOG(m_logger) << "fi: ifm = " << fi.inputFileName << ", abs = " << fi.bAbsInputPath;
}
DEBUG_LOG(m_logger) << "FI list size: " << inFiles.count();
compress(out, sourceDir, inFiles, {}, destRootDir);
}
bool Compress::_compress(QIODevice *output,
const QString &filesRootDir,
const QVector<FileInformations> &files,
const QVector<FileInformations> &memFiles,
const QString &_destRootDir)
{
std::shared_ptr<int> invalidatePointersOnScopeLeave(nullptr, [this](int *)
{
DEBUG_LOG(m_logger) << "invalidatePointersOnScopeLeave #1";
m_currentTarFilter = nullptr;
m_currentBzipDevice = nullptr;
});
(void)invalidatePointersOnScopeLeave;
if (!output || !output->isWritable())
{
m_lastError = tr("Device is not writeable");
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;;
return false;
}
const QString destRootDir = _destRootDir.endsWith(QLatin1Char('/')) ?
_destRootDir : _destRootDir + QLatin1Char('/');
const qint64 maxCount = files.count() + memFiles.count();
qint64 count = 0;
DEBUG_LOG(m_logger) << "destRootDir = " << destRootDir << ", maxCount = " << maxCount << ", count = " << count;
BZip2IODevice bzip2(output, 9);
m_currentBzipDevice = &bzip2;
if (!bzip2.open(QIODevice::WriteOnly))
{
m_lastError = bzip2.errorString();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
qint64 bytesTotal = 0;
for (const FileInformations &file : std::as_const(files))
{
if (m_aborted) return false;
const QString in = file.bAbsInputPath ? file.inputFileName : filesRootDir + QLatin1Char('/') + file.inputFileName;
const QFileInfo fi(in);
bytesTotal += fi.size();
}
qint64 currentFileSize = 0;
QString currentFileName;
auto readCallback = [this, &maxCount, &count, &currentFileSize, &currentFileName, &bytesTotal](const qint64 iSize)
{
DEBUG_LOG(m_logger) << "readCallback: maxCount = " << maxCount << ", count = " << count
<< ", currentFileSize = " << currentFileSize << ", currentFileName = " << currentFileName << ", iSize = " << iSize;
emit compressInfoEx(maxCount, count, currentFileSize, (currentFileSize - iSize), currentFileName, bytesTotal);
};
TarFilter tf(&bzip2, readCallback);
m_currentTarFilter = &tf;
const QStringList paths = destRootDir.split(QLatin1Char('/'), Qt::SkipEmptyParts);
QString curPath;
DEBUG_LOG(m_logger) << "prepairing tar filter";
for (const QString &path : paths)
{
DEBUG_LOG(m_logger) << "path = " << path;
if (m_aborted) return false;
if (curPath.isEmpty())
curPath = path + QLatin1Char('/');
else
curPath += path + QLatin1Char('/');
DEBUG_LOG(m_logger) << "curPath = " << curPath << ", filesRootDir.isEmpty() = " << filesRootDir.isEmpty();
if (filesRootDir.isEmpty())
{
if (!tf.addData(curPath, QByteArray(), true))
{
m_lastError = tf.lastError();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
else
{
if (!tf.addFile(filesRootDir, curPath))
{
m_lastError = tf.lastError();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
}
m_c_sizeInfo.realSize = 0;
for (const FileInformations &file : std::as_const(files))
{
DEBUG_LOG(m_logger) << "file = " << file.inputFileName;
emit compressInfo(maxCount, ++count, file.inputFileName);
if (m_aborted) return false;
QString in = file.bAbsInputPath ? file.inputFileName : filesRootDir + QLatin1Char('/') + file.inputFileName;
QString out = destRootDir + (file.outputFileName.isEmpty() ? file.inputFileName : file.outputFileName);
DEBUG_LOG(m_logger) << "in = " << in << ", out = " << out;
QFileInfo fi(in);
currentFileName = fi.fileName();
currentFileSize = fi.size();
m_c_sizeInfo.realSize += currentFileSize;
DEBUG_LOG(m_logger) << "currentFileName = " << currentFileName << ", currentFileSize = " << currentFileSize << "m_c_sizeInfo.realSize" << m_c_sizeInfo.realSize;
if (!tf.addFile(in, out))
{
m_lastError = tf.lastError();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
for (const FileInformations &file : std::as_const(memFiles))
{
DEBUG_LOG(m_logger) << "(mem) file = " << file.inputFileName;
emit compressInfo(maxCount, ++count, file.inputFileName);
if (m_aborted) return false;
currentFileName = file.inputFileName;
currentFileSize = file.fileData.size();
m_c_sizeInfo.compressedSize += currentFileSize;
DEBUG_LOG(m_logger) << "(mem) currentFileName = " << currentFileName << ", currentFileSize = " << currentFileSize << "m_c_sizeInfo.realSize" << m_c_sizeInfo.realSize;
if (!tf.addData(file.inputFileName, file.fileData))
{
m_lastError = tf.lastError();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
DEBUG_LOG(m_logger) << "writing eos";
tf.writeEOS();
DEBUG_LOG(m_logger) << "closing bzip";
bzip2.close();
m_c_sizeInfo.compressedSize = output->size();
DEBUG_LOG(m_logger) << "m_c_sizeInfo.compressedSize = " << m_c_sizeInfo.compressedSize;
m_success = true;
DEBUG_LOG(m_logger) << "DONE!";
return true;
}
void Compress::uncompress(QIODevice *in, const QString &outDir)
{
DEBUG_LOG(m_logger) << "uncompress #1";
reset();
setupFutureWatcher(QtConcurrent::run([this, in, outDir](){ return this->_uncompressDirectory(in, outDir); }));
}
bool Compress::_uncompressDirectory(QIODevice *input, const QString &outDir)
{
DEBUG_LOG(m_logger) << "_uncompressDirectory";
std::shared_ptr<int> invalidatePointersOnScopeLeave(nullptr, [this](int *)
{
DEBUG_LOG(m_logger) << "invalidatePointersOnScopeLeave #2";
m_currentTarFilter = nullptr;
m_currentBzipDevice = nullptr;
});
(void)invalidatePointersOnScopeLeave;
if (!input || !input->isReadable())
{
m_lastError = tr("Device is not readable");
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
BZip2IODevice bzip2(input, 9);
m_currentBzipDevice = &bzip2;
if (!bzip2.open(QIODevice::ReadOnly))
{
m_lastError = bzip2.errorString();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
TarFilter tf(&bzip2);
m_currentTarFilter = &tf;
TarFilter::FileInformations tfi;
QDir dir(outDir);
DEBUG_LOG(m_logger) << "dir = " << dir.path();
while (tf.getData(tfi))
{
DEBUG_LOG(m_logger) << "tfi.fileName = " << tfi.fileName;
emit uncompressInfo(input->size(), input->pos(), tfi.fileName);
if (m_aborted) return false;
QString path = outDir + QLatin1Char('/') + tfi.fileName;
DEBUG_LOG(m_logger) << "path = " << path;
if (tfi.fileType == TarFilter::directory)
{
DEBUG_LOG(m_logger) << "tfi.fileType == TarFilter::directory";
if (!dir.exists(path) && !dir.mkpath(path))
{
m_lastError = tr("Error creating directory %1").arg(outDir);
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
else
{
QString pathName = QFileInfo(path).absolutePath();
if (!dir.exists(pathName) && !dir.mkpath(pathName))
{
m_lastError = tr("Error creating directory %1").arg(pathName);
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
QFile f(path);
if (!f.open(QIODevice::WriteOnly))
{
m_lastError = tr("Error opening %1 for writing (%2)").arg(path, f.errorString());
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
if (!tf.getData(&f))
{
m_lastError = tf.lastError();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
}
DEBUG_LOG(m_logger) << "uncompress done!";
m_success = true;
return true;
}
void Compress::uncompress(QIODevice *in)
{
DEBUG_LOG(m_logger) << "uncompress #2!";
reset();
setupFutureWatcher(QtConcurrent::run([this, in](){ return this->_uncompressBuffer(in); }));
}
bool Compress::_uncompressBuffer(QIODevice *input)
{
DEBUG_LOG(m_logger) << "uncompressBuffer";
std::shared_ptr<int> invalidatePointersOnScopeLeave(nullptr, [this](int *)
{
DEBUG_LOG(m_logger) << "invalidatePointersOnScopeLeave #2";
m_currentTarFilter = nullptr;
m_currentBzipDevice = nullptr;
});
(void)invalidatePointersOnScopeLeave;
if (!input->isReadable())
{
m_lastError = tr("Device is not readable");
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
BZip2IODevice bzip2(input, 9);
m_currentBzipDevice = &bzip2;
if (!bzip2.open(QIODevice::ReadOnly))
{
m_lastError = bzip2.errorString();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
TarFilter tf(&bzip2);
m_currentTarFilter = &tf;
TarFilter::FileInformations tfi;
while (tf.getData(tfi))
{
DEBUG_LOG(m_logger) << "tfi.fileName = " << tfi.fileName;
emit uncompressInfo(input->size(), input->pos(), tfi.fileName);
if (m_aborted) return false;
FileInformations fi;
fi.outputFileName = tfi.fileName;
DEBUG_LOG(m_logger) << "fi.outputFileName = " << fi.outputFileName;
if (tfi.fileType == TarFilter::directory)
{
fi.outputFileName += QLatin1Char('/');
DEBUG_LOG(m_logger) << "> fi.outputFileName = " << fi.outputFileName;
}
else
{
DEBUG_LOG(m_logger) << "stuff";
QBuffer buf(&fi.fileData);
if (!buf.open(QIODevice::WriteOnly))
{
m_lastError = tr("Error opening buffer for writing (%1)").arg(buf.errorString());
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
if (!tf.getData(&buf))
{
m_lastError = tf.lastError();
ERROR_LOG(m_logger) << "compress failed: " << m_lastError;
return false;
}
}
fi.bAbsInputPath = fi.outputFileName.startsWith(QLatin1Char('/'));
DEBUG_LOG(m_logger) << "added fi.bAbsInputPath = " << fi.bAbsInputPath;
m_u2_memFiles += fi;
}
m_success = true;
return true;
}
bool Compress::success() const
{
ERROR_LOG(m_logger) << "success? " << (m_future.isFinished() && !m_future.isCanceled() && m_success);
ERROR_LOG(m_logger) << "success details: "
<< "success m_future.isFinished() = " << (m_future.isFinished())
<< " !m_future.isCanceled() = " << !m_future.isCanceled()
<< " m_success = " << m_success;
return (m_future.isFinished() && !m_future.isCanceled() && m_success);
}
bool Compress::waitForFinished()
{
DEBUG_LOG(m_logger) << "waitForFinished...";
m_future.waitForFinished();
DEBUG_LOG(m_logger) << "waitForFinished... done!";
return success();
}
QVector<Compress::FileInformations> Compress::directoryFileList(const QString &dir, bool recursive)
{
DEBUG_LOG(m_logger) << "directoryFileList";
QVector<FileInformations> sl_absolute;
QDir d(dir);
if (d.exists())
{
DEBUG_LOG(m_logger) << "exists";
const auto entries = d.entryList(QDir::Files);
sl_absolute.reserve(sl_absolute.size() + entries.size());
for (const QString &entry : entries)
sl_absolute << FileInformations(QFileInfo(d, entry).absoluteFilePath(), true);
}
if (recursive)
{
DEBUG_LOG(m_logger) << "recursive";
const auto entries = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
sl_absolute.reserve(sl_absolute.size() + entries.size());
for (const QString &s : entries)
sl_absolute << directoryFileList(dir + QLatin1Char('/') + s, true);
}
DEBUG_LOG(m_logger) << "done";
return sl_absolute;
}
void Compress::reset()
{
DEBUG_LOG(m_logger) << "reset";
m_u2_memFiles.clear();
m_lastError.clear();
m_c_sizeInfo.realSize = 0;
m_c_sizeInfo.compressedSize = 0;
m_success = false;
}
void Compress::setupFutureWatcher(const QFuture<bool> &future)
{
DEBUG_LOG(m_logger) << "setupFutureWatcher";
m_future = future;
QFutureWatcher<bool> *fw = new QFutureWatcher<bool>(this);
QObject::connect(fw, &QObject::destroyed, this, [this]() { DEBUG_LOG(m_logger) << "Future Watcher destroyed"; });
QObject::connect(fw, &QFutureWatcher<bool>::finished, this, &Compress::finished);
QObject::connect(fw, &QFutureWatcher<bool>::finished, fw, &QObject::deleteLater);
QObject::connect(fw, &QFutureWatcher<bool>::finished, fw, [this]() { DEBUG_LOG(m_logger) << "Future Watcher about to be deleted"; });
fw->setFuture(m_future);
}
}
@@ -0,0 +1,140 @@
#pragma once
#include "nglcomp_export.h"
#include <NGLLogger/INGLLogger.h>
#include <QByteArray>
#include <QFuture>
#include <QObject>
#include <QString>
#include <QVector>
class QIODevice;
class TarFilter;
class BZip2IODevice;
namespace NGL
{
class NGLCOMPRESSION_EXPORT Compress : public QObject
{
Q_OBJECT
public:
struct FileInformations
{
QString inputFileName;
QString outputFileName;
QByteArray fileData;
bool bAbsInputPath = true;
FileInformations() = default;
FileInformations(const QString &name, bool isAbsolute)
: inputFileName(name)
, bAbsInputPath(isAbsolute)
{}
};
struct FileSizeInfo
{
qint64 realSize = 0;
qint64 compressedSize = 0;
};
explicit Compress(QObject *parent = nullptr);
~Compress();
QVector<FileInformations> getMemFiles() const { return m_u2_memFiles; }
FileSizeInfo getFileSizeInfo() const { return m_c_sizeInfo; }
bool success() const;
void compress(QIODevice *out,
const QString &sourceRootDir,
const QVector<FileInformations> &files,
const QVector<FileInformations> &memFiles,
const QString &destRootDir);
void compress(QIODevice *out,
const QString &sourceDir,
const QString &destRootDir);
void uncompress(QIODevice *in,
const QString &outDir);
void uncompress(QIODevice *in);
QString lastError() const { return m_lastError; }
bool waitForFinished();
public Q_SLOTS:
void abort();
Q_SIGNALS:
void uncompressInfo(qint64 totalSize, qint64 curPos, const QString &curName);
void compressInfo(qint64 totalFiles, qint64 curFile, const QString &curName);
void compressInfoEx(qint64 totalFiles, qint64 curFile, qint64 totalSize, qint64 curPos, const QString &curName, const qint64 bytesTotal);
void error(const QString &errorString);
void finished();
private:
bool _compress(QIODevice *output,
const QString &filesRootDir,
const QVector<FileInformations> &files,
const QVector<FileInformations> &memFiles,
const QString &_destRootDir);
bool _uncompressDirectory(QIODevice *input, const QString &outDir);
bool _uncompressBuffer(QIODevice *input);
void reset();
void setupFutureWatcher(const QFuture<bool> &future);
QVector<FileInformations> directoryFileList(const QString &dir, bool recursive = false);
FileSizeInfo m_c_sizeInfo;
QVector<FileInformations> m_u2_memFiles;
QString m_lastError;
QAtomicInt m_aborted;
TarFilter *m_currentTarFilter { nullptr };
BZip2IODevice *m_currentBzipDevice { nullptr };
QFuture<bool> m_future;
bool m_success { false };
NGL::INGLLogger &m_logger;
};
}
Q_DECLARE_TYPEINFO(NGL::Compress::FileInformations, Q_MOVABLE_TYPE);
@@ -0,0 +1,17 @@
#pragma once
#include <QtCore/qglobal.h>
#ifndef NGLCOMPRESSION_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define NGLCOMPRESSION_EXPORT
# else
# if defined(MAKE_NGLCOMPRESSION_LIB)
# define NGLCOMPRESSION_EXPORT Q_DECL_EXPORT
# else
# define NGLCOMPRESSION_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
@@ -0,0 +1,522 @@
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include <QFileInfo>
#include <QDateTime>
// Use UTF-8 conversions instead of QTextCodec (Qt6)
#include <time.h>
#include "nglcomp_tarfilter.h"
#ifndef Q_OS_WIN
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#endif
class TarFilter::Private
{
public:
Private(QIODevice *dev)
: device(dev)
, iBufSize(1024 * 1024)
, nextChunkSize(-1)
{}
struct tar_posix_header
{
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char unused[12];
};
bool fi2tph(const FileInformations &infos);
bool addHeader(const FileInformations &infos);
bool tph2fi(FileInformations &infos);
bool getHeader(FileInformations &infos);
bool checkDevice(QIODevice *in, QIODevice *out);
bool copyData(QIODevice *in, QIODevice *out, qint64 iSize);
QIODevice *device;
QString lastError;
unsigned int iBufSize;
qint64 nextChunkSize;
QAtomicInt aborted { 0 };
std::function<void(qint64)> readCallback;
};
bool TarFilter::Private::fi2tph(const FileInformations &infos)
{
QByteArray fn = infos.fileName.toUtf8();
#ifdef Q_OS_WIN
if (fn.size() > 3 && fn[1] == ':' && (fn[2] == '\\' || fn[2] == '/'))
fn = fn.mid(3);
#endif
if (fn.startsWith('/'))
fn = fn.mid(1);
long unsigned int fileSize = infos.fileType == directory ? 0 : infos.fileSize;
long unsigned int mtime = infos.mtime != -1 ? infos.mtime : time(nullptr);
tar_posix_header hdr;
memset(&hdr, 0, sizeof(hdr));
QByteArray fn_part1 = fn.left(sizeof(hdr.name));
memcpy(hdr.name, fn_part1.constData(), fn_part1.size());
sprintf(hdr.mode, "%07x", 0x0fff & infos.mode);
sprintf(hdr.uid, "%07o", infos.uid);
sprintf(hdr.gid, "%07o", infos.gid);
sprintf(hdr.size, "%011lo", fileSize);
sprintf(hdr.mtime, "%011lo", mtime);
memset(hdr.chksum, ' ', sizeof(hdr.chksum));
hdr.typeflag = infos.fileType;
memcpy(hdr.magic, "ustar ",sizeof(hdr.magic) + sizeof(hdr.version));
#ifdef Q_OS_WIN
#else
{
QByteArray unameBa = infos.uname.toUtf8();
QByteArray gnameBa = infos.gname.toUtf8();
qstrncpy(hdr.uname, unameBa.constData(), sizeof(hdr.uname));
qstrncpy(hdr.gname, gnameBa.constData(), sizeof(hdr.gname));
}
#endif
unsigned int checksum = 0;
const unsigned char *ptr = reinterpret_cast<const unsigned char *>(&hdr);
for (unsigned i = 0; i < sizeof(hdr); i++)
checksum += ptr[i];
constexpr int octalMax = 01000000;
while (checksum > octalMax)
checksum -= octalMax;
sprintf(hdr.chksum, "%06o", checksum);
if (!device->write((const char *)&hdr, sizeof(hdr)))
{
lastError = QLatin1String("Error writing to QIODevice");
return false;
}
return true;
}
bool TarFilter::Private::addHeader(const FileInformations &infos)
{
QByteArray fn = infos.fileName.toUtf8();
#ifdef Q_OS_WIN
if (fn.size() > 3 && fn[1] == ':' && (fn[2] == '\\' || fn[2] == '/'))
fn = fn.mid(3);
#endif
if (fn.startsWith('/'))
fn = fn.mid(1);
if (fn.size() > 512)
{
lastError = QLatin1String("Can't handle filenames with more then 512 bytes");
return false;
}
if (fn.size() > 100)
{
FileInformations longlink;
longlink.fileName = QLatin1String("././@LongLink");
longlink.fileType = gnu_longname;
longlink.fileSize = strlen(fn.constData()) + 1;
longlink.mtime = 0;
longlink.uid = 0;
longlink.gid = 0;
#ifndef Q_OS_WIN
struct passwd *p = getpwuid(longlink.uid);
if (p)
longlink.uname = QString::fromUtf8(p->pw_name);
struct group *g = getgrgid(longlink.gid);
if (g)
longlink.gname = QString::fromUtf8(g->gr_name);
#endif
fi2tph(longlink);
QByteArray ba(512, '\0');
qstrncpy(ba.data(), fn.constData(), ba.size());
if (!device->write(ba.constData(), ba.size()))
{
lastError = QLatin1String("Error writing to QIODevice");
return false;
}
}
if (!fi2tph(infos))
return false;
return true;
}
bool TarFilter::Private::tph2fi(FileInformations &infos)
{
Private::tar_posix_header hdr;
do
{
int iBytesRead = device->read((char *)&hdr, sizeof(hdr));
if (iBytesRead != sizeof(hdr))
{
if (iBytesRead == 0 && device->atEnd())
{
lastError = QString();
return false;
}
lastError = QStringLiteral("Error reading from QIODevice! needed: %1, got: %2").arg(sizeof(hdr)).arg(iBytesRead);
return false;
}
}
while (memcmp(hdr.magic, "\0\0\0\0\0\0", sizeof(hdr.magic)) == 0);
if (memcmp(hdr.magic, "ustar ", sizeof(hdr.magic)))
{
lastError = QLatin1String("The archive is not supported");
return false;
}
int checksum = QByteArray(hdr.chksum, sizeof(hdr.chksum)).toUInt(nullptr, 8);
memset(hdr.chksum, ' ', sizeof(hdr.chksum));
int realChecksum = 0;
const unsigned char *ptr = reinterpret_cast<const unsigned char *>(&hdr);
for (unsigned i = 0; i < sizeof(hdr); i++)
realChecksum += ptr[i];
if (checksum != realChecksum)
{
lastError = QStringLiteral("Checksum does not match! needed: %1, got: %2").arg(checksum).arg(realChecksum);
return false;
}
QByteArray ba(hdr.name, sizeof(hdr.name));
ba.resize(qstrlen(ba.constData()));
infos.fileName = QString::fromUtf8(ba.constData());
infos.fileSize = QByteArray(hdr.size, sizeof(hdr.size)).toUInt(nullptr, 8);
infos.mode = static_cast<QFile::Permissions>(QByteArray(hdr.mode, sizeof(hdr.mode)).toUInt(nullptr, 16));
infos.uid = QByteArray(hdr.uid, sizeof(hdr.uid)).toUInt(nullptr, 8);
infos.gid = QByteArray(hdr.gid, sizeof(hdr.gid)).toUInt(nullptr, 8);
infos.mtime = QByteArray(hdr.mtime, sizeof(hdr.mtime)).toUInt(nullptr, 8);
infos.fileType = (FileType)hdr.typeflag;
infos.uname = QString::fromUtf8(QByteArray(hdr.uname, sizeof(hdr.uname)).constData());
infos.gname = QString::fromUtf8(QByteArray(hdr.gname, sizeof(hdr.gname)).constData());
return true;
}
bool TarFilter::Private::getHeader(FileInformations &infos)
{
if (!tph2fi(infos))
return false;
switch (infos.fileType)
{
case regular:
case regular2:
case directory:
case gnu_longname:
break;
default:
lastError = QStringLiteral("Unsupported fileType %1").arg(infos.fileType);
return false;
}
if (infos.fileType == gnu_longname)
{
const QByteArray fn = device->read(512);
if (fn.size() != 512)
{
lastError = QLatin1String("Unexpected end of inputstream");
return false;
}
if (!tph2fi(infos))
return false;
const int len = qstrlen(fn.constData());
infos.fileName = QString::fromUtf8(fn.constData(), len);
}
return true;
}
bool TarFilter::Private::checkDevice(QIODevice *in, QIODevice *out)
{
if (!in)
{
lastError = QLatin1String("No QIODevice for reading available");
return false;
}
if ((in->openMode() & QIODevice::ReadOnly) == 0)
{
lastError = QLatin1String("QIODevice not opened for reading");
return false;
}
if (!out)
{
lastError = QLatin1String("No QIODevice for writing available");
return false;
}
if ((out->openMode() & QIODevice::WriteOnly) == 0)
{
lastError = QLatin1String("QIODevice not opened for writing");
return false;
}
return true;
}
bool TarFilter::Private::copyData(QIODevice *in, QIODevice *out, qint64 iSize)
{
if (iSize <= 0)
return true;
while (iSize > 0)
{
if (aborted) return false;
const qint64 iBytesToRead = iSize > iBufSize ? iBufSize : iSize;
const QByteArray ba = in->read(iBytesToRead);
if (aborted) return false;
if (ba.size() == 0)
{
lastError = QStringLiteral("Could not read %1 bytes from QIODevice").arg(iBytesToRead);
return false;
}
if (out->write(ba) != ba.size())
{
lastError = QStringLiteral("Could not write %1 bytes to QIODevice").arg(ba.size());
return false;
}
iSize -= ba.size();
if (readCallback)
readCallback(iSize);
}
return true;
}
TarFilter::TarFilter(QIODevice *dev, const std::function<void(qint64)> &readCallback)
: d(new Private(dev))
{
d->readCallback = readCallback;
}
TarFilter::~TarFilter()
{
delete d;
}
void TarFilter::abort()
{
d->aborted = 1;
}
void TarFilter::setBufferSize(unsigned int size)
{
d->iBufSize = size > 1024 ? size : 1024;
}
bool TarFilter::error() const
{
return !d->lastError.isEmpty();
}
QString TarFilter::lastError() const
{
return d->lastError;
}
bool TarFilter::addFile(const QString &filename, const QString &filenameInArchive)
{
QFileInfo fi(filename);
if (!fi.exists())
{
d->lastError = QStringLiteral("File %1 does not exist").arg(filename);
return false;
}
if (!(fi.isDir() || fi.isFile()))
{
d->lastError = QStringLiteral("%1 isn't a directory or a regular file").arg(filename);
return false;
}
FileInformations fInfos;
fInfos.fileName = (filenameInArchive.isEmpty() ? filename : filenameInArchive);
fInfos.fileSize = fi.isDir() ? 0 : fi.size();
fInfos.mode = fi.permissions() & 0x0fff;
#ifdef Q_OS_WIN
fInfos.uid = 1000;
fInfos.gid = 100;
#else
fInfos.uid = fi.ownerId();
fInfos.gid = fi.groupId();
fInfos.uname = fi.owner();
fInfos.gname = fi.group();
#endif
fInfos.mtime = fi.lastModified().toSecsSinceEpoch();
fInfos.fileType = fi.isDir() ? directory : regular;
if (fi.isDir())
{
QBuffer buf;
buf.open(QIODevice::ReadOnly);
return addData(fInfos, &buf);
}
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
{
d->lastError = QStringLiteral("Error opening %1 for reading").arg(filename);
return false;
}
if (d->aborted) return false;
return addData(fInfos, &f);
}
bool TarFilter::addData(const QString &filename, const QByteArray &data, bool bIsDir)
{
if (d->aborted) return false;
FileInformations fi;
fi.fileName = filename;
fi.fileSize = bIsDir ? 0 : data.size();
fi.mode = static_cast<QFile::Permissions>(bIsDir ? 0x0755 : 0x0644);
fi.fileType = bIsDir ? directory : regular;
#ifndef Q_OS_WIN
fi.uid = getuid();
fi.gid = getgid();
char name[256];
struct passwd *p_ptr = nullptr;
struct passwd p;
getpwuid_r(fi.uid, &p, name, sizeof(name) - 1, &p_ptr);
if (p_ptr)
fi.uname = QString::fromUtf8(p_ptr->pw_name);
struct group *g_ptr;
struct group g;
getgrgid_r(fi.gid, &g, name, sizeof(name) - 1, &g_ptr);
if (g_ptr)
fi.gname = QString::fromUtf8(g_ptr->gr_name);
#endif
QBuffer buf(const_cast<QByteArray *>(&data));
if (!buf.open(QIODevice::ReadOnly))
{
d->lastError = QLatin1String("Internal Error - can't open QBuffer for reading!");
return false;
}
return addData(fi, &buf);
}
bool TarFilter::addData(const FileInformations &fi, QIODevice *in)
{
if (d->aborted) return false;
if (!d->checkDevice(in, d->device))
return false;
if (!d->addHeader(fi))
return false;
if (fi.fileSize)
{
if (!d->copyData(in, d->device, fi.fileSize))
return false;
int align = fi.fileSize % 512;
if (align)
{
align = 512 - align;
QByteArray ba2(align, '\0');
d->device->write(ba2.constData(), align);
}
}
return true;
}
bool TarFilter::writeEOS()
{
const int size = sizeof(TarFilter::Private::tar_posix_header) * 2;
QByteArray ba(size, '\0');
return (d->device->write(ba) == size);
}
bool TarFilter::getData(FileInformations &fi)
{
if (d->aborted) return false;
d->nextChunkSize = -1;
if (!d->getHeader(fi))
return false;
d->nextChunkSize = fi.fileSize;
return true;
}
bool TarFilter::getData(QIODevice *out)
{
if (d->aborted) return false;
if (!d->checkDevice(d->device, out))
return false;
if (d->nextChunkSize < 0)
{
d->lastError = QLatin1String("No informations about next chunk size. Did you call getData(FileInformations &fi) before?");
return false;
}
if (!d->copyData(d->device, out, d->nextChunkSize))
return false;
int align = d->nextChunkSize % 512;
if (align)
d->device->read(512 - align);
d->nextChunkSize = -1;
return true;
}
bool TarFilter::getData(FileInformations &fi, QByteArray &data)
{
if (!getData(fi))
return false;
QBuffer buf(&data);
buf.open(QIODevice::WriteOnly);
return getData(&buf);
}
bool TarFilter::getData(FileInformations &fi, QIODevice *out)
{
if (!getData(fi))
return false;
return getData(out);
}
@@ -0,0 +1,80 @@
#ifndef TARFILTER_H
#define TARFILTER_H
#include <QString>
#include <QFile>
#include <functional>
class QIODevice;
class QByteArray;
class TarFilter
{
public:
Q_DISABLE_COPY(TarFilter)
enum FileType
{
regular = '0',
regular2 = '\0',
link = '1',
reserved = '2',
charType = '3',
block = '4',
directory = '5',
fifo = '6',
reserved2 = '7',
gnu_longLink = 'K',
gnu_longname = 'L',
};
struct FileInformations
{
QString fileName;
qint64 fileSize = 0;
int uid = 1000;
int gid = 100;
QString uname;
QString gname;
time_t mtime = -1;
FileType fileType = regular;
QFile::Permissions mode = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther;
};
TarFilter(QIODevice *dev, const std::function<void(qint64)> &readCallback = nullptr);
~TarFilter();
void abort();
void setBufferSize(unsigned int size);
bool error() const;
QString lastError() const;
bool addData(const FileInformations &fi, QIODevice *in);
bool addFile(const QString &filename, const QString &filenameInArchive = QString());
bool addData(const QString &filename, const QByteArray &data = QByteArray(), bool bIsDir = false);
bool writeEOS();
bool getData(FileInformations &fi, QIODevice *out);
bool getData(FileInformations &fi);
bool getData(QIODevice *out);
bool getData(FileInformations &fi, QByteArray &data);
private:
class Private;
Private *const d;
};
#endif
@@ -0,0 +1,175 @@
#include "nglcomp_test.h"
#include "nglcomp_compress.h"
#include <ngltools/ngltools_filesystem.h>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QtTest>
void nglcomp_test::initTestCase()
{
m_tempDirIn.setAutoRemove(true);
m_tempDirOut.setAutoRemove(true);
QVERIFY(m_tempDirIn.isValid());
QVERIFY(m_tempDirOut.isValid());
QVERIFY(m_tf.open());
m_tf.setAutoRemove(true);
m_fileContent[QLatin1String("1.txt")] = "111111111111111111111111";
m_fileContent[QLatin1String("sub1/s1.txt")] = "222222222222222222222222";
m_fileContent[QLatin1String("sub1/sub11/s11.txt")] = "333333333333333333333333";
m_fileContent[QLatin1String("sub1/sub12/s12.txt")] = "444444444444444444444444";
m_fileContent[QLatin1String("sub2/2.txt")] = "555555555555555555555555";
}
void nglcomp_test::testCompressFile()
{
QDir dir;
const auto directories = {
QLatin1String("sub1"),
QLatin1String("sub1/sub11"),
QLatin1String("sub1/sub12"),
QLatin1String("sub2")
};
for (const QLatin1String &directory : directories)
QVERIFY(dir.mkdir(subPathIn(directory)));
for (auto it = m_fileContent.cbegin(); it != m_fileContent.cend(); ++it)
writeTestFile(it.key(), it.value());
NGL::Compress c;
c.compress(&m_tf, subPathIn(QString()), QLatin1String("out"));
QVERIFY(c.waitForFinished());
}
void nglcomp_test::testCompressMemory()
{
QBuffer f(&m_ba);
QVERIFY(f.open(QIODevice::WriteOnly));
QVector<NGL::Compress::FileInformations> fi1;
QVector<NGL::Compress::FileInformations> fi2;
NGL::Compress::FileInformations fi;
fi.inputFileName = QLatin1String("chris.txt");
fi.fileData = QByteArray("dumdidum\n");
fi2 += fi;
fi.inputFileName = QLatin1String("blub/chris.txt");
fi.fileData = QByteArray("dumdidum im Verzeichnis blub\n");
fi2 += fi;
fi.inputFileName = QLatin1String("blub/blub/chris2.txt");
fi.fileData = QByteArray("dumdidum im Verzeichnis blub/blub\n");
fi2 += fi;
NGL::Compress c;
c.compress(&f, QString(), fi1, fi2, QLatin1String("blub"));
QVERIFY(c.waitForFinished());
f.close();
}
void nglcomp_test::testUncompressMemory()
{
QBuffer f(&m_ba);
QVERIFY(f.open(QIODevice::ReadOnly));
NGL::Compress c;
c.uncompress(&f, subPathIn(QString()));
QVERIFY(c.waitForFinished());
f.close();
QVERIFY(f.open(QIODevice::ReadOnly));
c.uncompress(&f);
QVERIFY(c.waitForFinished());
QVector<NGL::Compress::FileInformations> fi1 = c.getMemFiles();
QVERIFY(fi1.count() == 4);
f.close();
}
void nglcomp_test::testUncompressFile()
{
NGL::Compress c;
m_tf.seek(0);
c.uncompress(&m_tf, subPathOut(QString()));
QVERIFY(c.waitForFinished());
for (auto it = m_fileContent.cbegin(); it != m_fileContent.cend(); ++it)
checkContent(it.key(), it.value());
}
void nglcomp_test::cleanupTestCase()
{
m_tf.close();
m_tempDirIn.remove();
m_tempDirOut.remove();
}
void nglcomp_test::writeTestFile(const QString &fn, const QByteArray &content) const
{
QFile f(subPathIn(fn));
QVERIFY(f.open(QIODevice::WriteOnly));
QVERIFY(f.write(content) == content.size());
f.close();
}
void nglcomp_test::checkContent(const QString &fn, const QByteArray &content) const
{
QFile f(subPathOut(QLatin1String("out/") + fn));
QVERIFY(f.open(QIODevice::ReadOnly));
QCOMPARE(f.readAll(), content);
}
QString nglcomp_test::subPathIn(const QString &p) const
{
return m_tempDirIn.path() + QLatin1Char('/') + p;
}
QString nglcomp_test::subPathOut(const QString &p) const
{
return m_tempDirOut.path() + QLatin1Char('/') + p;
}
int main(int argc, char *argv[])
{
NGLCoreApplication app(argc, argv);
app.createLogger("nglcomp_test", "log4cxx_nglcomp_test.props");
nglcomp_test tc;
return QTest::qExec(&tc, argc, argv);
}
@@ -0,0 +1,36 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QObject>
#include <QTemporaryDir>
#include <QTemporaryFile>
class nglcomp_test : public QObject
{
Q_OBJECT
public:
nglcomp_test() = default;
private Q_SLOTS:
void initTestCase();
void testCompressFile();
void testCompressMemory();
void testUncompressFile();
void testUncompressMemory();
void cleanupTestCase();
private:
void writeTestFile(const QString &fn, const QByteArray &content) const;
void checkContent(const QString &fn, const QByteArray &content) const;
QString subPathIn(const QString &p) const;
QString subPathOut(const QString &p) const;
private:
QHash<QString, QByteArray> m_fileContent;
QByteArray m_ba;
QTemporaryFile m_tf;
QTemporaryDir m_tempDirIn;
QTemporaryDir m_tempDirOut;
};