diff options
Diffstat (limited to 'util/install/archive/qarchive.cpp')
-rw-r--r-- | util/install/archive/qarchive.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/util/install/archive/qarchive.cpp b/util/install/archive/qarchive.cpp new file mode 100644 index 0000000..dd51c65 --- /dev/null +++ b/util/install/archive/qarchive.cpp @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the utils of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qarchive.h" +#include <qdatastream.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qapplication.h> +#include "../../../src/3rdparty/zlib/zlib.h" +#include "../keygen/keyinfo.h" +#ifdef Q_OS_UNIX +# include <sys/stat.h> +# include <unistd.h> +# include <sys/types.h> +# include <utime.h> +#endif + +enum ChunkType { + ChunkDirectory = 0, + ChunkFile = 1, + ChunkSymlink = 2, + ChunkBeginHeader = 3, + ChunkEndHeader = 4 +}; + +static bool createDir( const QString& fullPath ) +{ + QStringList hierarchy = QStringList::split( QDir::separator(), fullPath ); + QString pathComponent, tmpPath; + QDir dirTmp; +#ifdef Q_OS_UNIX + dirTmp = "/"; +#endif + + for( QStringList::Iterator it = hierarchy.begin(); it != hierarchy.end(); ++it ) { + pathComponent = *it + QDir::separator(); + tmpPath += pathComponent; + if (!dirTmp.exists(tmpPath) && !dirTmp.mkdir(tmpPath)) + return false; + } + return true; +} + +QArchive::QArchive( const QString& archivePath ) +{ + setPath( archivePath ); + + bufferSize = 512 * 1024; +} + +QArchive::~QArchive() +{ +} + +void QArchive::setPath( const QString& archivePath ) +{ + QString fullName = archivePath; + if( fullName.right( 4 ) != ".arq" ) + fullName += ".arq"; + arcFile.setName( fullName ); +} + +bool QArchive::open( int mode ) +{ + switch( mode ) { + case IO_ReadOnly: + // Fallthrough intentional + case IO_WriteOnly: + if( arcFile.open( mode ) ) + return true; + break; + } + return false; +} + +void QArchive::close() +{ + if( arcFile.isOpen() ) + arcFile.close(); +} + +bool QArchive::writeFile( const QString& fileName, const QString& localPath ) +{ + if( arcFile.isOpen() ) { + QDataStream outStream( &arcFile ); + QFileInfo fi( fileName ); + + QFile inFile( fi.absFilePath() ); + QByteArray inBuffer; + QByteArray outBuffer( bufferSize ); + z_stream ztream; + bool continueCompressing; + + if(symbolicLinks() && fi.isSymLink()) { + outStream << (int)ChunkSymlink; + outStream << fi.fileName().latin1(); + outStream << fi.readLink().latin1(); + } else if( inFile.open( IO_ReadOnly ) ) { + if( inBuffer.resize( fi.size() ) ) { + outStream << (int)ChunkFile; + outStream << fi.fileName().latin1(); + outStream << fi.lastModified(); + { + int perm = -1; +#ifdef Q_OS_UNIX + struct stat st; + if(!::stat(fi.filePath().latin1(), &st)) + perm = (int)st.st_mode; +#endif + outStream << perm; + } + if( verbosityMode & Source ) + emit operationFeedback( "Deflating " + fi.absFilePath() + "..." ); + else if( verbosityMode & Destination ) + emit operationFeedback( "Deflating " + localPath + "/" + fi.fileName() + "..." ); + ztream.next_in = (unsigned char*)inBuffer.data(); + ztream.avail_in = inBuffer.size(); + ztream.total_in = 0; + ztream.next_out = (unsigned char*)outBuffer.data(); + ztream.avail_out = outBuffer.size(); + ztream.total_out = 0; + ztream.msg = NULL; + ztream.zalloc = (alloc_func)NULL; + ztream.zfree = (free_func)NULL; + ztream.opaque = (voidpf)NULL; + ztream.data_type = Z_BINARY; + deflateInit( &ztream, 9 ); + if ( inBuffer.data() ) + inFile.readBlock( inBuffer.data(), inBuffer.size() ); + + continueCompressing = true; + while( continueCompressing ) { + if(qApp) + qApp->processEvents(); + continueCompressing = ( deflate( &ztream, Z_FINISH ) == Z_OK ); + if( !ztream.avail_out ) { + if( !outBuffer.resize( outBuffer.size() + bufferSize ) ) + qFatal( "Could not allocate compression buffer!" ); + ztream.next_out = (unsigned char*)&outBuffer.data()[ ztream.total_out ]; + ztream.avail_out = bufferSize; + } + } + + emit operationFeedback( QString( "done. %1 => %2 (%3%)\n" ) + .arg( ztream.total_in ) + .arg( ztream.total_out ) + .arg( int( + double( ztream.total_out ) / double( ztream.total_in ) * 100 ) ) ); + deflateEnd( &ztream ); + // Now write the compressed data to the output + outStream << ztream.total_out; + outStream.writeRawBytes( outBuffer.data(), ztream.total_out ); + } + inFile.close(); + return true; + } else { + return false; + } + } + return false; +} + +bool QArchive::setDirectory( const QString& dirName ) +{ + if( arcFile.isOpen() ) { + QString fullName = dirName; + QDataStream outStream( &arcFile ); + if( fullName.right( 1 ) != "/" ) + fullName += "/"; + outStream << (int)ChunkDirectory; + outStream << fullName.latin1(); + return true; + } + return false; +} + +bool QArchive::writeHeader( const QArchiveHeader header ) +{ + if( arcFile.isOpen() ) { + QDataStream outStream( &arcFile ); + outStream << (int)ChunkBeginHeader; + outStream << header.mayorVersion(); + outStream << header.minorVersion(); + outStream << header.features(); + outStream << header.description(); + outStream << header.extraData; + outStream << (int)ChunkEndHeader; + return true; + } + return false; +} + +bool QArchive::writeDir( const QString &dirName1, bool includeLastComponent, const QString &localPath1 ) +{ + if( arcFile.isOpen() ) { + QString localPath = localPath1, dirName = dirName1; + if(localPath.right(1) == "/") + localPath.truncate(localPath.length()-1); + if(dirName.right(1) == "/") + dirName.truncate(dirName.length()-1); + + QFileInfo fi( dirName ); + + if( includeLastComponent ) + setDirectory( fi.fileName() ); + QDir dir( dirName ); + const QFileInfoList* dirEntries = dir.entryInfoList(); + QFileInfoListIterator dirIter( *dirEntries ); + QDataStream outStream( &arcFile ); + QFileInfo* pFi; + + dirIter.toLast(); + while( ( pFi = dirIter.current() ) ) { + if( pFi->fileName() != "." && pFi->fileName() != ".." ) { + if( pFi->isDir() ) + writeDir( pFi->absFilePath(), true, localPath + "/" + + pFi->fileName() ); // Subdirs should always get its name in the archive. + else + writeFile( pFi->absFilePath(), localPath ); + } + --dirIter; + } + setDirectory( ".." ); + return true; + } + return false; +} + +bool QArchive::writeFileList( const QStringList fileList ) +{ + for( QStringList::ConstIterator it = fileList.begin(); it != fileList.end(); ++it ) { + if( !writeFile( (*it) ) ) + return false; + } + return true; +} + +bool QArchive::writeDirList( const QStringList dirList, bool includeLastComponent ) +{ + for( QStringList::ConstIterator it = dirList.begin(); it != dirList.end(); ++it ) { + QString lastComponent = (*it).mid( (*it).findRev( "/" ) + 1 ); + if( !writeDir( (*it), includeLastComponent, lastComponent ) ) + return false; + } + return true; +} + +void QArchive::setVerbosity( int verbosity ) +{ + verbosityMode = verbosity; +} + +QArchiveHeader* QArchive::readArchiveHeader() +{ + QDataStream inStream( &arcFile ); + return readArchiveHeader( &inStream ); +} + +/* + Reads the archive header and returns it on success. If an error occurs, it + returns 0. The caller has to delete the object. +*/ +QArchiveHeader* QArchive::readArchiveHeader( QDataStream *inStream ) +{ + int chunktype; + QArchiveHeader *header = new QArchiveHeader; + + *inStream >> chunktype; + if( chunktype == ChunkBeginHeader ) { + *inStream >> header->_mayorVersion; + *inStream >> header->_minorVersion; + if ( header->mayorVersion()!=1 || header->minorVersion()!=0 ) { + emit operationFeedback( "Incompatible package version" ); + delete header; + return 0; + } + *inStream >> header->_features; + *inStream >> header->_description; + *inStream >> header->extraData; + *inStream >> chunktype; + if ( chunktype != ChunkEndHeader ) { + emit operationFeedback( "Invalid package header" ); + delete header; + return 0; + } + } else { + emit operationFeedback( "No package header found." ); + delete header; + return 0; + } + return header; +} + +bool QArchive::readArchive( const QString &outpath, const QString &key ) +{ + QDataStream inStream( &arcFile ); + return readArchive( &inStream, outpath, key ); +} + +bool QArchive::readArchive( QDataStream *inStream, const QString &outpath, const QString &key ) +{ + QDataStream outStream; + QFile outFile; + QDir outDir; + QByteArray inBuffer; + QByteArray outBuffer( bufferSize ); + z_stream ztream; + bool continueDeCompressing; + QString entryName, dirName, symName; + int entryLength, chunktype; + + //get the key + QArchiveHeader *header = readArchiveHeader( inStream ); + if ( header == 0 ) + return false; + uint infeatures = featuresForKey( key ); + if( (header->features() & infeatures) != header->features()) { + emit operationFeedback( "Invalid key" ); + return false; + } + + // Set up the initial directory. + // If the dir does not exist, try to create it + dirName = QDir::toNativeSeparators( outpath ); + outDir.setPath( dirName ); + if( !outDir.exists( dirName ) && !createDir( dirName ) ) + return false; + outDir.cd( dirName ); + + while( !inStream->atEnd() ) { + //get our type + *inStream >> chunktype; + if(chunktype == ChunkDirectory) { + *inStream >> entryLength; + inBuffer.resize( entryLength ); + inStream->readRawBytes( inBuffer.data(), entryLength ); + entryName = inBuffer.data(); + + if( verbosityMode & Source ) + emit operationFeedback( "Directory " + entryName + "... " ); + if( entryName == "../" ) { + outDir.cdUp(); + } else { + dirName = QDir::toNativeSeparators( outDir.absPath() + + QString( "/" ) + entryName.left( entryName.length() - 1 ) ); + if( verbosityMode & Destination ) + emit operationFeedback( "Directory " + dirName + "... " ); + + if( !outDir.exists( dirName ) && !createDir( dirName ) ) { + emit operationFeedback( "Cannot create directory: " + dirName ); + return false; + } + outDir.cd( dirName ); + } + } else if(chunktype == ChunkFile) { + *inStream >> entryLength; + inBuffer.resize( entryLength ); + inStream->readRawBytes( inBuffer.data(), entryLength ); + entryName = inBuffer.data(); + + int filePerm; + QDateTime timeStamp; + QString fileName = QDir::toNativeSeparators( outDir.absPath() + QString( "/" ) + entryName ); + outFile.setName( fileName ); + if( outFile.open( IO_WriteOnly ) ) { + *inStream >> timeStamp; // Get timestamp from the archive + *inStream >> filePerm; + outStream.setDevice( &outFile ); + *inStream >> entryLength; + if( verbosityMode & Source ) + emit operationFeedback( "Expanding " + entryName + "..." ); + else if( verbosityMode & Destination ) + emit operationFeedback( "Expanding " + fileName + "..." ); + inBuffer.resize( entryLength ); + inStream->readRawBytes( inBuffer.data(), entryLength ); + ztream.next_in = (unsigned char*)inBuffer.data(); + ztream.avail_in = entryLength; + ztream.total_in = 0; + ztream.msg = NULL; + ztream.zalloc = (alloc_func)0; + ztream.zfree = (free_func)0; + ztream.opaque = (voidpf)0; + ztream.data_type = Z_BINARY; + inflateInit( &ztream ); + continueDeCompressing = true; + while( continueDeCompressing ) { + ztream.next_out = (unsigned char*)outBuffer.data(); + ztream.avail_out = outBuffer.size(); + ztream.total_out = 0; + continueDeCompressing = ( inflate( &ztream, Z_NO_FLUSH ) == Z_OK ); + outStream.writeRawBytes( outBuffer.data(), ztream.total_out ); + } + inflateEnd( &ztream ); + outFile.close(); +#ifdef Q_OS_UNIX + QDateTime t; t.setTime_t(0); //epoch + struct utimbuf tb; + tb.actime = tb.modtime = t.secsTo(timeStamp); + utime(fileName.local8Bit(), &tb); + if(filePerm != -1) + chmod(fileName.local8Bit(), (mode_t)filePerm); +#endif + } else { + emit operationFeedback( "Cannot open: " + fileName ); + return false; + } + } else if(chunktype == ChunkSymlink) { + *inStream >> entryLength; + inBuffer.resize( entryLength ); + inStream->readRawBytes( inBuffer.data(), entryLength ); + entryName = inBuffer.data(); + QString fileName = QDir::toNativeSeparators( outDir.absPath() + QString( "/" ) + entryName ); + + *inStream >> entryLength; + inBuffer.resize( entryLength ); + inStream->readRawBytes( inBuffer.data(), entryLength ); + symName = inBuffer.data(); + if( verbosityMode & Source ) + emit operationFeedback( "Linking " + symName + "... " ); + else if( verbosityMode & Destination ) + emit operationFeedback( "Linking " + fileName + "... " ); +#ifdef Q_OS_UNIX + symlink( symName.local8Bit(), fileName.local8Bit() ); +#endif + } else { + if( verbosityMode & Source ) + emit operationFeedback( QString("Unknown chunk: %d") .arg(chunktype) ); + } + if( verbosityMode & Progress ) + emit operationFeedback( inStream->device()->at() ); + } + return true; +} + |