//============================================================================= // // File : KviPackageWriter.cpp // Creation date : Tue 26 Dec 2006 05:33:33 by Szymon Stefanek // // This file is part of the KVIrc IRC Client distribution // Copyright (C) 2006-2010 Szymon Stefanek // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #include "KviPackageWriter.h" #include "KviFile.h" #include "KviFileUtils.h" #include "KviLocale.h" #include "KviPointerList.h" #include "kvi_inttypes.h" #include #include #include #ifdef COMPILE_ZLIB_SUPPORT #include #endif // // See KviPackageIOEngine.cpp for the description of the KVIrc package file // class KviPackageWriterDataField { public: kvi_u32_t m_uType; // output length of the field kvi_u32_t m_uWrittenFieldLength; // data fields for the File KviPackageWriterDataFieldType bool m_bFileAllowCompression; QString m_szFileLocalName; QString m_szFileTargetName; }; class KviPackageWriterPrivate { public: KviPointerList * pDataFields; int iCurrentProgress; }; KviPackageWriter::KviPackageWriter() : KviPackageIOEngine() { m_p = new KviPackageWriterPrivate(); m_p->pDataFields = new KviPointerList(); m_p->pDataFields->setAutoDelete(true); } KviPackageWriter::~KviPackageWriter() { delete m_p->pDataFields; delete m_p; } void KviPackageWriter::addInfoField(const QString & szName, const QString & szValue) { stringInfoFields()->replace(szName, new QString(szValue)); } void KviPackageWriter::addInfoField(const QString & szName, QByteArray * pValue) { binaryInfoFields()->replace(szName, pValue); } bool KviPackageWriter::addFile(const QString & szLocalFileName, const QString & szTargetFileName, kvi_u32_t uAddFileFlags) { QFileInfo fi(szLocalFileName); return addFileInternal(&fi, szLocalFileName, szTargetFileName, uAddFileFlags); } bool KviPackageWriter::addFileInternal(const QFileInfo * fi, const QString & szLocalFileName, const QString & szTargetFileName, kvi_u32_t uAddFileFlags) { if(!(fi->isFile() && fi->isReadable())) return false; if(!(uAddFileFlags & FollowSymLinks)) { if(fi->isSymLink()) return true; // do NOT add a symlink } KviPackageWriterDataField * f = new KviPackageWriterDataField(); f->m_uType = KVI_PACKAGE_DATAFIELD_TYPE_FILE; f->m_bFileAllowCompression = !(uAddFileFlags & NoCompression); f->m_szFileLocalName = szLocalFileName; f->m_szFileTargetName = szTargetFileName; m_p->pDataFields->append(f); return true; } bool KviPackageWriter::addDirectory(const QString & szLocalDirectoryName, const QString & szTargetDirectoryPrefix, kvi_u32_t uAddFileFlags) { QDir d(szLocalDirectoryName); QDir::Filters iFlags; iFlags = QDir::Files | QDir::Readable; if(!(uAddFileFlags & FollowSymLinks)) iFlags |= QDir::NoSymLinks; int j; QFileInfoList sl = d.entryInfoList(iFlags); for(j = 0; j < sl.size(); j++) { QString szSFileName = szLocalDirectoryName; KviQString::ensureLastCharIs(szSFileName, QChar(KVI_PATH_SEPARATOR_CHAR)); QFileInfo slowCopy = sl.at(j); szSFileName += slowCopy.fileName(); QString szDFileName = szTargetDirectoryPrefix; KviQString::ensureLastCharIs(szDFileName, QChar(KVI_PATH_SEPARATOR_CHAR)); szDFileName += slowCopy.fileName(); if(!addFileInternal(&slowCopy, szSFileName, szDFileName, uAddFileFlags)) return false; } iFlags = QDir::Dirs | QDir::Readable; if(!(uAddFileFlags & FollowSymLinks)) iFlags |= QDir::NoSymLinks; sl = d.entryInfoList(iFlags); for(j = 0; j < sl.size(); j++) { QString szDir = sl.at(j).fileName(); if(!KviQString::equalCS(szDir, "..") && !KviQString::equalCS(szDir, ".")) { QString szSDirName = szLocalDirectoryName; KviQString::ensureLastCharIs(szSDirName, QChar(KVI_PATH_SEPARATOR_CHAR)); szSDirName += szDir; QString szDDirName = szTargetDirectoryPrefix; KviQString::ensureLastCharIs(szDDirName, QChar(KVI_PATH_SEPARATOR_CHAR)); szDDirName += szDir; if(!addDirectory(szSDirName, szDDirName, uAddFileFlags)) return false; } } return true; } #define BUFFER_SIZE 32768 bool KviPackageWriter::packFile(KviFile * pFile, KviPackageWriterDataField * pDataField) { QString szProgressText = QString(__tr2qs("Packaging file %1")).arg(pDataField->m_szFileLocalName); if(!updateProgress(m_p->iCurrentProgress, szProgressText)) return false; // aborted KviFile source(pDataField->m_szFileLocalName); if(!source.open(QFile::ReadOnly)) { setLastError(__tr2qs("Failed to open a source file for reading")); return false; } kvi_u32_t uSize = source.size(); // Flags #ifdef COMPILE_ZLIB_SUPPORT kvi_u32_t uFlags = pDataField->m_bFileAllowCompression ? (uSize > 64 ? KVI_PACKAGE_DATAFIELD_FLAG_FILE_DEFLATE : 0) : 0; #else kvi_u32_t uFlags = 0; #endif if(!pFile->save(uFlags)) return writeError(); QByteArray szTargetFileName = pDataField->m_szFileTargetName.toUtf8(); // Path if(!pFile->save(szTargetFileName)) return writeError(); kvi_file_offset_t savedSizeOffset = pFile->pos(); // Size : will update it if compression is requested if(!pFile->save(uSize)) return writeError(); pDataField->m_uWrittenFieldLength = 4 + 4 + 4 + szTargetFileName.length(); // sizeof(flags + uncompressed size + path len + path) // FilePayload #ifdef COMPILE_ZLIB_SUPPORT if(uFlags & KVI_PACKAGE_DATAFIELD_FLAG_FILE_DEFLATE) { unsigned char ibuffer[BUFFER_SIZE]; unsigned char obuffer[BUFFER_SIZE]; kvi_i32_t iReaded = source.read((char *)ibuffer, BUFFER_SIZE); if(iReaded < 0) return readError(); z_stream zstr; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; zstr.next_in = ibuffer; zstr.avail_in = iReaded; zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; if(deflateInit(&zstr, 9) != Z_OK) { setLastError(__tr2qs("Compression library initialization error")); return false; } while(iReaded > 0) { zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; if(deflate(&zstr, Z_NO_FLUSH) != Z_OK) { setLastError(__tr2qs("Compression library error")); return false; } if(zstr.avail_out < BUFFER_SIZE) { int iCompressed = zstr.next_out - obuffer; pDataField->m_uWrittenFieldLength += iCompressed; if(pFile->write((char *)obuffer, iCompressed) != iCompressed) { deflateEnd(&zstr); return writeError(); } } if(zstr.avail_in < BUFFER_SIZE) { int iDataToRead = BUFFER_SIZE - zstr.avail_in; if(iDataToRead < BUFFER_SIZE) { if(ibuffer != zstr.next_in) { // hum, there is still some data in the buffer to be readed // and it is not at the beginning...move it to the beginning of ibuffer memmove(ibuffer, zstr.next_in, zstr.avail_in); } } iReaded = source.read((char *)(ibuffer + zstr.avail_in), iDataToRead); if(iReaded < 0) { deflateEnd(&zstr); return readError(); } zstr.avail_in += iReaded; zstr.next_in = ibuffer; if((zstr.total_in % 2000000) == 0) { QString szTmp = QString(" (%1 of %2 bytes)").arg(zstr.total_in, uSize); QString szPrg = szProgressText + szTmp; if(!updateProgress(m_p->iCurrentProgress, szPrg)) return false; // aborted } } } // flush pending output zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; int ret; do { ret = deflate(&zstr, Z_FINISH); if((ret == Z_OK) || (ret == Z_STREAM_END)) { if(zstr.avail_out < BUFFER_SIZE) { int iCompressed = zstr.next_out - obuffer; pDataField->m_uWrittenFieldLength += iCompressed; if(pFile->write((char *)obuffer, iCompressed) != iCompressed) { deflateEnd(&zstr); return writeError(); } } else { deflateEnd(&zstr); setLastError(__tr2qs("Compression library internal error")); return false; } zstr.next_out = obuffer; zstr.avail_out = BUFFER_SIZE; } } while(ret == Z_OK); // store the compressed data size kvi_file_offset_t here = pFile->pos(); pFile->seek(savedSizeOffset); uSize = zstr.total_out; deflateEnd(&zstr); if(!pFile->save(uSize)) return writeError(); if(ret != Z_STREAM_END) { setLastError(__tr2qs("Error while compressing a file stream")); return false; } pFile->seek(here); } else { #endif unsigned char buffer[BUFFER_SIZE]; int iTotalFileSize = 0; kvi_i32_t iReaded = source.read((char *)buffer, BUFFER_SIZE); if(iReaded < 0) return readError(); while(iReaded > 0) { iTotalFileSize += iReaded; if((iTotalFileSize % 1000000) == 0) { QString szTmp = QString(" (%1 of %2 bytes)").arg(iTotalFileSize, uSize); QString szPrg = szProgressText + szTmp; if(!updateProgress(m_p->iCurrentProgress, szPrg)) return false; // aborted } pDataField->m_uWrittenFieldLength += iReaded; if(pFile->write((char *)buffer, iReaded) != iReaded) return writeError(); iReaded = source.read((char *)buffer, BUFFER_SIZE); } #ifdef COMPILE_ZLIB_SUPPORT } #endif source.close(); return true; } bool KviPackageWriter::pack(const QString & szFileName, kvi_u32_t uPackFlags) { m_p->iCurrentProgress = 0; if(!(uPackFlags & NoProgressDialog)) { showProgressDialog(__tr2qs("Creating package..."), 100); updateProgress(m_p->iCurrentProgress, __tr2qs("Writing package header")); } bool bRet = packInternal(szFileName, uPackFlags); hideProgressDialog(); return bRet; } bool KviPackageWriter::packInternal(const QString & szFileName, kvi_u32_t) { KviFile f(szFileName); if(!f.open(QFile::WriteOnly | QFile::Truncate)) { setLastError(__tr2qs("Can't open file for writing")); return false; } // write the PackageHeader // Magic char magic[4]; magic[0] = 'K'; magic[1] = 'V'; magic[2] = 'P'; magic[3] = 'F'; if(f.write(magic, 4) != 4) return writeError(); // Version kvi_u32_t uVersion = 0x1; if(!f.save(uVersion)) return writeError(); // Flags kvi_u32_t uFlags = 0x0; if(!f.save(uFlags)) return writeError(); // write PackageInfo // InfoFieldCount kvi_u32_t uCount = stringInfoFields()->count() + binaryInfoFields()->count(); if(!f.save(uCount)) return writeError(); m_p->iCurrentProgress = 5; if(!updateProgress(m_p->iCurrentProgress, __tr2qs("Writing informational fields"))) return false; // aborted // InfoFields (string) KviPointerHashTableIterator it(*stringInfoFields()); while(QString * s = it.current()) { if(!f.save(it.currentKey())) return writeError(); kvi_u32_t uType = KVI_PACKAGE_INFOFIELD_TYPE_STRING; if(!f.save(uType)) return writeError(); if(!f.save(*s)) return writeError(); ++it; } // InfoFields (binary) KviPointerHashTableIterator it2(*binaryInfoFields()); while(QByteArray * b = it2.current()) { if(!f.save(it2.currentKey())) return writeError(); kvi_u32_t uType = KVI_PACKAGE_INFOFIELD_TYPE_BINARYBUFFER; if(!f.save(uType)) return writeError(); if(!f.save(*b)) return writeError(); ++it2; } m_p->iCurrentProgress = 10; if(!updateProgress(m_p->iCurrentProgress, __tr2qs("Writing package data"))) return false; // aborted // write PackageData int iIdx = 0; for(KviPackageWriterDataField * pDataField = m_p->pDataFields->first(); pDataField; pDataField = m_p->pDataFields->next()) { kvi_u32_t uKviPackageWriterDataFieldType = pDataField->m_uType; if(!f.save(uKviPackageWriterDataFieldType)) return writeError(); kvi_file_offset_t savedLenOffset = f.pos(); // here we will store the length of the field once it's written if(!f.save(uKviPackageWriterDataFieldType)) return writeError(); m_p->iCurrentProgress = 10 + ((90 * iIdx) / m_p->pDataFields->count()); switch(pDataField->m_uType) { case KVI_PACKAGE_DATAFIELD_TYPE_FILE: if(!packFile(&f, pDataField)) return false; break; default: setLastError(__tr2qs("Internal error")); return false; break; } kvi_file_offset_t savedEndOffset = f.pos(); f.seek(savedLenOffset); if(!f.save(pDataField->m_uWrittenFieldLength)) return writeError(); f.seek(savedEndOffset); iIdx++; } return true; }