Akonadi

compressionstream.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Daniel Vrátil <dvratil@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "akonadiprivate_debug.h"
8#include "compressionstream_p.h"
9
10#include <QByteArray>
11
12#include <lzma.h>
13
14#include <array>
15
16using namespace Akonadi;
17
18namespace
19{
20class LZMAErrorCategory : public std::error_category
21{
22public:
23 const char *name() const noexcept override
24 {
25 return "lzma";
26 }
27 std::string message(int ev) const noexcept override
28 {
29 switch (static_cast<lzma_ret>(ev)) {
30 case LZMA_OK:
31 return "Operation completed successfully";
32 case LZMA_STREAM_END:
33 return "End of stream was reached";
34 case LZMA_NO_CHECK:
35 return "Input stream has no integrity check";
36 case LZMA_UNSUPPORTED_CHECK:
37 return "Cannot calculate the integrity check";
38 case LZMA_GET_CHECK:
39 return "Integrity check type is now available";
40 case LZMA_MEM_ERROR:
41 return "Cannot allocate memory";
42 case LZMA_MEMLIMIT_ERROR:
43 return "Memory usage limit was reached";
44 case LZMA_FORMAT_ERROR:
45 return "File format not recognized";
46 case LZMA_OPTIONS_ERROR:
47 return "Invalid or unsupported options";
48 case LZMA_DATA_ERROR:
49 return "Data is corrupt";
50 case LZMA_BUF_ERROR:
51 return "No progress is possible";
52 case LZMA_PROG_ERROR:
53 return "Programming error";
54 }
55
56 Q_UNREACHABLE();
57 }
58};
59
60const LZMAErrorCategory &lzmaErrorCategory()
61{
62 static const LZMAErrorCategory lzmaErrorCategory{};
63 return lzmaErrorCategory;
64}
65
66} // namespace
67
68namespace std
69{
70template<>
71struct is_error_code_enum<lzma_ret> : std::true_type {
72};
73
74std::error_condition make_error_condition(lzma_ret ret)
75{
76 return std::error_condition(static_cast<int>(ret), lzmaErrorCategory());
77}
78
79} // namespace std
80
81std::error_code make_error_code(lzma_ret e)
82{
83 return {static_cast<int>(e), lzmaErrorCategory()};
84}
85
86class Akonadi::Compressor
87{
88public:
89 std::error_code initialize(QIODevice::OpenMode openMode)
90 {
91 if (openMode == QIODevice::ReadOnly) {
92 return lzma_auto_decoder(&mStream, 100 * 1024 * 1024 /* 100 MiB */, 0);
93 } else {
94 return lzma_easy_encoder(&mStream, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC32);
95 }
96 }
97
98 void setInputBuffer(const char *data, qint64 size)
99 {
100 mStream.next_in = reinterpret_cast<const uint8_t *>(data);
101 mStream.avail_in = size;
102 }
103
104 void setOutputBuffer(char *data, qint64 maxSize)
105 {
106 mStream.next_out = reinterpret_cast<uint8_t *>(data);
107 mStream.avail_out = maxSize;
108 }
109
110 size_t inputBufferAvailable() const
111 {
112 return mStream.avail_in;
113 }
114
115 size_t outputBufferAvailable() const
116 {
117 return mStream.avail_out;
118 }
119
120 std::error_code finalize()
121 {
122 lzma_end(&mStream);
123 return LZMA_OK;
124 }
125
126 std::error_code inflate()
127 {
128 return lzma_code(&mStream, LZMA_RUN);
129 }
130
131 std::error_code deflate(bool finish)
132 {
133 return lzma_code(&mStream, finish ? LZMA_FINISH : LZMA_RUN);
134 }
135
136protected:
137 lzma_stream mStream = LZMA_STREAM_INIT;
138};
139
140CompressionStream::CompressionStream(QIODevice *stream, QObject *parent)
141 : QIODevice(parent)
142 , mStream(stream)
143 , mResult(LZMA_OK)
144{
145}
146
147CompressionStream::~CompressionStream()
148{
149 CompressionStream::close();
150}
151
152bool CompressionStream::isSequential() const
153{
154 return true;
155}
156
157bool CompressionStream::open(OpenMode mode)
158{
159 if ((mode & QIODevice::ReadOnly) && (mode & QIODevice::WriteOnly)) {
160 qCWarning(AKONADIPRIVATE_LOG) << "Invalid open mode for CompressionStream.";
161 return false;
162 }
163
164 mCompressor = std::make_unique<Compressor>();
165 if (const auto err = mCompressor->initialize(mode & QIODevice::ReadOnly ? QIODevice::ReadOnly : QIODevice::WriteOnly); err != LZMA_OK) {
166 qCWarning(AKONADIPRIVATE_LOG) << "Failed to initialize LZMA stream coder:" << err.message();
167 return false;
168 }
169
170 if (mode & QIODevice::WriteOnly) {
171 mBuffer.resize(BUFSIZ);
172 mCompressor->setOutputBuffer(mBuffer.data(), mBuffer.size());
173 }
174
175 return QIODevice::open(mode);
176}
177
178void CompressionStream::close()
179{
180 if (!isOpen()) {
181 return;
182 }
183
184 if (openMode() & QIODevice::WriteOnly && mResult == LZMA_OK) {
185 write(nullptr, 0);
186 }
187
188 mResult = mCompressor->finalize();
189
190 setOpenMode(QIODevice::NotOpen);
191}
192
193std::error_code CompressionStream::error() const
194{
195 return mResult == LZMA_STREAM_END ? LZMA_OK : mResult;
196}
197
198bool CompressionStream::atEnd() const
199{
200 return mResult == LZMA_STREAM_END && QIODevice::atEnd() && mStream->atEnd();
201}
202
203qint64 CompressionStream::readData(char *data, qint64 dataSize)
204{
205 qint64 dataRead = 0;
206
207 if (mResult == LZMA_STREAM_END) {
208 return 0;
209 } else if (mResult != LZMA_OK) {
210 return -1;
211 }
212
213 mCompressor->setOutputBuffer(data, dataSize);
214
215 while (dataSize > 0) {
216 if (mCompressor->inputBufferAvailable() == 0) {
217 mBuffer.resize(BUFSIZ);
218 const auto compressedDataRead = mStream->read(mBuffer.data(), mBuffer.size());
219
220 if (compressedDataRead > 0) {
221 mCompressor->setInputBuffer(mBuffer.data(), compressedDataRead);
222 } else {
223 break;
224 }
225 }
226
227 mResult = mCompressor->inflate();
228
229 if (mResult != LZMA_OK && mResult != LZMA_STREAM_END) {
230 qCWarning(AKONADIPRIVATE_LOG) << "Error while decompressing LZMA stream:" << mResult.message();
231 break;
232 }
233
234 const auto decompressedDataRead = dataSize - mCompressor->outputBufferAvailable();
235 dataRead += decompressedDataRead;
236 dataSize -= decompressedDataRead;
237
238 if (mResult == LZMA_STREAM_END) {
239 if (mStream->atEnd()) {
240 break;
241 }
242 }
243
244 mCompressor->setOutputBuffer(data + dataRead, dataSize);
245 }
246
247 return dataRead;
248}
249
250qint64 CompressionStream::writeData(const char *data, qint64 dataSize)
251{
252 if (mResult != LZMA_OK) {
253 return 0;
254 }
255
256 bool finish = (data == nullptr);
257 if (!finish) {
258 mCompressor->setInputBuffer(data, dataSize);
259 }
260
261 size_t dataWritten = 0;
262
263 while (dataSize > 0 || finish) {
264 mResult = mCompressor->deflate(finish);
265
266 if (mResult != LZMA_OK && mResult != LZMA_STREAM_END) {
267 qCWarning(AKONADIPRIVATE_LOG) << "Error while compressing LZMA stream:" << mResult.message();
268 break;
269 }
270
271 if (mCompressor->inputBufferAvailable() == 0 || (mResult == LZMA_STREAM_END)) {
272 const auto wrote = dataSize - mCompressor->inputBufferAvailable();
273
274 dataWritten += wrote;
275 dataSize -= wrote;
276
277 if (dataSize > 0) {
278 mCompressor->setInputBuffer(data + dataWritten, dataSize);
279 }
280 }
281
282 if (mCompressor->outputBufferAvailable() == 0 || (mResult == LZMA_STREAM_END) || finish) {
283 const auto toWrite = mBuffer.size() - mCompressor->outputBufferAvailable();
284 if (toWrite > 0) {
285 const auto writtenSize = mStream->write(mBuffer.constData(), toWrite);
286 if (writtenSize != toWrite) {
287 qCWarning(AKONADIPRIVATE_LOG) << "Failed to write compressed data to output device:" << mStream->errorString();
288 setErrorString(QStringLiteral("Failed to write compressed data to output device."));
289 return 0;
290 }
291 }
292
293 if (mResult == LZMA_STREAM_END) {
294 Q_ASSERT(finish);
295 break;
296 }
297 mBuffer.resize(BUFSIZ);
298 mCompressor->setOutputBuffer(mBuffer.data(), mBuffer.size());
299 }
300 }
301
302 return dataWritten;
303}
304
305bool CompressionStream::isCompressed(QIODevice *data)
306{
307 constexpr std::array<uchar, 6> magic = {0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00};
308
309 if (!data->isOpen() && !data->isReadable()) {
310 return false;
311 }
312
313 char buf[6] = {};
314 if (data->peek(buf, sizeof(buf)) != sizeof(buf)) {
315 return false;
316 }
317
318 return memcmp(magic.data(), buf, sizeof(buf)) == 0;
319}
320
321#include "moc_compressionstream_p.cpp"
Helper integration between Akonadi and Qt.
QString name(GameStandardAction id)
virtual bool atEnd() const const
bool isOpen() const const
bool isReadable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QByteArray peek(qint64 maxSize)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:31:58 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.