6 #include "staticcompressed_p.h" 8 #include <Cutelyst/Application> 9 #include <Cutelyst/Context> 10 #include <Cutelyst/Engine> 11 #include <Cutelyst/Request> 12 #include <Cutelyst/Response> 16 #include <QCoreApplication> 17 #include <QCryptographicHash> 18 #include <QDataStream> 22 #include <QLoggingCategory> 23 #include <QMimeDatabase> 24 #include <QStandardPaths> 26 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 27 # include <brotli/encode.h> 32 Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
34 StaticCompressed::StaticCompressed(
Application *parent)
36 , d_ptr(new StaticCompressedPrivate)
39 d->includePaths.append(
parent->config(u
"root"_qs).toString());
42 StaticCompressed::StaticCompressed(
Application *parent,
const QVariantMap &defaultConfig)
44 , d_ptr(new StaticCompressedPrivate)
47 d->includePaths.append(
parent->config(u
"root"_qs).toString());
48 d->defaultConfig = defaultConfig;
51 StaticCompressed::~StaticCompressed() =
default;
53 void StaticCompressed::setIncludePaths(
const QStringList &paths)
56 d->includePaths.clear();
57 for (
const QString &path : paths) {
58 d->includePaths.append(
QDir(path));
62 void StaticCompressed::setDirs(
const QStringList &dirs)
68 void StaticCompressed::setServeDirsOnly(
bool dirsOnly)
71 d->serveDirsOnly = dirsOnly;
78 const QVariantMap config = app->
engine()->
config(u
"Cutelyst_StaticCompressed_Plugin"_qs);
79 const QString _defaultCacheDir =
82 d->cacheDir.setPath(config
83 .value(u
"cache_directory"_qs,
84 d->defaultConfig.value(u
"cache_directory"_qs, _defaultCacheDir))
87 if (Q_UNLIKELY(!d->cacheDir.exists())) {
88 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
89 qCCritical(C_STATICCOMPRESSED)
90 <<
"Failed to create cache directory for compressed static files at" 91 << d->cacheDir.absolutePath();
96 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
100 .value(u
"mime_types"_qs,
101 d->defaultConfig.value(u
"mime_types"_qs,
102 u
"text/css,application/javascript,text/javascript"_qs))
104 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
111 d->defaultConfig.value(u
"suffixes"_qs, u
"js.map,css.map,min.js.map,min.css.map"_qs))
113 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
116 d->checkPreCompressed = config
117 .
value(u
"check_pre_compressed"_qs,
118 d->defaultConfig.value(u
"check_pre_compressed"_qs,
true))
120 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
122 d->onTheFlyCompression = config
123 .value(u
"on_the_fly_compression"_qs,
124 d->defaultConfig.value(u
"on_the_fly_compression"_qs,
true))
126 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
128 QStringList supportedCompressions{u
"deflate"_qs, u
"gzip"_qs};
129 d->loadZlibConfig(config);
131 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 132 d->loadZopfliConfig(config);
133 qCInfo(C_STATICCOMPRESSED) <<
"Use Zopfli:" << d->useZopfli;
136 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 137 d->loadBrotliConfig(config);
138 supportedCompressions << u
"br"_qs;
141 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 142 if (Q_UNLIKELY(!d->loadZstdConfig(config))) {
145 supportedCompressions << u
"zstd"_qs;
149 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 152 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 160 .
value(u
"compression_format_order"_qs,
161 d->defaultConfig.value(u
"compression_format_order"_qs,
162 defaultCompressionFormatOrder.join(u
',')))
165 if (Q_UNLIKELY(_compressionFormatOrder.
empty())) {
166 _compressionFormatOrder = defaultCompressionFormatOrder;
167 qCWarning(C_STATICCOMPRESSED)
168 <<
"Invalid or empty value for compression_format_order. Has to be a string list " 169 "containing supported values. Using default value" 170 << defaultCompressionFormatOrder.
join(u
',');
172 for (
const auto &cfo : std::as_const(_compressionFormatOrder)) {
174 if (supportedCompressions.contains(order)) {
175 d->compressionFormatOrder << order;
178 if (Q_UNLIKELY(d->compressionFormatOrder.empty())) {
179 d->compressionFormatOrder = defaultCompressionFormatOrder;
180 qCWarning(C_STATICCOMPRESSED)
181 <<
"Invalid or empty value for compression_format_order. Has to be a string list " 182 "containing supported values. Using default value" 183 << defaultCompressionFormatOrder.join(u
',');
186 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.join(u
',');
187 qCInfo(C_STATICCOMPRESSED) <<
"Compression format order:" 188 << d->compressionFormatOrder.join(u
',');
189 qCInfo(C_STATICCOMPRESSED) <<
"Include paths:" << d->includePaths;
192 d->beforePrepareAction(c, skipMethod);
198 void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
207 for (
const QString &dir : std::as_const(dirs)) {
209 if (!locateCompressedFile(c, path)) {
213 res->
setBody(u
"File not found: "_qs + path);
227 if (match.
hasMatch() && locateCompressedFile(c, path)) {
232 bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const 234 for (
const QDir &includePath : includePaths) {
235 qCDebug(C_STATICCOMPRESSED)
236 <<
"Trying to find" << relPath <<
"in" << includePath.absolutePath();
237 const QString path = includePath.absoluteFilePath(relPath);
239 if (fileInfo.exists()) {
241 const QDateTime currentDateTime = fileInfo.lastModified();
261 _mimeTypeName =
"application/json"_qba;
268 const auto acceptEncoding = c->
req()->
header(
"Accept-Encoding");
270 for (
const QString &format : std::as_const(compressionFormatOrder)) {
271 if (!acceptEncoding.contains(format.toLatin1())) {
274 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 276 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
277 if (compressedPath.
isEmpty()) {
280 qCDebug(C_STATICCOMPRESSED)
281 <<
"Serving brotli compressed data from" << compressedPath;
282 contentEncoding =
"br"_qba;
287 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 289 compressedPath = locateCacheFile(path, currentDateTime, Zstd);
290 if (compressedPath.
isEmpty()) {
293 qCDebug(C_STATICCOMPRESSED)
294 <<
"Serving zstd compressed data from" << compressedPath;
295 contentEncoding =
"zstd"_qba;
301 compressedPath = locateCacheFile(
302 path, currentDateTime, useZopfli ? ZopfliGzip : Gzip);
303 if (compressedPath.
isEmpty()) {
306 qCDebug(C_STATICCOMPRESSED)
307 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
308 <<
"compressed gzip data from" << compressedPath;
309 contentEncoding =
"gzip"_qba;
313 compressedPath = locateCacheFile(
314 path, currentDateTime, useZopfli ? ZopfliDeflate : Deflate);
315 if (compressedPath.
isEmpty()) {
318 qCDebug(C_STATICCOMPRESSED)
319 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
320 <<
"compressed deflate data from" << compressedPath;
321 contentEncoding =
"deflate"_qba;
333 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
341 if (!_mimeTypeName.
isEmpty()) {
343 }
else if (mimeType.
isValid()) {
352 if (!contentEncoding.
isEmpty()) {
356 qCDebug(C_STATICCOMPRESSED)
358 <<
"Original Size:" << fileInfo.size();
361 headers.
pushHeader(
"Vary"_qba,
"Accept-Encoding"_qba);
367 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
373 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
377 QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
379 Compression compression)
const 385 switch (compression) {
390 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 395 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 402 suffix = u
".deflate"_qs;
405 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
409 if (checkPreCompressed) {
410 const QFileInfo origCompressed(origPath + suffix);
411 if (origCompressed.exists()) {
412 compressedPath = origCompressed.absoluteFilePath();
413 return compressedPath;
417 if (onTheFlyCompression) {
419 const QString path = cacheDir.absoluteFilePath(
425 if (info.exists() && (info.lastModified() > origLastModified)) {
426 compressedPath = path;
429 if (lock.tryLock(std::chrono::milliseconds{10})) {
430 switch (compression) {
431 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 433 if (compressZstd(origPath, path)) {
434 compressedPath = path;
438 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 440 if (compressBrotli(origPath, path)) {
441 compressedPath = path;
446 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 447 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_GZIP)) {
448 compressedPath = path;
453 if (compressGzip(origPath, path, origLastModified)) {
454 compressedPath = path;
458 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 459 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_ZLIB)) {
460 compressedPath = path;
465 if (compressDeflate(origPath, path)) {
466 compressedPath = path;
477 return compressedPath;
480 void StaticCompressedPrivate::loadZlibConfig(
const QVariantMap &conf)
483 zlib.compressionLevel =
484 conf.value(u
"zlib_compression_level"_qs,
485 defaultConfig.value(u
"zlib_compression_level"_qs, zlib.compressionLevelDefault))
488 if (!ok || zlib.compressionLevel < zlib.compressionLevelMin ||
489 zlib.compressionLevel > zlib.compressionLevelMax) {
490 qCWarning(C_STATICCOMPRESSED).nospace()
491 <<
"Invalid value set for zlib_compression_level. Value hat to be between " 492 << zlib.compressionLevelMin <<
" and " << zlib.compressionLevelMax
493 <<
" inclusive. Using default value " << zlib.compressionLevelDefault;
494 zlib.compressionLevel = zlib.compressionLevelDefault;
498 static constexpr std::array<quint32, 256> crc32Tab = []() {
499 std::array<quint32, 256> tab{0};
500 for (std::size_t n = 0; n < 256; n++) {
501 auto c =
static_cast<quint32
>(n);
502 for (
int k = 0; k < 8; k++) {
504 c = 0xedb88320L ^ (c >> 1);
514 quint32 updateCRC32(
unsigned char ch, quint32 crc)
517 return crc32Tab[(crc ^ ch) & 0xff] ^ (crc >> 8);
522 return ~
std::accumulate(data.
begin(),
525 [](quint32 oldcrc32,
char buf) {
526 return updateCRC32(static_cast<unsigned char>(buf), oldcrc32);
530 bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
534 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
536 QFile input(inputPath);
538 qCWarning(C_STATICCOMPRESSED)
539 <<
"Can not open input file to compress with gzip:" << inputPath;
544 if (Q_UNLIKELY(data.
isEmpty())) {
545 qCWarning(C_STATICCOMPRESSED)
546 <<
"Can not read input file or input file is empty:" << inputPath;
551 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
554 QFile output(outputPath);
556 qCWarning(C_STATICCOMPRESSED)
557 <<
"Can not open output file to compress with gzip:" << outputPath;
561 if (Q_UNLIKELY(compressedData.
isEmpty())) {
562 qCWarning(C_STATICCOMPRESSED)
563 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
564 if (output.exists()) {
565 if (Q_UNLIKELY(!output.remove())) {
566 qCWarning(C_STATICCOMPRESSED)
567 <<
"Can not remove invalid compressed gzip file:" << outputPath;
575 compressedData.
remove(0, 6);
576 compressedData.
chop(4);
582 headerStream << quint8(0x1f) << quint8(0x8b)
587 #if defined Q_OS_UNIX 589 #elif defined Q_OS_MACOS 591 #elif defined Q_OS_WIN 600 auto crc = crc32buf(data);
601 auto inSize = data.
size();
604 footerStream << static_cast<quint8>(crc % 256) << static_cast<quint8>((crc >> 8) % 256)
605 << static_cast<quint8>((crc >> 16) % 256) << static_cast<quint8>((crc >> 24) % 256)
606 << static_cast<quint8>(inSize % 256) <<
static_cast<quint8
>((inSize >> 8) % 256)
607 <<
static_cast<quint8
>((inSize >> 16) % 256)
608 <<
static_cast<quint8
>((inSize >> 24) % 256);
610 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
611 qCCritical(C_STATICCOMPRESSED).nospace()
612 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
619 bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
620 const QString &outputPath)
const 622 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
624 QFile input(inputPath);
626 qCWarning(C_STATICCOMPRESSED)
627 <<
"Can not open input file to compress with deflate:" << inputPath;
632 if (Q_UNLIKELY(data.
isEmpty())) {
633 qCWarning(C_STATICCOMPRESSED)
634 <<
"Can not read input file or input file is empty:" << inputPath;
639 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
642 QFile output(outputPath);
644 qCWarning(C_STATICCOMPRESSED)
645 <<
"Can not open output file to compress with deflate:" << outputPath;
649 if (Q_UNLIKELY(compressedData.
isEmpty())) {
650 qCWarning(C_STATICCOMPRESSED)
651 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
652 if (output.exists()) {
653 if (Q_UNLIKELY(!output.remove())) {
654 qCWarning(C_STATICCOMPRESSED)
655 <<
"Can not remove invalid compressed deflate file:" << outputPath;
662 compressedData.
remove(0, 4);
664 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
665 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file " 666 << inputPath <<
": " << output.errorString();
673 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 674 void StaticCompressedPrivate::loadZopfliConfig(
const QVariantMap &conf)
676 useZopfli = conf.value(u
"use_zopfli"_qs, defaultConfig.value(u
"use_zopfli"_qs,
false)).toBool();
678 ZopfliInitOptions(&zopfli.options);
680 zopfli.options.numiterations =
681 conf.value(u
"zopfli_iterations"_qs,
682 defaultConfig.value(u
"zopfli_iterations"_qs, zopfli.iterationsDefault))
684 if (!ok || zopfli.options.numiterations < zopfli.iterationsMin) {
685 qCWarning(C_STATICCOMPRESSED).nospace()
686 <<
"Invalid value set for zopfli_iterations. Value has to to be an integer value " 687 "greater than or equal to " 688 << zopfli.iterationsMin <<
". Using default value " << zopfli.iterationsDefault;
689 zopfli.options.numiterations = zopfli.iterationsDefault;
694 bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
696 ZopfliFormat format)
const 698 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
700 QFile input(inputPath);
702 qCWarning(C_STATICCOMPRESSED)
703 <<
"Can not open input file to compress with zopfli:" << inputPath;
708 if (Q_UNLIKELY(data.
isEmpty())) {
709 qCWarning(C_STATICCOMPRESSED)
710 <<
"Can not read input file or input file is empty:" << inputPath;
716 unsigned char *out{
nullptr};
719 ZopfliCompress(&zopfli.options,
721 reinterpret_cast<const unsigned char *>(data.
constData()),
726 if (Q_UNLIKELY(outSize <= 0)) {
727 qCWarning(C_STATICCOMPRESSED)
728 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
733 QFile output{outputPath};
735 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
736 <<
"for zopfli compression:" << output.
errorString();
741 if (Q_UNLIKELY(output.write(reinterpret_cast<const char *>(out), outSize) < 0)) {
742 if (output.exists()) {
743 if (Q_UNLIKELY(!output.remove())) {
744 qCWarning(C_STATICCOMPRESSED)
745 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
748 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zopfli compressed data to output file" 749 << outputPath <<
":" << output.errorString();
760 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 761 void StaticCompressedPrivate::loadBrotliConfig(
const QVariantMap &conf)
764 brotli.qualityLevel =
765 conf.value(u
"brotli_quality_level"_qs,
766 defaultConfig.value(u
"brotli_quality_level"_qs, brotli.qualityLevelDefault))
769 if (!ok || brotli.qualityLevel < BROTLI_MIN_QUALITY ||
770 brotli.qualityLevel > BROTLI_MAX_QUALITY) {
771 qCWarning(C_STATICCOMPRESSED).nospace()
772 <<
"Invalid value for brotli_quality_level. " 773 "Has to be an integer value between " 774 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
775 <<
" inclusive. Using default value " << brotli.qualityLevelDefault;
776 brotli.qualityLevel = brotli.qualityLevelDefault;
780 bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
781 const QString &outputPath)
const 783 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
785 QFile input(inputPath);
787 qCWarning(C_STATICCOMPRESSED)
788 <<
"Can not open input file to compress with brotli:" << inputPath;
793 if (Q_UNLIKELY(data.
isEmpty())) {
794 qCWarning(C_STATICCOMPRESSED)
795 <<
"Can not read input file or input file is empty:" << inputPath;
801 size_t outSize = BrotliEncoderMaxCompressedSize(static_cast<size_t>(data.
size()));
802 if (Q_UNLIKELY(outSize == 0)) {
803 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size" 804 << data.
size() <<
"with brotli";
807 QByteArray outData{
static_cast<qsizetype
>(outSize), Qt::Uninitialized};
809 const auto in =
reinterpret_cast<const uint8_t *
>(data.
constData());
810 auto out =
reinterpret_cast<uint8_t *
>(outData.data());
812 const BROTLI_BOOL status = BrotliEncoderCompress(brotli.qualityLevel,
813 BROTLI_DEFAULT_WINDOW,
819 if (Q_UNLIKELY(status != BROTLI_TRUE)) {
820 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
824 outData.
resize(static_cast<qsizetype>(outSize));
826 QFile output{outputPath};
828 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
829 <<
"for brotli compression:" << output.
errorString();
833 if (Q_UNLIKELY(output.write(outData) < 0)) {
834 if (output.exists()) {
835 if (Q_UNLIKELY(!output.remove())) {
836 qCWarning(C_STATICCOMPRESSED)
837 <<
"Can not remove invalid compressed brotli file:" << outputPath;
840 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write brotli compressed data to output file" 841 << outputPath <<
":" << output.errorString();
849 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 850 bool StaticCompressedPrivate::loadZstdConfig(
const QVariantMap &conf)
852 zstd.ctx = ZSTD_createCCtx();
854 qCCritical(C_STATICCOMPRESSED) <<
"Failed to create Zstandard compression context";
860 zstd.compressionLevel =
861 conf.value(u
"zstd_compression_level"_qs,
862 defaultConfig.value(u
"zstd_compression_level"_qs, zstd.compressionLevelDefault))
864 if (!ok || zstd.compressionLevel < ZSTD_minCLevel() ||
865 zstd.compressionLevel > ZSTD_maxCLevel()) {
866 qCWarning(C_STATICCOMPRESSED).nospace()
867 <<
"Invalid value for zstd_compression_level. Has to be an integer value between " 868 << ZSTD_minCLevel() <<
" and " << ZSTD_maxCLevel() <<
" inclusive. Using default value " 869 << zstd.compressionLevelDefault;
870 zstd.compressionLevel = zstd.compressionLevelDefault;
876 bool StaticCompressedPrivate::compressZstd(
const QString &inputPath,
877 const QString &outputPath)
const 879 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zstd to" << outputPath;
881 QFile input{inputPath};
883 qCWarning(C_STATICCOMPRESSED)
884 <<
"Can not open input file to compress with zstd:" << inputPath;
889 if (Q_UNLIKELY(inData.
isEmpty())) {
890 qCWarning(C_STATICCOMPRESSED)
891 <<
"Can not read input file or input file is empty:" << inputPath;
897 const size_t outBufSize = ZSTD_compressBound(static_cast<size_t>(inData.
size()));
898 if (Q_UNLIKELY(ZSTD_isError(outBufSize) == 1)) {
899 qCWarning(C_STATICCOMPRESSED)
900 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outBufSize);
903 QByteArray outData{
static_cast<qsizetype
>(outBufSize), Qt::Uninitialized};
905 auto outDataP =
static_cast<void *
>(outData.data());
906 auto inDataP =
static_cast<const void *
>(inData.
constData());
908 const size_t outSize = ZSTD_compressCCtx(
909 zstd.ctx, outDataP, outBufSize, inDataP, inData.
size(), zstd.compressionLevel);
910 if (Q_UNLIKELY(ZSTD_isError(outSize) == 1)) {
911 qCWarning(C_STATICCOMPRESSED)
912 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outSize);
916 outData.resize(static_cast<qsizetype>(outSize));
918 QFile output{outputPath};
920 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
921 <<
"for zstd compression:" << output.
errorString();
925 if (Q_UNLIKELY(output.write(outData) < 0)) {
926 if (output.exists()) {
927 if (Q_UNLIKELY(!output.remove())) {
928 qCWarning(C_STATICCOMPRESSED)
929 <<
"Can not remove invalid compressed zstd file:" << outputPath;
932 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zstd compressed data to output file" 933 << outputPath <<
":" << output.errorString();
941 #include "moc_staticcompressed.cpp" QByteArray header(QByteArrayView key) const noexcept
QString writableLocation(QStandardPaths::StandardLocation type)
Serve static files compressed on the fly or pre-compressed.
Headers & headers() noexcept
QString errorString() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
Response * res() const noexcept
bool isEmpty() const const
QString join(QChar separator) const const
void setContentType(const QByteArray &type)
T value(qsizetype i) const const
Headers headers() const noexcept
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
QVariantMap config(const QString &entity) const
void resize(qsizetype newSize, QChar fillChar)
bool isEmpty() const const
QString trimmed() const const
const char * constData() const const
QByteArray::iterator begin()
bool hasMatch() const const
The Cutelyst namespace holds all public Cutelyst API.
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, QMimeDatabase::MatchMode mode) const const
QString toLower() const const
virtual qint64 size() const const override
bool isValid() const const
QString fromLatin1(QByteArrayView str)
QString mid(qsizetype position, qsizetype n) const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
qint64 toSecsSinceEpoch() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray hash(QByteArrayView data, QCryptographicHash::Algorithm method)
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
Base class for Cutelyst Plugins.
The Cutelyst application.
Engine * engine() const noexcept
void setBody(QIODevice *body)
qsizetype size() const const
QObject * parent() const const
Response * response() const noexcept
QByteArray & remove(qsizetype pos, qsizetype len)
void setStatus(quint16 status) noexcept
QByteArray::iterator end()
QByteArray toUtf8() const const