/*
Copyright (C) 2005-2014 Sergey A. Tachenov
This file is part of QuaZIP.
QuaZIP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.
QuaZIP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with QuaZIP. If not, see .
See COPYING file for the full LGPL text.
Original ZIP package is copyrighted by Gilles Vollant and contributors,
see quazip/(un)zip.h files for details. Basically it's the zlib license.
*/
#include "quaziodevice.h"
#define QUAZIO_INBUFSIZE 4096
#define QUAZIO_OUTBUFSIZE 4096
/// \cond internal
class QuaZIODevicePrivate {
friend class QuaZIODevice;
QuaZIODevicePrivate(QIODevice *io);
~QuaZIODevicePrivate();
QIODevice *io;
z_stream zins;
z_stream zouts;
char *inBuf;
int inBufPos;
int inBufSize;
char *outBuf;
int outBufPos;
int outBufSize;
bool zBufError;
bool atEnd;
int doFlush(QString &error);
};
QuaZIODevicePrivate::QuaZIODevicePrivate(QIODevice *io):
io(io),
inBuf(NULL),
inBufPos(0),
inBufSize(0),
outBuf(NULL),
outBufPos(0),
outBufSize(0),
zBufError(false),
atEnd(false)
{
zins.zalloc = (alloc_func) NULL;
zins.zfree = (free_func) NULL;
zins.opaque = NULL;
zouts.zalloc = (alloc_func) NULL;
zouts.zfree = (free_func) NULL;
zouts.opaque = NULL;
inBuf = new char[QUAZIO_INBUFSIZE];
outBuf = new char[QUAZIO_OUTBUFSIZE];
#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT
debug.setFileName("debug.out");
debug.open(QIODevice::WriteOnly);
#endif
#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT
indebug.setFileName("debug.in");
indebug.open(QIODevice::WriteOnly);
#endif
}
QuaZIODevicePrivate::~QuaZIODevicePrivate()
{
#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT
debug.close();
#endif
#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT
indebug.close();
#endif
if (inBuf != NULL)
delete[] inBuf;
if (outBuf != NULL)
delete[] outBuf;
}
int QuaZIODevicePrivate::doFlush(QString &error)
{
int flushed = 0;
while (outBufPos < outBufSize) {
int more = io->write(outBuf + outBufPos, outBufSize - outBufPos);
if (more == -1) {
error = io->errorString();
return -1;
}
if (more == 0)
break;
outBufPos += more;
flushed += more;
}
if (outBufPos == outBufSize) {
outBufPos = outBufSize = 0;
}
return flushed;
}
/// \endcond
// #define QUAZIP_ZIODEVICE_DEBUG_OUTPUT
// #define QUAZIP_ZIODEVICE_DEBUG_INPUT
#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT
#include
static QFile debug;
#endif
#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT
#include
static QFile indebug;
#endif
QuaZIODevice::QuaZIODevice(QIODevice *io, QObject *parent):
QIODevice(parent),
d(new QuaZIODevicePrivate(io))
{
connect(io, SIGNAL(readyRead()), SIGNAL(readyRead()));
}
QuaZIODevice::~QuaZIODevice()
{
if (isOpen())
close();
delete d;
}
QIODevice *QuaZIODevice::getIoDevice() const
{
return d->io;
}
bool QuaZIODevice::open(QIODevice::OpenMode mode)
{
if ((mode & QIODevice::Append) != 0) {
setErrorString(trUtf8("QIODevice::Append is not supported for"
" QuaZIODevice"));
return false;
}
if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
setErrorString(trUtf8("QIODevice::ReadWrite is not supported for"
" QuaZIODevice"));
return false;
}
if ((mode & QIODevice::ReadOnly) != 0) {
if (inflateInit(&d->zins) != Z_OK) {
setErrorString(d->zins.msg);
return false;
}
}
if ((mode & QIODevice::WriteOnly) != 0) {
if (deflateInit(&d->zouts, Z_DEFAULT_COMPRESSION) != Z_OK) {
setErrorString(d->zouts.msg);
return false;
}
}
return QIODevice::open(mode);
}
void QuaZIODevice::close()
{
if ((openMode() & QIODevice::ReadOnly) != 0) {
if (inflateEnd(&d->zins) != Z_OK) {
setErrorString(d->zins.msg);
}
}
if ((openMode() & QIODevice::WriteOnly) != 0) {
flush();
if (deflateEnd(&d->zouts) != Z_OK) {
setErrorString(d->zouts.msg);
}
}
QIODevice::close();
}
qint64 QuaZIODevice::readData(char *data, qint64 maxSize)
{
int read = 0;
while (read < maxSize) {
if (d->inBufPos == d->inBufSize) {
d->inBufPos = 0;
d->inBufSize = d->io->read(d->inBuf, QUAZIO_INBUFSIZE);
if (d->inBufSize == -1) {
d->inBufSize = 0;
setErrorString(d->io->errorString());
return -1;
}
if (d->inBufSize == 0)
break;
}
while (read < maxSize && d->inBufPos < d->inBufSize) {
d->zins.next_in = (Bytef *) (d->inBuf + d->inBufPos);
d->zins.avail_in = d->inBufSize - d->inBufPos;
d->zins.next_out = (Bytef *) (data + read);
d->zins.avail_out = (uInt) (maxSize - read); // hope it's less than 2GB
int more = 0;
switch (inflate(&d->zins, Z_SYNC_FLUSH)) {
case Z_OK:
read = (char *) d->zins.next_out - data;
d->inBufPos = (char *) d->zins.next_in - d->inBuf;
break;
case Z_STREAM_END:
read = (char *) d->zins.next_out - data;
d->inBufPos = (char *) d->zins.next_in - d->inBuf;
d->atEnd = true;
return read;
case Z_BUF_ERROR: // this should never happen, but just in case
if (!d->zBufError) {
qWarning("Z_BUF_ERROR detected with %d/%d in/out, weird",
d->zins.avail_in, d->zins.avail_out);
d->zBufError = true;
}
memmove(d->inBuf, d->inBuf + d->inBufPos, d->inBufSize - d->inBufPos);
d->inBufSize -= d->inBufPos;
d->inBufPos = 0;
more = d->io->read(d->inBuf + d->inBufSize, QUAZIO_INBUFSIZE - d->inBufSize);
if (more == -1) {
setErrorString(d->io->errorString());
return -1;
}
if (more == 0)
return read;
d->inBufSize += more;
break;
default:
setErrorString(QString::fromLocal8Bit(d->zins.msg));
return -1;
}
}
}
#ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT
indebug.write(data, read);
#endif
return read;
}
qint64 QuaZIODevice::writeData(const char *data, qint64 maxSize)
{
int written = 0;
QString error;
if (d->doFlush(error) == -1) {
setErrorString(error);
return -1;
}
while (written < maxSize) {
// there is some data waiting in the output buffer
if (d->outBufPos < d->outBufSize)
return written;
d->zouts.next_in = (Bytef *) (data + written);
d->zouts.avail_in = (uInt) (maxSize - written); // hope it's less than 2GB
d->zouts.next_out = (Bytef *) d->outBuf;
d->zouts.avail_out = QUAZIO_OUTBUFSIZE;
switch (deflate(&d->zouts, Z_NO_FLUSH)) {
case Z_OK:
written = (char *) d->zouts.next_in - data;
d->outBufSize = (char *) d->zouts.next_out - d->outBuf;
break;
default:
setErrorString(QString::fromLocal8Bit(d->zouts.msg));
return -1;
}
if (d->doFlush(error) == -1) {
setErrorString(error);
return -1;
}
}
#ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT
debug.write(data, written);
#endif
return written;
}
bool QuaZIODevice::flush()
{
QString error;
if (d->doFlush(error) < 0) {
setErrorString(error);
return false;
}
// can't flush buffer, some data is still waiting
if (d->outBufPos < d->outBufSize)
return true;
Bytef c = 0;
d->zouts.next_in = &c; // fake input buffer
d->zouts.avail_in = 0; // of zero size
do {
d->zouts.next_out = (Bytef *) d->outBuf;
d->zouts.avail_out = QUAZIO_OUTBUFSIZE;
switch (deflate(&d->zouts, Z_SYNC_FLUSH)) {
case Z_OK:
d->outBufSize = (char *) d->zouts.next_out - d->outBuf;
if (d->doFlush(error) < 0) {
setErrorString(error);
return false;
}
if (d->outBufPos < d->outBufSize)
return true;
break;
case Z_BUF_ERROR: // nothing to write?
return true;
default:
setErrorString(QString::fromLocal8Bit(d->zouts.msg));
return false;
}
} while (d->zouts.avail_out == 0);
return true;
}
bool QuaZIODevice::isSequential() const
{
return true;
}
bool QuaZIODevice::atEnd() const
{
// Here we MUST check QIODevice::bytesAvailable() because WE
// might have reached the end, but QIODevice didn't--
// it could have simply pre-buffered all remaining data.
return (openMode() == NotOpen) || (QIODevice::bytesAvailable() == 0 && d->atEnd);
}
qint64 QuaZIODevice::bytesAvailable() const
{
// If we haven't recevied Z_STREAM_END, it means that
// we have at least one more input byte available.
// Plus whatever QIODevice has buffered.
return (d->atEnd ? 0 : 1) + QIODevice::bytesAvailable();
}