text: /*
  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 3 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 <http://www.gnu.org/licenses/>.
  
  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;
      int doFlush(QString &error);
  };
  
  QuaZIODevicePrivate::QuaZIODevicePrivate(QIODevice *io):
    io(io),
    inBuf(NULL),
    inBufPos(0),
    inBufSize(0),
    outBuf(NULL),
    outBufPos(0),
    outBufSize(0),
    zBufError(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 <QFile>
  static QFile debug;
  #endif
  #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT
  #include <QFile>
  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;
          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;
  }
  