Akonadi

compressionstream.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Daniel Vr├ítil <[email protected]>
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 
16 using namespace Akonadi;
17 
18 namespace
19 {
20 class LZMAErrorCategory : public std::error_category
21 {
22 public:
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 
60 const LZMAErrorCategory &lzmaErrorCategory()
61 {
62  static const LZMAErrorCategory lzmaErrorCategory{};
63  return lzmaErrorCategory;
64 }
65 
66 } // namespace
67 
68 namespace std
69 {
70 template<> struct is_error_code_enum<lzma_ret> : std::true_type {
71 };
72 
73 std::error_condition make_error_condition(lzma_ret ret)
74 {
75  return std::error_condition(static_cast<int>(ret), lzmaErrorCategory());
76 }
77 
78 QDebug operator<<(QDebug dbg, const std::string &str)
79 {
80  dbg << QString::fromStdString(str);
81  return dbg;
82 }
83 
84 } // namespace std
85 
86 std::error_code make_error_code(lzma_ret e)
87 {
88  return {static_cast<int>(e), lzmaErrorCategory()};
89 }
90 
91 class Akonadi::Compressor
92 {
93 public:
94  std::error_code initialize(QIODevice::OpenMode openMode)
95  {
96  if (openMode == QIODevice::ReadOnly) {
97  return lzma_auto_decoder(&mStream, 100 * 1024 * 1024 /* 100 MiB */, 0);
98  } else {
99  return lzma_easy_encoder(&mStream, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC32);
100  }
101  }
102 
103  void setInputBuffer(const char *data, qint64 size)
104  {
105  mStream.next_in = reinterpret_cast<const uint8_t *>(data);
106  mStream.avail_in = size;
107  }
108 
109  void setOutputBuffer(char *data, qint64 maxSize)
110  {
111  mStream.next_out = reinterpret_cast<uint8_t *>(data);
112  mStream.avail_out = maxSize;
113  }
114 
115  int inputBufferAvailable() const
116  {
117  return mStream.avail_in;
118  }
119 
120  int outputBufferAvailable() const
121  {
122  return mStream.avail_out;
123  }
124 
125  std::error_code finalize()
126  {
127  lzma_end(&mStream);
128  return LZMA_OK;
129  }
130 
131  std::error_code inflate()
132  {
133  return lzma_code(&mStream, LZMA_RUN);
134  }
135 
136  std::error_code deflate(bool finish)
137  {
138  return lzma_code(&mStream, finish ? LZMA_FINISH : LZMA_RUN);
139  }
140 
141 protected:
142  lzma_stream mStream = LZMA_STREAM_INIT;
143 };
144 
145 CompressionStream::CompressionStream(QIODevice *stream, QObject *parent)
146  : QIODevice(parent)
147  , mStream(stream)
148  , mResult(LZMA_OK)
149 {
150 }
151 
152 CompressionStream::~CompressionStream()
153 {
154  CompressionStream::close();
155 }
156 
157 bool CompressionStream::isSequential() const
158 {
159  return true;
160 }
161 
162 bool CompressionStream::open(OpenMode mode)
163 {
164  if ((mode & QIODevice::ReadOnly) && (mode & QIODevice::WriteOnly)) {
165  qCWarning(AKONADIPRIVATE_LOG) << "Invalid open mode for CompressionStream.";
166  return false;
167  }
168 
169  mCompressor = std::make_unique<Compressor>();
170  if (const auto err = mCompressor->initialize(mode & QIODevice::ReadOnly ? QIODevice::ReadOnly : QIODevice::WriteOnly); err != LZMA_OK) {
171  qCWarning(AKONADIPRIVATE_LOG) << "Failed to initialize LZMA stream coder:" << err.message();
172  return false;
173  }
174 
175  if (mode & QIODevice::WriteOnly) {
176  mBuffer.resize(BUFSIZ);
177  mCompressor->setOutputBuffer(mBuffer.data(), mBuffer.size());
178  }
179 
180  return QIODevice::open(mode);
181 }
182 
183 void CompressionStream::close()
184 {
185  if (!isOpen()) {
186  return;
187  }
188 
189  if (openMode() & QIODevice::WriteOnly && mResult == LZMA_OK) {
190  write(nullptr, 0);
191  }
192 
193  mResult = mCompressor->finalize();
194 
195  setOpenMode(QIODevice::NotOpen);
196 }
197 
198 std::error_code CompressionStream::error() const
199 {
200  return mResult == LZMA_STREAM_END ? LZMA_OK : mResult;
201 }
202 
203 bool CompressionStream::atEnd() const
204 {
205  return mResult == LZMA_STREAM_END && QIODevice::atEnd() && mStream->atEnd();
206 }
207 
208 qint64 CompressionStream::readData(char *data, qint64 dataSize)
209 {
210  qint64 dataRead = 0;
211 
212  if (mResult == LZMA_STREAM_END) {
213  return 0;
214  } else if (mResult != LZMA_OK) {
215  return -1;
216  }
217 
218  mCompressor->setOutputBuffer(data, dataSize);
219 
220  while (dataSize > 0) {
221  if (mCompressor->inputBufferAvailable() == 0) {
222  mBuffer.resize(BUFSIZ);
223  const auto compressedDataRead = mStream->read(mBuffer.data(), mBuffer.size());
224 
225  if (compressedDataRead > 0) {
226  mCompressor->setInputBuffer(mBuffer.data(), compressedDataRead);
227  } else {
228  break;
229  }
230  }
231 
232  mResult = mCompressor->inflate();
233 
234  if (mResult != LZMA_OK && mResult != LZMA_STREAM_END) {
235  qCWarning(AKONADIPRIVATE_LOG) << "Error while decompressing LZMA stream:" << mResult.message();
236  break;
237  }
238 
239  const auto decompressedDataRead = dataSize - mCompressor->outputBufferAvailable();
240  dataRead += decompressedDataRead;
241  dataSize -= decompressedDataRead;
242 
243  if (mResult == LZMA_STREAM_END) {
244  if (mStream->atEnd()) {
245  break;
246  }
247  }
248 
249  mCompressor->setOutputBuffer(data + dataRead, dataSize);
250  }
251 
252  return dataRead;
253 }
254 
255 qint64 CompressionStream::writeData(const char *data, qint64 dataSize)
256 {
257  if (mResult != LZMA_OK) {
258  return 0;
259  }
260 
261  bool finish = (data == nullptr);
262  if (!finish) {
263  mCompressor->setInputBuffer(data, dataSize);
264  }
265 
266  qint64 dataWritten = 0;
267 
268  while (dataSize > 0 || finish) {
269  mResult = mCompressor->deflate(finish);
270 
271  if (mResult != LZMA_OK && mResult != LZMA_STREAM_END) {
272  qCWarning(AKONADIPRIVATE_LOG) << "Error while compressing LZMA stream:" << mResult.message();
273  break;
274  }
275 
276  if (mCompressor->inputBufferAvailable() == 0 || (mResult == LZMA_STREAM_END)) {
277  const auto wrote = dataSize - mCompressor->inputBufferAvailable();
278 
279  dataWritten += wrote;
280  dataSize -= wrote;
281 
282  if (dataSize > 0) {
283  mCompressor->setInputBuffer(data + dataWritten, dataSize);
284  }
285  }
286 
287  if (mCompressor->outputBufferAvailable() == 0 || (mResult == LZMA_STREAM_END) || finish) {
288  const auto toWrite = mBuffer.size() - mCompressor->outputBufferAvailable();
289  if (toWrite > 0) {
290  const auto writtenSize = mStream->write(mBuffer.constData(), toWrite);
291  if (writtenSize != toWrite) {
292  qCWarning(AKONADIPRIVATE_LOG) << "Failed to write compressed data to output device:" << mStream->errorString();
293  setErrorString(QStringLiteral("Failed to write compressed data to output device."));
294  return 0;
295  }
296  }
297 
298  if (mResult == LZMA_STREAM_END) {
299  Q_ASSERT(finish);
300  break;
301  }
302  mBuffer.resize(BUFSIZ);
303  mCompressor->setOutputBuffer(mBuffer.data(), mBuffer.size());
304  }
305  }
306 
307  return dataWritten;
308 }
309 
310 bool CompressionStream::isCompressed(QIODevice *data)
311 {
312  constexpr std::array<uchar, 6> magic = {0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00};
313 
314  if (!data->isOpen() && !data->isReadable()) {
315  return false;
316  }
317 
318  char buf[6] = {};
319  if (data->peek(buf, sizeof(buf)) != sizeof(buf)) {
320  return false;
321  }
322 
323  return memcmp(magic.data(), buf, sizeof(buf)) == 0;
324 }
typedef OpenMode
virtual bool open(QIODevice::OpenMode mode)
QDataStream & operator<<(QDataStream &out, const KDateTime &dateTime)
bool isReadable() const const
void initialize(StandardShortcut id)
QString fromStdString(const std::string &str)
bool isOpen() const const
qint64 peek(char *data, qint64 maxSize)
A glue between Qt and the standard library.
QString name(StandardShortcut id)
virtual bool atEnd() const const
QString message
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jul 2 2022 06:41:47 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.