KArchive

kcompressiondevice.cpp
1 /* This file is part of the KDE libraries
2  SPDX-FileCopyrightText: 2000 David Faure <[email protected]>
3  SPDX-FileCopyrightText: 2011 Mario Bensi <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "kcompressiondevice.h"
9 #include "kcompressiondevice_p.h"
10 #include "loggingcategory.h"
11 #include <config-compression.h>
12 #include "kfilterbase.h"
13 #include <QFile>
14 #include <stdio.h> // for EOF
15 #include <stdlib.h>
16 #include <assert.h>
17 
18 #include "kgzipfilter.h"
19 #include "knonefilter.h"
20 
21 #if HAVE_BZIP2_SUPPORT
22 #include "kbzip2filter.h"
23 #endif
24 #if HAVE_XZ_SUPPORT
25 #include "kxzfilter.h"
26 #endif
27 
28 #include <QDebug>
29 
30 class KCompressionDevicePrivate
31 {
32 public:
33  KCompressionDevicePrivate(KCompressionDevice *q)
34  : bNeedHeader(true)
35  , bSkipHeaders(false)
36  , bOpenedUnderlyingDevice(false)
38  , errorCode(QFileDevice::NoError)
39  , deviceReadPos(0)
40  , q(q)
41  {
42  }
43 
44  void propagateErrorCode();
45 
46  bool bNeedHeader;
47  bool bSkipHeaders;
48  bool bOpenedUnderlyingDevice;
49  QByteArray buffer; // Used as 'input buffer' when reading, as 'output buffer' when writing
50  QByteArray origFileName;
51  KFilterBase::Result result;
53  KCompressionDevice::CompressionType type;
54  QFileDevice::FileError errorCode;
55  qint64 deviceReadPos;
57 };
58 
59 void KCompressionDevicePrivate::propagateErrorCode()
60 {
61  QIODevice *dev = filter->device();
62  if (QFileDevice *fileDev = qobject_cast<QFileDevice *>(dev)) {
63  if (fileDev->error() != QFileDevice::NoError) {
64  errorCode = fileDev->error();
65  q->setErrorString(dev->errorString());
66  }
67  }
68  // ... we have no generic way to propagate errors from other kinds of iodevices. Sucks, heh? :(
69 }
70 
71 KFilterBase *KCompressionDevice::filterForCompressionType(KCompressionDevice::CompressionType type)
72 {
73  switch (type) {
74  case KCompressionDevice::GZip:
75  return new KGzipFilter;
76  case KCompressionDevice::BZip2:
77 #if HAVE_BZIP2_SUPPORT
78  return new KBzip2Filter;
79 #else
80  return nullptr;
81 #endif
82  case KCompressionDevice::Xz:
83 #if HAVE_XZ_SUPPORT
84  return new KXzFilter;
85 #else
86  return nullptr;
87 #endif
88  case KCompressionDevice::None:
89  return new KNoneFilter;
90  }
91  return nullptr;
92 }
93 
94 KCompressionDevice::KCompressionDevice(QIODevice *inputDevice, bool autoDeleteInputDevice, CompressionType type)
95  : d(new KCompressionDevicePrivate(this))
96 {
97  assert(inputDevice);
98  d->filter = filterForCompressionType(type);
99  if (d->filter) {
100  d->type = type;
101  d->filter->setDevice(inputDevice, autoDeleteInputDevice);
102  }
103 }
104 
105 KCompressionDevice::KCompressionDevice(const QString &fileName, CompressionType type)
106  : d(new KCompressionDevicePrivate(this))
107 {
108  QFile *f = new QFile(fileName);
109  d->filter = filterForCompressionType(type);
110  if (d->filter) {
111  d->type = type;
112  d->filter->setDevice(f, true);
113  } else {
114  delete f;
115  }
116 }
117 
119 {
120  if (isOpen()) {
121  close();
122  }
123  delete d->filter;
124  delete d;
125 }
126 
127 KCompressionDevice::CompressionType KCompressionDevice::compressionType() const
128 {
129  return d->type;
130 }
131 
133 {
134  if (isOpen()) {
135  //qCWarning(KArchiveLog) << "KCompressionDevice::open: device is already open";
136  return true; // QFile returns false, but well, the device -is- open...
137  }
138  if (!d->filter) {
139  return false;
140  }
141  d->bOpenedUnderlyingDevice = false;
142  //qCDebug(KArchiveLog) << mode;
143  if (mode == QIODevice::ReadOnly) {
144  d->buffer.resize(0);
145  } else {
146  d->buffer.resize(BUFFER_SIZE);
147  d->filter->setOutBuffer(d->buffer.data(), d->buffer.size());
148  }
149  if (!d->filter->device()->isOpen()) {
150  if (!d->filter->device()->open(mode)) {
151  //qCWarning(KArchiveLog) << "KCompressionDevice::open: Couldn't open underlying device";
152  d->propagateErrorCode();
153  return false;
154  }
155  d->bOpenedUnderlyingDevice = true;
156  }
157  d->bNeedHeader = !d->bSkipHeaders;
158  d->filter->setFilterFlags(d->bSkipHeaders ? KFilterBase::NoHeaders : KFilterBase::WithHeaders);
159  if (!d->filter->init(mode)) {
160  return false;
161  }
162  d->result = KFilterBase::Ok;
163  setOpenMode(mode);
164  return true;
165 }
166 
168 {
169  if (!isOpen()) {
170  return;
171  }
172  if (d->filter->mode() == QIODevice::WriteOnly && d->errorCode == QFileDevice::NoError) {
173  write(nullptr, 0); // finish writing
174  }
175  //qCDebug(KArchiveLog) << "Calling terminate().";
176 
177  if (!d->filter->terminate()) {
178  //qCWarning(KArchiveLog) << "KCompressionDevice::close: terminate returned an error";
179  d->errorCode = QFileDevice::UnspecifiedError;
180  }
181  if (d->bOpenedUnderlyingDevice) {
182  QIODevice *dev = d->filter->device();
183  dev->close();
184  d->propagateErrorCode();
185  }
186  setOpenMode(QIODevice::NotOpen);
187 }
188 
189 QFileDevice::FileError KCompressionDevice::error() const
190 {
191  return d->errorCode;
192 }
193 
195 {
196  if (d->deviceReadPos == pos) {
197  return QIODevice::seek(pos);
198  }
199 
200  //qCDebug(KArchiveLog) << "seek(" << pos << ") called, current pos=" << QIODevice::pos();
201 
202  Q_ASSERT(d->filter->mode() == QIODevice::ReadOnly);
203 
204  if (pos == 0) {
205  if (!QIODevice::seek(pos))
206  return false;
207 
208  // We can forget about the cached data
209  d->bNeedHeader = !d->bSkipHeaders;
210  d->result = KFilterBase::Ok;
211  d->filter->setInBuffer(nullptr, 0);
212  d->filter->reset();
213  d->deviceReadPos = 0;
214  return d->filter->device()->reset();
215  }
216 
217  qint64 bytesToRead;
218  if (d->deviceReadPos < pos) { // we can start from here
219  bytesToRead = pos - d->deviceReadPos;
220  // Since we're going to do a read() below
221  // we need to reset the internal QIODevice pos to the real position we are
222  // so that after read() we are indeed pointing to the pos seek
223  // asked us to be in
224  if (!QIODevice::seek(d->deviceReadPos)) {
225  return false;
226  }
227  } else {
228  // we have to start from 0 ! Ugly and slow, but better than the previous
229  // solution (KTarGz was allocating everything into memory)
230  if (!seek(0)) { // recursive
231  return false;
232  }
233  bytesToRead = pos;
234  }
235 
236  //qCDebug(KArchiveLog) << "reading " << bytesToRead << " dummy bytes";
237  QByteArray dummy(qMin(bytesToRead, qint64(SEEK_BUFFER_SIZE)), 0);
238  while (bytesToRead > 0) {
239  const qint64 bytesToReadThisTime = qMin(bytesToRead, qint64(dummy.size()));
240  const bool result = (read(dummy.data(), bytesToReadThisTime) == bytesToReadThisTime);
241  if (!result) {
242  return false;
243  }
244  bytesToRead -= bytesToReadThisTime;
245  }
246  return true;
247 }
248 
249 bool KCompressionDevice::atEnd() const
250 {
251  return (d->type == KCompressionDevice::None || d->result == KFilterBase::End)
252  && QIODevice::atEnd() // take QIODevice's internal buffer into account
253  && d->filter->device()->atEnd();
254 }
255 
256 qint64 KCompressionDevice::readData(char *data, qint64 maxlen)
257 {
258  Q_ASSERT(d->filter->mode() == QIODevice::ReadOnly);
259  //qCDebug(KArchiveLog) << "maxlen=" << maxlen;
260  KFilterBase *filter = d->filter;
261 
262  uint dataReceived = 0;
263 
264  // We came to the end of the stream
265  if (d->result == KFilterBase::End) {
266  return dataReceived;
267  }
268 
269  // If we had an error, return -1.
270  if (d->result != KFilterBase::Ok) {
271  return -1;
272  }
273 
274  qint64 availOut = maxlen;
275  filter->setOutBuffer(data, maxlen);
276 
277  while (dataReceived < maxlen) {
278  if (filter->inBufferEmpty()) {
279  // Not sure about the best size to set there.
280  // For sure, it should be bigger than the header size (see comment in readHeader)
281  d->buffer.resize(BUFFER_SIZE);
282  // Request data from underlying device
283  int size = filter->device()->read(d->buffer.data(),
284  d->buffer.size());
285  //qCDebug(KArchiveLog) << "got" << size << "bytes from device";
286  if (size) {
287  filter->setInBuffer(d->buffer.data(), size);
288  } else {
289  // Not enough data available in underlying device for now
290  break;
291  }
292  }
293  if (d->bNeedHeader) {
294  (void) filter->readHeader();
295  d->bNeedHeader = false;
296  }
297 
298  d->result = filter->uncompress();
299 
300  if (d->result == KFilterBase::Error) {
301  //qCWarning(KArchiveLog) << "KCompressionDevice: Error when uncompressing data";
302  break;
303  }
304 
305  // We got that much data since the last time we went here
306  uint outReceived = availOut - filter->outBufferAvailable();
307  //qCDebug(KArchiveLog) << "avail_out = " << filter->outBufferAvailable() << " result=" << d->result << " outReceived=" << outReceived;
308  if (availOut < uint(filter->outBufferAvailable())) {
309  //qCWarning(KArchiveLog) << " last availOut " << availOut << " smaller than new avail_out=" << filter->outBufferAvailable() << " !";
310  }
311 
312  dataReceived += outReceived;
313  data += outReceived;
314  availOut = maxlen - dataReceived;
315  if (d->result == KFilterBase::End) {
316  // We're actually at the end, no more data to check
317  if (filter->device()->atEnd()) {
318  break;
319  }
320 
321  // Still not done, re-init and try again
322  filter->init(filter->mode());
323  }
324  filter->setOutBuffer(data, availOut);
325  }
326 
327  d->deviceReadPos += dataReceived;
328  return dataReceived;
329 }
330 
331 qint64 KCompressionDevice::writeData(const char *data /*0 to finish*/, qint64 len)
332 {
333  KFilterBase *filter = d->filter;
334  Q_ASSERT(filter->mode() == QIODevice::WriteOnly);
335  // If we had an error, return 0.
336  if (d->result != KFilterBase::Ok) {
337  return 0;
338  }
339 
340  bool finish = (data == nullptr);
341  if (!finish) {
342  filter->setInBuffer(data, len);
343  if (d->bNeedHeader) {
344  (void)filter->writeHeader(d->origFileName);
345  d->bNeedHeader = false;
346  }
347  }
348 
349  uint dataWritten = 0;
350  uint availIn = len;
351  while (dataWritten < len || finish) {
352 
353  d->result = filter->compress(finish);
354 
355  if (d->result == KFilterBase::Error) {
356  //qCWarning(KArchiveLog) << "KCompressionDevice: Error when compressing data";
357  // What to do ?
358  break;
359  }
360 
361  // Wrote everything ?
362  if (filter->inBufferEmpty() || (d->result == KFilterBase::End)) {
363  // We got that much data since the last time we went here
364  uint wrote = availIn - filter->inBufferAvailable();
365 
366  //qCDebug(KArchiveLog) << " Wrote everything for now. avail_in=" << filter->inBufferAvailable() << "result=" << d->result << "wrote=" << wrote;
367 
368  // Move on in the input buffer
369  data += wrote;
370  dataWritten += wrote;
371 
372  availIn = len - dataWritten;
373  //qCDebug(KArchiveLog) << " availIn=" << availIn << "dataWritten=" << dataWritten << "pos=" << pos();
374  if (availIn > 0) {
375  filter->setInBuffer(data, availIn);
376  }
377  }
378 
379  if (filter->outBufferFull() || (d->result == KFilterBase::End) || finish) {
380  //qCDebug(KArchiveLog) << " writing to underlying. avail_out=" << filter->outBufferAvailable();
381  int towrite = d->buffer.size() - filter->outBufferAvailable();
382  if (towrite > 0) {
383  // Write compressed data to underlying device
384  int size = filter->device()->write(d->buffer.data(), towrite);
385  if (size != towrite) {
386  //qCWarning(KArchiveLog) << "KCompressionDevice::write. Could only write " << size << " out of " << towrite << " bytes";
387  d->errorCode = QFileDevice::WriteError;
388  setErrorString(tr("Could not write. Partition full?"));
389  return 0; // indicate an error
390  }
391  //qCDebug(KArchiveLog) << " wrote " << size << " bytes";
392  }
393  if (d->result == KFilterBase::End) {
394  Q_ASSERT(finish); // hopefully we don't get end before finishing
395  break;
396  }
397  d->buffer.resize(BUFFER_SIZE);
398  filter->setOutBuffer(d->buffer.data(), d->buffer.size());
399  }
400  }
401 
402  return dataWritten;
403 }
404 
406 {
407  d->origFileName = fileName;
408 }
409 
411 {
412  d->bSkipHeaders = true;
413 }
414 
415 KFilterBase *KCompressionDevice::filterBase()
416 {
417  return d->filter;
418 }
virtual bool outBufferFull() const
Definition: kfilterbase.cpp:53
virtual ~KCompressionDevice()
Destructs the KCompressionDevice.
virtual bool atEnd() const const
virtual bool seek(qint64 pos)
QString errorString() const const
virtual void setInBuffer(const char *data, uint size)=0
typedef OpenMode
virtual int mode() const =0
static KFilterBase * filterForCompressionType(CompressionType type)
Call this to create the appropriate filter for the CompressionType named type.
virtual void close()
QString tr(const char *sourceText, const char *disambiguation, int n)
A class for reading and writing compressed data onto a device (e.g.
virtual qint64 pos() const const
Internal class used by KFilterDev.
Definition: kgzipfilter.h:19
virtual bool inBufferEmpty() const
Definition: kfilterbase.cpp:48
QIODevice * device()
Returns the device on which the filter will work.
Definition: kfilterbase.cpp:43
QFileDevice::FileError error() const
Returns the error code from the last failing operation.
virtual Result compress(bool finish)=0
virtual qint64 size() const const
Type type(const QSqlDatabase &db)
virtual void setOutBuffer(char *data, uint maxlen)=0
Internal class used by KFilterDev.
Definition: knonefilter.h:22
virtual int outBufferAvailable() const =0
CompressionType compressionType() const
The compression actually used by this device.
qint64 read(char *data, qint64 maxSize)
bool isOpen() const const
This is the base class for compression filters such as gzip and bzip2.
Definition: kfilterbase.h:26
virtual int inBufferAvailable() const =0
KCompressionDevice(QIODevice *inputDevice, bool autoDeleteInputDevice, CompressionType type)
Constructs a KCompressionDevice for a given CompressionType (e.g.
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
void close() override
Close after reading or writing.
char * data()
qint64 write(const char *data, qint64 maxSize)
bool open(QIODevice::OpenMode mode) override
Open for reading or writing.
void setSkipHeaders()
Call this let this device skip the gzip headers when reading/writing.
void setErrorString(const QString &str)
int size() const const
void setOpenMode(QIODevice::OpenMode openMode)
virtual bool writeHeader(const QByteArray &filename)=0
bool seek(qint64) override
That one can be quite slow, when going back.
void setOrigFileName(const QByteArray &fileName)
For writing gzip compressed files only: set the name of the original file, to be used in the gzip hea...
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Jun 2 2020 22:51:08 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.