You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1029 lines
27 KiB
1029 lines
27 KiB
//=============================================================================
|
|
//
|
|
// File : kvi_packagefile.cpp
|
|
// Created on Tue 26 Dec 2006 05:33:33 by Szymon Stefanek
|
|
//
|
|
// This file is part of the KVIrc IRC Client distribution
|
|
// Copyright (C) 2006 Szymon Stefanek <pragma at kvirc dot net>
|
|
//
|
|
// 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 opinion) 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.
|
|
//
|
|
//=============================================================================
|
|
|
|
#define __KVILIB__
|
|
#include "kvi_packagefile.h"
|
|
|
|
#include "kvi_file.h"
|
|
#include "kvi_fileutils.h"
|
|
#include "kvi_locale.h"
|
|
#include "kvi_inttypes.h"
|
|
|
|
#include <tqprogressdialog.h>
|
|
#include <tqlabel.h>
|
|
|
|
#include <tqdir.h>
|
|
|
|
#ifdef COMPILE_ZLIB_SUPPORT
|
|
#include <zlib.h>
|
|
#endif
|
|
|
|
//
|
|
// A KVIrc Package File is basically a simple zip file with some additional meta-data.
|
|
// The package file has the following format
|
|
//
|
|
|
|
// Field Type Bytes Description
|
|
//-------------------------------------------------------------------------------
|
|
// Package:
|
|
// PackageHeader
|
|
// PackageInfo
|
|
// PackageData
|
|
|
|
// PackageHeader:
|
|
// Magic Bytes 4 'KVPF': Signature for the Kvirc Package File
|
|
// Version uint32 4 0x00000001: Version of this package file
|
|
// Flags uint32 4 0x00000000: Flags, in version 1 is reserved and must be zero
|
|
//
|
|
|
|
// PackageInfo:
|
|
// InfoFieldCount uint32 4 Number of package info fields
|
|
// InfoField InfoField Variable A list of informational name-value pairs
|
|
// InfoField InfoField Variable A list of informational name-value pairs
|
|
// InfoField InfoField Variable A list of informational name-value pairs
|
|
// .... .... ....
|
|
|
|
// PackageData:
|
|
// DataField DataField Variable A list of data fields with format defined below
|
|
// DataField DataField Variable A list of data fields with format defined below
|
|
// DataField DataField Variable A list of data fields with format defined below
|
|
// .... .... ....
|
|
|
|
// InfoField:
|
|
// Name UniString Variable The "name" element of the info field
|
|
// ValueType uint32 4 The type of the following ValueData field
|
|
// ValueData ValueData Variable
|
|
|
|
// ValueData for ValueType 1 (string field)
|
|
// Value UniString Variable The value element of type string of the the info field
|
|
|
|
// ValueData for ValueType 2 (binary buffer field)
|
|
// BufferLen uint32 4 The length of the binary buffer
|
|
// BufferData Bytes Variable The data for the binary buffer
|
|
|
|
|
|
// UniString:
|
|
// StringLen uint32 4 The length of the string data in BYTES (null terminator NOT included)
|
|
// StringData Bytes StringLen An utf8 encoded string (do NOT write the NULL terminator)
|
|
|
|
// Bytes:
|
|
// Byte uint8 1 A byte
|
|
// Byte uint8 1 A byte
|
|
// .... .... ....
|
|
|
|
// DataField:
|
|
// FieldType uint32 4 The type of the field, see below for defined values
|
|
// FieldLen uint32 4 FieldData length in bytes (useful for skipping a field if unsupported)
|
|
// FieldData Variable FieldLen The data of the field, see below for defined values
|
|
|
|
// FieldData for FieldType 1 (file field)
|
|
// Flags uint32 4 Bitmask. Bits: 1=FileIsDeflated
|
|
// Path UniString Variable A relative path expressed as utf8 string. \ AND / are considered to be separators
|
|
// Size uint32 4 Size of the following file data
|
|
// FilePayload Bytes Variable
|
|
|
|
// Everything is stored in LITTLE ENDIAN byte order.
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Da Base Engine
|
|
|
|
KviPackageIOEngine::KviPackageIOEngine()
|
|
{
|
|
m_pProgressDialog = 0;
|
|
m_pStringInfoFields = new KviPointerHashTable<TQString,TQString>();
|
|
m_pStringInfoFields->setAutoDelete(true);
|
|
m_pBinaryInfoFields = new KviPointerHashTable<TQString,TQByteArray>();
|
|
m_pBinaryInfoFields->setAutoDelete(true);
|
|
}
|
|
|
|
KviPackageIOEngine::~KviPackageIOEngine()
|
|
{
|
|
if(m_pProgressDialog)delete m_pProgressDialog;
|
|
delete m_pStringInfoFields;
|
|
delete m_pBinaryInfoFields;
|
|
}
|
|
|
|
|
|
bool KviPackageIOEngine::updateProgress(int iProgress,const TQString &szLabel)
|
|
{
|
|
if(!m_pProgressDialog)return true;
|
|
#ifdef COMPILE_USE_QT4
|
|
m_pProgressDialog->setValue(iProgress);
|
|
#else
|
|
m_pProgressDialog->setProgress(iProgress);
|
|
#endif
|
|
m_pProgressDialogLabel->setText(szLabel);
|
|
tqApp->processEvents();
|
|
if(m_pProgressDialog->wasCanceled())
|
|
{
|
|
setLastError(__tr2qs("Operation cancelled"));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void KviPackageIOEngine::showProgressDialog(const TQString &szCaption,int iTotalSteps)
|
|
{
|
|
#ifdef COMPILE_USE_QT4
|
|
m_pProgressDialog = new TQProgressDialog(TQString(""),__tr2qs("Cancel"),0,iTotalSteps,0);
|
|
m_pProgressDialog->setModal(true);
|
|
m_pProgressDialog->setWindowTitle(szCaption);
|
|
#else
|
|
m_pProgressDialog = new TQProgressDialog(TQString(""),__tr2qs("Cancel"),iTotalSteps,0,"",true);
|
|
m_pProgressDialog->setCaption(szCaption);
|
|
#endif
|
|
m_pProgressDialogLabel = new TQLabel(m_pProgressDialog);
|
|
m_pProgressDialogLabel->setMaximumSize(500,300);
|
|
m_pProgressDialog->setLabel(m_pProgressDialogLabel);
|
|
}
|
|
|
|
void KviPackageIOEngine::hideProgressDialog()
|
|
{
|
|
if(!m_pProgressDialog)return;
|
|
delete m_pProgressDialog;
|
|
m_pProgressDialog = 0;
|
|
}
|
|
|
|
bool KviPackageIOEngine::writeError()
|
|
{
|
|
setLastError(__tr2qs("File write error"));
|
|
return false;
|
|
}
|
|
|
|
bool KviPackageIOEngine::readError()
|
|
{
|
|
setLastError(__tr2qs("File read error"));
|
|
return false;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Da Writer
|
|
|
|
|
|
KviPackageWriter::KviPackageWriter()
|
|
: KviPackageIOEngine()
|
|
{
|
|
m_pDataFields = new KviPointerList<DataField>();
|
|
m_pDataFields->setAutoDelete(true);
|
|
}
|
|
|
|
KviPackageWriter::~KviPackageWriter()
|
|
{
|
|
delete m_pDataFields;
|
|
}
|
|
|
|
void KviPackageWriter::addInfoField(const TQString &szName,const TQString &szValue)
|
|
{
|
|
m_pStringInfoFields->replace(szName,new TQString(szValue));
|
|
}
|
|
|
|
void KviPackageWriter::addInfoField(const TQString &szName,TQByteArray * pValue)
|
|
{
|
|
m_pBinaryInfoFields->replace(szName,pValue);
|
|
}
|
|
|
|
bool KviPackageWriter::addFile(const TQString &szLocalFileName,const TQString &szTargetFileName,kvi_u32_t uAddFileFlags)
|
|
{
|
|
TQFileInfo fi(szLocalFileName);
|
|
return addFileInternal(&fi,szLocalFileName,szTargetFileName,uAddFileFlags);
|
|
}
|
|
|
|
bool KviPackageWriter::addFileInternal(const TQFileInfo * fi,const TQString &szLocalFileName,const TQString &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
|
|
}
|
|
|
|
DataField * f = new DataField();
|
|
f->m_uType = KVI_PACKAGE_DATAFIELD_TYPE_FILE;
|
|
f->m_bFileAllowCompression = !(uAddFileFlags & NoCompression);
|
|
f->m_szFileLocalName = szLocalFileName;
|
|
f->m_szFileTargetName = szTargetFileName;
|
|
m_pDataFields->append(f);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KviPackageWriter::addDirectory(const TQString &szLocalDirectoryName,const TQString &szTargetDirectoryPrefix,kvi_u32_t uAddFileFlags)
|
|
{
|
|
TQDir d(szLocalDirectoryName);
|
|
#ifdef COMPILE_USE_QT4
|
|
TQDir::Filters iFlags;
|
|
#else
|
|
int iFlags;
|
|
#endif
|
|
iFlags = TQDir::Files | TQDir::Readable;
|
|
if(!(uAddFileFlags & FollowSymLinks))
|
|
iFlags |= TQDir::NoSymLinks;
|
|
|
|
// QT4SUX: Because the TQDir::entryInfoList() breaks really a lot of code by returning an object that behaves in a _totally_ different way.. it's also much slower
|
|
|
|
#ifdef COMPILE_USE_QT4
|
|
int j;
|
|
TQFileInfoList sl = d.entryInfoList(iFlags);
|
|
for(j=0;j<sl.size();j++)
|
|
{
|
|
#else
|
|
const TQFileInfoList * sl = d.entryInfoList(iFlags);
|
|
if(!sl)return false;
|
|
TQFileInfoListIterator it(*sl);
|
|
while(TQFileInfo * fi = it.current())
|
|
{
|
|
#endif
|
|
TQString szSFileName = szLocalDirectoryName;
|
|
KviTQString::ensureLastCharIs(szSFileName,TQChar(KVI_PATH_SEPARATOR_CHAR));
|
|
#ifdef COMPILE_USE_QT4
|
|
TQFileInfo slowCopy = sl.at(j);
|
|
szSFileName += slowCopy.fileName();
|
|
#else
|
|
szSFileName += fi->fileName();
|
|
#endif
|
|
TQString szDFileName = szTargetDirectoryPrefix;
|
|
KviTQString::ensureLastCharIs(szDFileName,TQChar(KVI_PATH_SEPARATOR_CHAR));
|
|
#ifdef COMPILE_USE_QT4
|
|
szDFileName += slowCopy.fileName();
|
|
if(!addFileInternal(&slowCopy,szSFileName,szDFileName,uAddFileFlags))
|
|
return false;
|
|
#else
|
|
szDFileName += fi->fileName();
|
|
if(!addFileInternal(fi,szSFileName,szDFileName,uAddFileFlags))
|
|
return false;
|
|
#endif
|
|
#ifndef COMPILE_USE_QT4
|
|
++it;
|
|
#endif
|
|
}
|
|
iFlags = TQDir::Dirs | TQDir::Readable;
|
|
if(!(uAddFileFlags & FollowSymLinks))
|
|
iFlags |= TQDir::NoSymLinks;
|
|
sl = d.entryInfoList(iFlags);
|
|
#ifdef COMPILE_USE_QT4
|
|
for(j=0;j<sl.size();j++)
|
|
{
|
|
TQString szDir = sl.at(j).fileName();
|
|
#else
|
|
if(!sl)return false;
|
|
TQFileInfoListIterator it2(*sl);
|
|
while(TQFileInfo * fi2 = it2.current())
|
|
{
|
|
TQString szDir = fi2->fileName();
|
|
#endif
|
|
if(!KviTQString::equalCS(szDir,"..") && !KviTQString::equalCS(szDir,"."))
|
|
{
|
|
TQString szSDirName = szLocalDirectoryName;
|
|
KviTQString::ensureLastCharIs(szSDirName,TQChar(KVI_PATH_SEPARATOR_CHAR));
|
|
szSDirName += szDir;
|
|
TQString szDDirName = szTargetDirectoryPrefix;
|
|
KviTQString::ensureLastCharIs(szDDirName,TQChar(KVI_PATH_SEPARATOR_CHAR));
|
|
szDDirName += szDir;
|
|
if(!addDirectory(szSDirName,szDDirName,uAddFileFlags))
|
|
return false;
|
|
}
|
|
#ifndef COMPILE_USE_QT4
|
|
++it2;
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
#define BUFFER_SIZE 32768
|
|
|
|
bool KviPackageWriter::packFile(KviFile * pFile,DataField * pDataField)
|
|
{
|
|
TQString szProgressText;
|
|
KviTQString::sprintf(szProgressText,__tr2qs("Packaging file %Q"),&(pDataField->m_szFileLocalName));
|
|
if(!updateProgress(m_iCurrentProgress,szProgressText))
|
|
return false; // aborted
|
|
|
|
|
|
KviFile source(pDataField->m_szFileLocalName);
|
|
if(!source.openForReading())
|
|
{
|
|
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();
|
|
|
|
KviTQCString szTargetFileName = KviTQString::toUtf8(pDataField->m_szFileTargetName);
|
|
|
|
// 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.readBlock((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->writeBlock((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.readBlock((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)
|
|
{
|
|
TQString szTmp;
|
|
KviTQString::sprintf(szTmp,TQString(" (%d of %d bytes)"),zstr.total_in,uSize);
|
|
TQString szPrg = szProgressText + szTmp;
|
|
if(!updateProgress(m_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->writeBlock((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.readBlock((char *)buffer,BUFFER_SIZE);
|
|
if(iReaded < 0)
|
|
return readError();
|
|
while(iReaded > 0)
|
|
{
|
|
iTotalFileSize += iReaded;
|
|
if((iTotalFileSize % 1000000) == 0)
|
|
{
|
|
TQString szTmp;
|
|
KviTQString::sprintf(szTmp,TQString(" (%d of %d bytes)"),iTotalFileSize,uSize);
|
|
TQString szPrg = szProgressText + szTmp;
|
|
if(!updateProgress(m_iCurrentProgress,szPrg))
|
|
return false; // aborted
|
|
}
|
|
pDataField->m_uWrittenFieldLength += iReaded;
|
|
if(pFile->writeBlock((char *)buffer,iReaded) != iReaded)
|
|
return writeError();
|
|
iReaded = source.readBlock((char *)buffer,BUFFER_SIZE);
|
|
}
|
|
#ifdef COMPILE_ZLIB_SUPPORT
|
|
}
|
|
#endif
|
|
source.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KviPackageWriter::pack(const TQString &szFileName,kvi_u32_t uPackFlags)
|
|
{
|
|
m_iCurrentProgress = 0;
|
|
if(!(uPackFlags & NoProgressDialog))
|
|
{
|
|
showProgressDialog(__tr2qs("Creating package..."),100);
|
|
updateProgress(m_iCurrentProgress,__tr2qs("Writing package header"));
|
|
}
|
|
|
|
bool bRet = packInternal(szFileName,uPackFlags);
|
|
|
|
hideProgressDialog();
|
|
return bRet;
|
|
}
|
|
|
|
bool KviPackageWriter::packInternal(const TQString &szFileName,kvi_u32_t uPackFlags)
|
|
{
|
|
|
|
KviFile f(szFileName);
|
|
if(!f.openForWriting())
|
|
{
|
|
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.writeBlock(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 = m_pStringInfoFields->count() + m_pBinaryInfoFields->count();
|
|
if(!f.save(uCount))return writeError();
|
|
|
|
m_iCurrentProgress = 5;
|
|
if(!updateProgress(m_iCurrentProgress,__tr2qs("Writing informational fields")))
|
|
return false; // aborted
|
|
|
|
// InfoFields (string)
|
|
KviPointerHashTableIterator<TQString,TQString> it(*m_pStringInfoFields);
|
|
while(TQString * 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<TQString,TQByteArray> it2(*m_pBinaryInfoFields);
|
|
while(TQByteArray * 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_iCurrentProgress = 10;
|
|
if(!updateProgress(m_iCurrentProgress,__tr2qs("Writing package data")))
|
|
return false; // aborted
|
|
|
|
// write PackageData
|
|
int iIdx = 0;
|
|
for(DataField * pDataField = m_pDataFields->first();pDataField;pDataField = m_pDataFields->next())
|
|
{
|
|
kvi_u32_t uDataFieldType = pDataField->m_uType;
|
|
if(!f.save(uDataFieldType))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(uDataFieldType))return writeError();
|
|
|
|
m_iCurrentProgress = 10 + ((90 * iIdx) / m_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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Da Reader
|
|
|
|
KviPackageReader::KviPackageReader()
|
|
: KviPackageIOEngine()
|
|
{
|
|
}
|
|
|
|
KviPackageReader::~KviPackageReader()
|
|
{
|
|
}
|
|
|
|
bool KviPackageReader::readHeaderInternal(KviFile * pFile,const TQString &szLocalFileName)
|
|
{
|
|
// read the PackageHeader
|
|
|
|
// Magic
|
|
char magic[4];
|
|
|
|
if(pFile->readBlock(magic,4) != 4)return readError();
|
|
if((magic[0] != 'K') || (magic[1] != 'V') || (magic[2] != 'P') || (magic[3] != 'F'))
|
|
{
|
|
setLastError(__tr2qs("The file specified is not a valid KVIrc package"));
|
|
return false;
|
|
}
|
|
|
|
// Version
|
|
kvi_u32_t uVersion;
|
|
if(!pFile->load(uVersion))return readError();
|
|
if(uVersion != 0x1)
|
|
{
|
|
setLastError(__tr2qs("The package has an invalid version number, it might have been created by a newer KVIrc"));
|
|
return false;
|
|
}
|
|
|
|
// Flags
|
|
kvi_u32_t uFlags;
|
|
if(!pFile->load(uFlags))return readError();
|
|
// we ignore them at the moment
|
|
|
|
// read PackageInfo
|
|
|
|
// InfoFieldCount
|
|
kvi_u32_t uCount;
|
|
if(!pFile->load(uCount))return writeError();
|
|
|
|
m_pStringInfoFields->clear();
|
|
m_pBinaryInfoFields->clear();
|
|
|
|
kvi_u32_t uIdx = 0;
|
|
while(uIdx < uCount)
|
|
{
|
|
TQString szKey;
|
|
if(!pFile->load(szKey))return readError();
|
|
kvi_u32_t uFieldType;
|
|
if(!pFile->load(uFieldType))return readError();
|
|
switch(uFieldType)
|
|
{
|
|
case KVI_PACKAGE_INFOFIELD_TYPE_STRING:
|
|
{
|
|
TQString szValue;
|
|
if(!pFile->load(szValue))return readError();
|
|
m_pStringInfoFields->replace(szKey,new TQString(szValue));
|
|
}
|
|
break;
|
|
case KVI_PACKAGE_INFOFIELD_TYPE_BINARYBUFFER:
|
|
{
|
|
TQByteArray * pbValue = new TQByteArray();
|
|
if(!pFile->load(*pbValue))
|
|
{
|
|
delete pbValue;
|
|
return readError();
|
|
}
|
|
m_pBinaryInfoFields->replace(szKey,pbValue);
|
|
}
|
|
break;
|
|
default:
|
|
setLastError(__tr2qs("Invalid info field: the package is probably corrupt"));
|
|
break;
|
|
}
|
|
uIdx++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool KviPackageReader::readHeader(const TQString &szLocalFileName)
|
|
{
|
|
KviFile f(szLocalFileName);
|
|
if(!f.openForReading())
|
|
{
|
|
setLastError(__tr2qs("Can't open file for reading"));
|
|
return false;
|
|
}
|
|
|
|
return readHeaderInternal(&f,szLocalFileName);
|
|
}
|
|
|
|
bool KviPackageReader::unpackFile(KviFile * pFile,const TQString &szUnpackPath)
|
|
{
|
|
// Flags
|
|
kvi_u32_t uFlags;
|
|
if(!pFile->load(uFlags))return readError();
|
|
|
|
#ifndef COMPILE_ZLIB_SUPPORT
|
|
if(uFlags & KVI_PACKAGE_DATAFIELD_FLAG_FILE_DEFLATE)
|
|
{
|
|
setLastError(__tr2qs("The package contains compressed data but this executable does not support compression"));
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Path
|
|
TQString szPath;
|
|
if(!pFile->load(szPath))return readError();
|
|
|
|
TQString szFileName = szUnpackPath;
|
|
KviTQString::ensureLastCharIs(szFileName,TQChar(KVI_PATH_SEPARATOR_CHAR));
|
|
|
|
szFileName += szPath;
|
|
|
|
// no attacks please :)
|
|
szFileName.replace(TQString("..\\"),TQString(""));
|
|
szFileName.replace(TQString("..//"),TQString(""));
|
|
|
|
KviFileUtils::adjustFilePath(szFileName);
|
|
|
|
int idx = KviTQString::findRev(szFileName,TQChar(KVI_PATH_SEPARATOR_CHAR));
|
|
if(idx != -1)
|
|
{
|
|
TQString szPrefixPath = szFileName.left(idx);
|
|
if(!KviFileUtils::makeDir(szPrefixPath))
|
|
{
|
|
setLastError(__tr2qs("Failed to create the target directory"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
KviFile dest(szFileName);
|
|
if(!dest.openForWriting())
|
|
{
|
|
setLastError(__tr2qs("Failed to open a source file for reading"));
|
|
return false;
|
|
}
|
|
|
|
TQString szProgressText;
|
|
KviTQString::sprintf(szProgressText,__tr2qs("Unpacking file %Q"),&szFileName);
|
|
if(!updateProgress(pFile->pos(),szProgressText))
|
|
return false; // aborted
|
|
|
|
// Size
|
|
kvi_u32_t uSize;
|
|
if(!pFile->load(uSize))return readError();
|
|
|
|
|
|
// FilePayload
|
|
#ifdef COMPILE_ZLIB_SUPPORT
|
|
if(uFlags & KVI_PACKAGE_DATAFIELD_FLAG_FILE_DEFLATE)
|
|
{
|
|
int iRemainingSize = uSize;
|
|
unsigned char ibuffer[BUFFER_SIZE];
|
|
unsigned char obuffer[BUFFER_SIZE];
|
|
|
|
int iToRead = iRemainingSize;
|
|
if(iToRead > BUFFER_SIZE)iToRead = BUFFER_SIZE;
|
|
int iReaded = pFile->readBlock((char *)ibuffer,iToRead);
|
|
iRemainingSize -= iReaded;
|
|
|
|
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(inflateInit(&zstr) != Z_OK)
|
|
{
|
|
setLastError(__tr2qs("Compression library initialization error"));
|
|
return false;
|
|
}
|
|
|
|
while((iReaded > 0) && (iRemainingSize > 0))
|
|
{
|
|
zstr.next_out = obuffer;
|
|
zstr.avail_out = BUFFER_SIZE;
|
|
|
|
if(inflate(&zstr,Z_NO_FLUSH) != Z_OK)
|
|
{
|
|
setLastError(__tr2qs("Compression library error"));
|
|
return false;
|
|
}
|
|
|
|
if(zstr.avail_out < BUFFER_SIZE)
|
|
{
|
|
int iDecompressed = zstr.next_out - obuffer;
|
|
if(dest.writeBlock((char *)obuffer,iDecompressed) != iDecompressed)
|
|
{
|
|
inflateEnd(&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);
|
|
}
|
|
}
|
|
|
|
if(iDataToRead > iRemainingSize)
|
|
iDataToRead = iRemainingSize;
|
|
|
|
iReaded = pFile->readBlock((char *)(ibuffer + zstr.avail_in),iDataToRead);
|
|
if(iReaded < 0)
|
|
{
|
|
inflateEnd(&zstr);
|
|
return readError();
|
|
}
|
|
|
|
iRemainingSize -= iReaded;
|
|
zstr.avail_in += iReaded;
|
|
zstr.next_in = ibuffer;
|
|
|
|
if((zstr.total_in % 2000000) == 0)
|
|
{
|
|
TQString szTmp;
|
|
KviTQString::sprintf(szTmp,TQString(" (%d of %d bytes)"),zstr.total_in,uSize);
|
|
TQString szPrg = szProgressText + szTmp;
|
|
if(!updateProgress(pFile->pos(),szPrg))
|
|
return false; // aborted
|
|
}
|
|
}
|
|
}
|
|
|
|
// flush pending output
|
|
zstr.next_out = obuffer;
|
|
zstr.avail_out = BUFFER_SIZE;
|
|
|
|
int ret;
|
|
|
|
do {
|
|
ret = inflate(&zstr,Z_FINISH);
|
|
|
|
if((ret == Z_OK) || (ret == Z_STREAM_END) || (ret == Z_BUF_ERROR))
|
|
{
|
|
if(zstr.avail_out < BUFFER_SIZE)
|
|
{
|
|
int iDecompressed = zstr.next_out - obuffer;
|
|
if(dest.writeBlock((char *)obuffer,iDecompressed) != iDecompressed)
|
|
{
|
|
inflateEnd(&zstr);
|
|
return writeError();
|
|
}
|
|
} /* else { THIS HAPPENS FOR ZERO SIZE FILES
|
|
debug("hum.... internal, rEWq (ret = %d) (avail_out = %d)",ret,zstr.avail_out);
|
|
|
|
inflateEnd(&zstr);
|
|
setLastError(__tr2qs("Compression library internal error"));
|
|
return false;
|
|
} */
|
|
zstr.next_out = obuffer;
|
|
zstr.avail_out = BUFFER_SIZE;
|
|
}
|
|
|
|
} while((ret == Z_OK) || (ret == Z_BUF_ERROR));
|
|
|
|
inflateEnd(&zstr);
|
|
|
|
if(ret != Z_STREAM_END)
|
|
{
|
|
setLastError(__tr2qs("Error in compressed file stream"));
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
#endif
|
|
unsigned char buffer[BUFFER_SIZE];
|
|
int iTotalFileSize = 0;
|
|
int iRemainingData = uSize;
|
|
int iToRead = iRemainingData;
|
|
if(iToRead > BUFFER_SIZE)iToRead = BUFFER_SIZE;
|
|
int iReaded = 1;
|
|
|
|
while((iReaded > 0) && (iToRead > 0))
|
|
{
|
|
iReaded = pFile->readBlock((char *)buffer,iToRead);
|
|
if(iReaded > 0)
|
|
{
|
|
iTotalFileSize += iReaded;
|
|
iRemainingData -= iReaded;
|
|
|
|
if((iTotalFileSize % 3000000) == 0)
|
|
{
|
|
TQString szTmp;
|
|
KviTQString::sprintf(szTmp,TQString(" (%d of %d bytes)"),iTotalFileSize,uSize);
|
|
TQString szPrg = szProgressText + szTmp;
|
|
if(!updateProgress(pFile->pos(),szPrg))
|
|
return false; // aborted
|
|
}
|
|
|
|
if(dest.writeBlock((char *)buffer,iReaded) != iReaded)
|
|
return writeError();
|
|
}
|
|
|
|
int iToRead = iRemainingData;
|
|
if(iToRead > BUFFER_SIZE)iToRead = BUFFER_SIZE;
|
|
}
|
|
#ifdef COMPILE_ZLIB_SUPPORT
|
|
}
|
|
#endif
|
|
dest.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KviPackageReader::getStringInfoField(const TQString &szName,TQString &szBuffer)
|
|
{
|
|
TQString * pVal = m_pStringInfoFields->find(szName);
|
|
if(!pVal)return false;
|
|
szBuffer = *pVal;
|
|
return true;
|
|
}
|
|
|
|
bool KviPackageReader::unpack(const TQString &szLocalFileName,const TQString &szUnpackPath,kvi_u32_t uUnpackFlags)
|
|
{
|
|
bool bRet = unpackInternal(szLocalFileName,szUnpackPath,uUnpackFlags);
|
|
hideProgressDialog();
|
|
return bRet;
|
|
}
|
|
|
|
bool KviPackageReader::unpackInternal(const TQString &szLocalFileName,const TQString &szUnpackPath,kvi_u32_t uUnpackFlags)
|
|
{
|
|
|
|
KviFile f(szLocalFileName);
|
|
if(!f.openForReading())
|
|
{
|
|
setLastError(__tr2qs("Can't open file for reading"));
|
|
return false;
|
|
}
|
|
|
|
kvi_file_offset_t size = f.size();
|
|
|
|
if(!(uUnpackFlags & NoProgressDialog))
|
|
{
|
|
showProgressDialog(__tr2qs("Reading package..."),size);
|
|
updateProgress(0,__tr2qs("Reading package header"));
|
|
}
|
|
|
|
|
|
if(!readHeaderInternal(&f,szLocalFileName))
|
|
return false;
|
|
|
|
if(!updateProgress(f.pos(),__tr2qs("Reading package data")))
|
|
return false; // aborted
|
|
|
|
while(!f.atEnd())
|
|
{
|
|
// DataFieldType
|
|
kvi_u32_t uDataFieldType;
|
|
if(!f.load(uDataFieldType))return readError();
|
|
// DataFieldLen
|
|
kvi_u32_t uDataFieldLen;
|
|
if(!f.load(uDataFieldLen))return readError();
|
|
|
|
switch(uDataFieldType)
|
|
{
|
|
case KVI_PACKAGE_DATAFIELD_TYPE_FILE:
|
|
if(!unpackFile(&f,szUnpackPath))
|
|
return false;
|
|
break;
|
|
default:
|
|
setLastError(__tr2qs("Invalid data field: the package is probably corrupt"));
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|