diff options
Diffstat (limited to 'installerbuilder/libinstaller/3rdparty/p7zip_9.04/lib7z_facade.cpp')
-rw-r--r-- | installerbuilder/libinstaller/3rdparty/p7zip_9.04/lib7z_facade.cpp | 1253 |
1 files changed, 1253 insertions, 0 deletions
diff --git a/installerbuilder/libinstaller/3rdparty/p7zip_9.04/lib7z_facade.cpp b/installerbuilder/libinstaller/3rdparty/p7zip_9.04/lib7z_facade.cpp new file mode 100644 index 000000000..f1dd2e439 --- /dev/null +++ b/installerbuilder/libinstaller/3rdparty/p7zip_9.04/lib7z_facade.cpp @@ -0,0 +1,1253 @@ +#include "lib7z_facade.h" + +#include "StdAfx.h" + +#include "Common/MyInitGuid.h" + +#include "Common/CommandLineParser.h" +#include "Common/IntToString.h" +#include "Common/MyException.h" +#include "Common/StdOutStream.h" +#include "Common/StringConvert.h" +#include "Common/StringToInt.h" + +#include "Windows/Defs.h" +#include "Windows/Error.h" +#include "Windows/FileDir.h" +#include "Windows/FileName.h" + +#include "7zip/ICoder.h" +#include "7zip/IPassword.h" + +#include "7zip/UI/Common/ArchiveCommandLine.h" +#include "7zip/UI/Common/ExitCode.h" +#include "7zip/UI/Common/Extract.h" +#include "7zip/UI/Common/Update.h" +#include "7zip/UI/Common/ArchiveExtractCallback.h" + +#include "Windows/Defs.h" +#include "Windows/Error.h" +#include "Windows/FileDir.h" +#include "Windows/FileName.h" + +#include "7zip/UI/Common/LoadCodecs.h" +#include "7zip/UI/Common/PropIDUtils.h" +#include "Windows/PropVariant.h" +#include "Windows/PropVariantConversions.h" + +#include <common/errors.h> +#include <common/fileutils.h> + +#include <QCoreApplication> +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QIODevice> +#include <QPointer> +#include <QString> +#include <QStringList> +#include <QTemporaryFile> + +#ifdef Q_WS_WIN +#include <time.h> +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */ +#define S_IFMT 00170000 +#define S_IFLNK 0120000 +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) + +typedef BOOL (WINAPI *CREATEHARDLINK)(LPCSTR dst, LPCSTR str, LPSECURITY_ATTRIBUTES sa); + +bool CreateHardLinkWrapper( const QString& dest, const QString& file ) +{ + static HMODULE module = 0; + static CREATEHARDLINK proc = 0; + + if( module == 0 ) + module = LoadLibrary( L"kernel32.dll" ); + if( module == 0 ) + return false; + if( proc == 0 ) + proc = (CREATEHARDLINK) GetProcAddress( module, "CreateHardLinkA" ); + if( proc == 0 ) + return false; + QString target = file; + if( !QFileInfo( file ).isAbsolute() ) + { + target = QFileInfo( dest ).dir().absoluteFilePath( file ); + } + const QString link = QDir::toNativeSeparators( dest ); + const QString existingFile = QDir::toNativeSeparators( target ); + return proc( link.toLocal8Bit(), existingFile.toLocal8Bit(), 0 ); +} + +#else +#include <sys/stat.h> +#endif + +#include <iostream> +#include <memory> + +#include <cassert> + +using namespace Lib7z; +using namespace NWindows; + +namespace { + /** + * RAII class to create a directory (tryCreate()) and delete it on destruction unless released. + */ + struct DirectoryGuard { + explicit DirectoryGuard( const QString& path ) + : m_path( path ), + m_created( false ), + m_released( false ) + { + static const QRegExp re( QLatin1String( "\\\\|/" ) ); + static const QLatin1String sep( "/" ); + m_path.replace( re, sep ); + } + ~DirectoryGuard() { + if ( !m_created || m_released ) + return; + QDir dir( m_path ); + if ( !dir.rmdir( m_path ) ) + qWarning() << "Could not delete directory " << m_path; + } + + /** + * Tries to create the directorie structure. + * Returns a list of every directory created. + */ + QStringList tryCreate() { + if( m_path.isEmpty() ) + return QStringList(); + + const QFileInfo fi( m_path ); + if ( fi.exists() && fi.isDir() ) + return QStringList(); + if ( fi.exists() && !fi.isDir() ) + throw SevenZipException( QObject::tr("Path exists but is not a folder: %1").arg( m_path ) ); + + QStringList created; + + QDir toCreate( m_path ); + while( !toCreate.exists() ) + { + QString p = toCreate.absolutePath(); + created.push_front( p ); + p = p.section( QLatin1Char( '/' ), 0, -2 ); + toCreate = QDir( p ); + } + + QDir dir( m_path ); + m_created = dir.mkpath( m_path ); + if ( !m_created ) + throw SevenZipException( QObject::tr("Could not create folder: %1").arg( m_path ) ); + + return created; + } + + void release() { + m_released = true; + } + + QString m_path; + bool m_created; + bool m_released; + }; +} + +static void throwIfNotOK( HRESULT result, const QString& msg ) { + if ( result != S_OK ) + throw SevenZipException( msg ); +} + +static UString QString2UString( const QString& str ) { + return str.toStdWString().c_str(); +} + +static QString UString2QString( const UString& str ) { + return QString::fromStdWString( static_cast<const wchar_t*>( str ) ); +} + +static QString generateTempFileName() { + QTemporaryFile tmp; + if ( !tmp.open() ) + throw SevenZipException( QObject::tr("Could not create temporary file") ); + return QDir::toNativeSeparators( tmp.fileName() ); +} + +/* +static QStringList UStringVector2QStringList( const UStringVector& vec ) { + QStringList res; + for( int i = 0; i < vec.Size(); ++i ) + res += UString2QString( vec[i] ); + return res; +} +*/ + +static NCOM::CPropVariant readProperty( IInArchive* archive, int index, int propId ) { + NCOM::CPropVariant prop; + throwIfNotOK( archive->GetProperty(index, propId, &prop), QObject::tr("Could not retrieve property %1 for item %2").arg( QString::number( propId ), QString::number( index ) ) ); + return prop; +} +static bool IsFileTimeZero( const FILETIME *lpFileTime ) +{ + return (lpFileTime->dwLowDateTime == 0) && (lpFileTime->dwHighDateTime == 0); +} + +static bool IsDST( const QDateTime& datetime = QDateTime() ) +{ + const time_t seconds = static_cast< time_t >( datetime.isValid() ? datetime.toTime_t() : QDateTime::currentDateTime().toTime_t() ); + const tm* const t = localtime( &seconds ); + return t->tm_isdst; +} + +static QDateTime getDateTimeProperty( IInArchive* archive, int index, int propId, const QDateTime& defaultValue ) { + const NCOM::CPropVariant prop = readProperty( archive, index, propId ); + if (prop.vt != VT_FILETIME) + throw SevenZipException( QObject::tr("Property %1 for item %2 not of type VT_FILETIME but %3").arg( QString::number( propId ), QString::number( index ), QString::number( prop.vt ) ) ); + if ( IsFileTimeZero( &prop.filetime ) ) + return defaultValue; + + FILETIME localFileTime; + if ( !FileTimeToLocalFileTime( &prop.filetime, &localFileTime ) ) + throw SevenZipException( QObject::tr("Could not convert file time to local time") ); + + SYSTEMTIME st; + if ( !BOOLToBool(FileTimeToSystemTime(&localFileTime, &st)) ) + throw SevenZipException( QObject::tr("Could not convert local file time to system time") ); + + const QDate date( st.wYear, st.wMonth, st.wDay ); + const QTime time( st.wHour, st.wMinute, st.wSecond ); + QDateTime result( date, time ); + + // fix daylight saving time + const bool dst = IsDST(); + if( dst != IsDST( result ) ) + result = result.addSecs( dst ? -3600 : 3600 ); + + return result; +} + +static quint64 getUInt64Property( IInArchive* archive, int index, int propId, quint64 defaultValue=0 ) { + const NCOM::CPropVariant prop = readProperty( archive, index, propId ); + if ( prop.vt == VT_EMPTY ) + return defaultValue; + return static_cast<quint64>( ConvertPropVariantToUInt64( prop ) ); +} + +static quint32 getUInt32Property( IInArchive* archive, int index, int propId, quint32 defaultValue=0 ) { + const NCOM::CPropVariant prop = readProperty( archive, index, propId ); + if ( prop.vt == VT_EMPTY ) + return defaultValue; + return static_cast< quint32 >( prop.ulVal ); +} + +static QFile::Permissions getPermissions( IInArchive* archive, int index, bool* hasPermissions = 0 ) +{ + quint32 attributes = getUInt32Property( archive, index, kpidAttrib, 0 ); + QFile::Permissions permissions = 0; + if( attributes & FILE_ATTRIBUTE_UNIX_EXTENSION ) + { + if( hasPermissions != 0 ) + *hasPermissions = true; + // filter the unix permissions + attributes = ( attributes >> 16 ) & 0777; + permissions |= static_cast< QFile::Permissions >( ( attributes & 0700 ) << 2 ); // owner rights + permissions |= static_cast< QFile::Permissions >( ( attributes & 0070 ) << 1 ); // group + permissions |= static_cast< QFile::Permissions >( ( attributes & 0007 ) << 0 ); // and world rights + } + else if( hasPermissions != 0 ) + *hasPermissions = false; + return permissions; +} + +namespace Lib7z { + + class QIODeviceSequentialOutStream : public ISequentialOutStream, public CMyUnknownImp { + public: + MY_UNKNOWN_IMP + explicit QIODeviceSequentialOutStream( QIODevice* device ); + ~QIODeviceSequentialOutStream(); + + /* reimp */ STDMETHOD(Write)( const void* data, UInt32 size, UInt32* processedSize ); + + private: + QPointer<QIODevice> m_device; + const bool closeOnDestroy; + }; + + + QIODeviceSequentialOutStream::QIODeviceSequentialOutStream( QIODevice* device ) + : ISequentialOutStream(), + CMyUnknownImp(), + m_device( device ), + closeOnDestroy( !device->isOpen() ) + { + assert( m_device ); + if( closeOnDestroy ) + m_device->open( QIODevice::WriteOnly ); + } + + QIODeviceSequentialOutStream::~QIODeviceSequentialOutStream() + { + if( closeOnDestroy ) + { + m_device->close(); + delete m_device; + } + } + + HRESULT QIODeviceSequentialOutStream::Write( const void* data, UInt32 size, UInt32* processedSize ) { + if ( !m_device ) { + if ( processedSize ) + *processedSize = 0; + return E_FAIL; + } + if( closeOnDestroy && !m_device->isOpen() ) { + const bool opened = m_device->open( QIODevice::WriteOnly ); + if ( !opened ) { + if ( processedSize ) + *processedSize = 0; + return E_FAIL; + } + } + + const qint64 written = m_device->write( reinterpret_cast<const char*>( data ), size ); + if ( processedSize ) + *processedSize = written; + return written >= 0 ? S_OK : E_FAIL; + } + + class QIODeviceInStream : public IInStream, public CMyUnknownImp { + public: + MY_UNKNOWN_IMP + + explicit QIODeviceInStream( QIODevice* device ) : IInStream(), CMyUnknownImp(), m_device( device ) { + assert( m_device ); + assert( !m_device->isSequential() ); + } + + /* reimp */ STDMETHOD(Read)( void* data, UInt32 size, UInt32* processedSize ) { + assert( m_device ); + assert( m_device->isReadable() ); + const qint64 actual = m_device->read( reinterpret_cast<char*>( data ), size ); + Q_ASSERT( actual != 0 || m_device->atEnd() ); + if ( processedSize ) + *processedSize = actual; + return actual >= 0 ? S_OK : E_FAIL; + } + + /* reimp */ STDMETHOD(Seek)( Int64 offset, UInt32 seekOrigin, UInt64* newPosition ) { + assert( m_device ); + assert( !m_device->isSequential() ); + assert( m_device->isReadable() ); + if ( seekOrigin > STREAM_SEEK_END ) + return STG_E_INVALIDFUNCTION; + UInt64 np = 0; + switch( seekOrigin ) { + case STREAM_SEEK_SET: + np = offset; + break; + case STREAM_SEEK_CUR: + np = m_device->pos() + offset; + break; + case STREAM_SEEK_END: + np = m_device->size() + offset; + break; + default: + return STG_E_INVALIDFUNCTION; + } + + np = qBound( static_cast<UInt64>( 0 ), np, static_cast<UInt64>( m_device->size() - 1 ) ); + const bool ok = m_device->seek( np ); + if ( newPosition ) + *newPosition = np; + return ok ? S_OK : E_FAIL; + } + + private: + QPointer<QIODevice> m_device; + }; +} + +File::File() : permissions( 0 ), uncompressedSize( 0 ), compressedSize( 0 ), isDirectory( false ) { +} + +QVector<File> File::subtreeInPreorder() const { + QVector<File> res; + res += *this; + Q_FOREACH( const File& child, children ) + res += child.subtreeInPreorder(); + return res; +} + +bool File::operator<( const File& other ) const { + if ( path != other.path ) + return path < other.path; + if ( mtime != other.mtime ) + return mtime < other.mtime; + if ( uncompressedSize != other.uncompressedSize ) + return uncompressedSize < other.uncompressedSize; + if ( compressedSize != other.compressedSize ) + return compressedSize < other.compressedSize; + if ( isDirectory != other.isDirectory ) + return !isDirectory; + if( permissions != other.permissions ) + return permissions < other.permissions; + return false; +} + +bool File::operator==( const File& other ) const { + return mtime == other.mtime + && path == other.path + && uncompressedSize == other.uncompressedSize + && compressedSize == other.compressedSize + && isDirectory == other.isDirectory + && children == other.children + && (permissions == other.permissions || permissions == static_cast< QFile::Permissions >( -1 ) || other.permissions == static_cast< QFile::Permissions >( -1 ) ); +} + +QByteArray Lib7z::formatKeyValuePairs( const QVariantList& l ) { + assert( l.size() % 2 == 0 ); + QByteArray res; + for ( QVariantList::ConstIterator it = l.constBegin(); it != l.constEnd(); ++it ) { + if ( !res.isEmpty() ) + res += ", "; + res += qPrintable(it->toString()) + QByteArray(" = "); + ++it; + res += qPrintable(it->toString()); + } + return res; +} + +class Job::Private { +public: + Private() : error(Lib7z::NoError) {} + int error; + QString errorString; +}; + +Job::Job( QObject* parent ) : QObject( parent ), QRunnable(), d( new Private ) { +} + +Job::~Job() { + delete d; +} + +void Job::emitResult() { + emit finished( this ); +} + +void Job::setError( int code ) { + d->error = code; +} + +void Job::setErrorString( const QString& str ) { + d->errorString = str; +} + +void Job::emitProgress( qint64 completed, qint64 total ) { + emit progress( completed, total ); +} + +int Job::error() const { + return d->error; +} + +bool Job::hasError() const { + return d->error != NoError; +} + +void Job::run() { + doStart(); +} + +QString Job::errorString() const { + return d->errorString; +} + +void Job::start() { + QMetaObject::invokeMethod( this, "doStart", Qt::QueuedConnection ); +} + +class ListArchiveJob::Private { +public: + QPointer<QIODevice> archive; + QVector<File> files; +}; + +ListArchiveJob::ListArchiveJob( QObject* parent ) : Job( parent ), d( new Private ) { +} + +ListArchiveJob::~ListArchiveJob() { + delete d; +} + +QIODevice* ListArchiveJob::archive() const { + return d->archive; +} + +void ListArchiveJob::setArchive( QIODevice* device ) { + d->archive = device; +} + +QVector<File> ListArchiveJob::index() const { + return d->files; +} + +class OpenArchiveInfo +{ +private: + OpenArchiveInfo( QIODevice* device ) + : codecs( new CCodecs ), + stream( new QIODeviceInStream( device ) ) + { + throwIfNotOK( codecs->Load(), QObject::tr( "Could not load codecs" ) ); + + if( !codecs->FindFormatForArchiveType( L"", formatIndices ) ) + throw SevenZipException( QObject::tr( "Could not retrieve default format" ) ); + + throwIfNotOK( archiveLink.Open2( codecs.get(), formatIndices, false, stream, UString(), 0 ), QObject::tr( "Could not open archive" ) ); + if( archiveLink.Arcs.Size() == 0 ) + throw SevenZipException( QObject::tr( "No CArc found" ) ); + + m_cleaner = new OpenArchiveInfoCleaner(); + m_cleaner->moveToThread( device->thread() ); + QObject::connect(device, SIGNAL(destroyed(QObject*)), m_cleaner, SLOT(deviceDestroyed(QObject*))); + } + +public: + ~OpenArchiveInfo() + { + m_cleaner->deleteLater(); + } + + static QMap< QIODevice*, OpenArchiveInfo* > instances; + + static OpenArchiveInfo* instance( QIODevice* device ) + { + if( instances[ device ] == 0 ) + instances[ device ] = new OpenArchiveInfo( device ); + return instances[ device ]; + } + + std::auto_ptr< CCodecs > codecs; + CIntVector formatIndices; + CArchiveLink archiveLink; + CMyComPtr< IInStream > stream; +private: + OpenArchiveInfoCleaner *m_cleaner; +}; + +QMap< QIODevice*, OpenArchiveInfo* > OpenArchiveInfo::instances; + +void OpenArchiveInfoCleaner::deviceDestroyed(QObject* dev) +{ + QIODevice* device = static_cast<QIODevice*>(dev); + Q_ASSERT(device); + Q_ASSERT(OpenArchiveInfo::instances.contains(device)); + delete OpenArchiveInfo::instances.take(device); +} + +QVector<File> Lib7z::listArchive( QIODevice* archive ) { + assert( archive ); + try { + const OpenArchiveInfo* const openArchive = OpenArchiveInfo::instance( archive ); + + QVector<File> flat; + + for ( int i = 0; i < openArchive->archiveLink.Arcs.Size(); ++i ) { + const CArc& arc = openArchive->archiveLink.Arcs[i]; + IInArchive* const arch = arc.Archive; + + UInt32 numItems = 0; + throwIfNotOK( arch->GetNumberOfItems(&numItems), QObject::tr("Could not retrieve number of items in archive") ); + + flat.reserve( flat.size() + numItems ); + for ( uint item = 0; item < numItems; ++item ) { + UString s; + throwIfNotOK( arc.GetItemPath( item, s ), QObject::tr("Could not retrieve path of archive item %1").arg( item ) ); + File f; + f.archiveIndex.setX( i ); + f.archiveIndex.setY( item ); + f.path = UString2QString( s ).replace( QLatin1Char( '\\' ), QLatin1Char( '/' ) ); + IsArchiveItemFolder( arch, item, f.isDirectory ); + f.permissions = getPermissions( arch, item ); + f.mtime = getDateTimeProperty( arch, item, kpidMTime, QDateTime() ); + f.uncompressedSize = getUInt64Property( arch, item, kpidSize, 0 ); + f.compressedSize = getUInt64Property( arch, item, kpidPackSize, 0 ); + flat.push_back( f ); + qApp->processEvents(); + } + } + return flat; + } catch ( const SevenZipException& e ) { + throw e; + } + catch(const char *err) { + throw SevenZipException( err ); + } + catch ( ... ) { + throw SevenZipException( QObject::tr("Unknown exception caught (%1)").arg(QString::fromLatin1(Q_FUNC_INFO)) ); + } + return QVector<File>(); // never reached +} + +void ListArchiveJob::doStart() { + try { + if ( !d->archive ) + throw SevenZipException( tr("Could not list archive: QIODevice already destroyed") ); + d->files = listArchive( d->archive ); + } catch ( const SevenZipException& e ) { + setError( Failed ); + setErrorString( e.message() ); + } catch ( ... ) { + setError( Failed ); + setErrorString( QObject::tr("Unknown exception caught (%1)").arg( QObject::tr( "Failed" )) ); + } + emitResult(); +} + +class Lib7z::ExtractCallbackImpl : public IArchiveExtractCallback, public CMyUnknownImp { +public: + MY_UNKNOWN_IMP + explicit ExtractCallbackImpl( ExtractCallback* qq ) + : q( qq ), + currentIndex( 0 ), + arc( 0 ), + total( 0 ), + completed( 0 ), + device( 0 ) + { + } + + void setTarget( QIODevice* dev ) + { + device = dev; + } + + void setTarget( const QString& targetDirectory ) + { + targetDir = targetDirectory; + } + + // this method will be called by CFolderOutStream::OpenFile to stream via + // CDecoder::CodeSpec extracted content to an output stream. + /* reimp */ STDMETHOD(GetStream)( UInt32 index, ISequentialOutStream** outStream, Int32 askExtractMode ) + { + Q_UNUSED( askExtractMode ) + *outStream = 0; + if( device != 0 ) + { + CMyComPtr<ISequentialOutStream> stream = new QIODeviceSequentialOutStream( device ); + *outStream = stream.Detach(); + return S_OK; + } + else if( !targetDir.isEmpty() ) + { + assert( arc ); + + currentIndex = index; + + UString s; + throwIfNotOK( arc->GetItemPath( index, s ), QObject::tr( "Could not retrieve path of archive item %1" ).arg( index ) ); + const QString path = UString2QString( s ).replace( QLatin1Char( '\\' ), QLatin1Char( '/' ) ); + + const QFileInfo fi( QString::fromLatin1( "%1/%2" ).arg( targetDir, path ) ); + DirectoryGuard guard( fi.absolutePath() ); + const QStringList directories = guard.tryCreate(); + + bool isDir = false; + IsArchiveItemFolder( arc->Archive, index, isDir ); + if( isDir ) + { + QDir( fi.absolutePath() ).mkdir( fi.fileName() ); + } + + // this makes sure that all directories created get removed as well + for( QStringList::const_iterator it = directories.begin(); it != directories.end(); ++it ) + q->setCurrentFile( *it ); + + if( !isDir && !q->prepareForFile( fi.absoluteFilePath() ) ) + return E_FAIL; + + q->setCurrentFile( fi.absoluteFilePath() ); + + if( !isDir ) + { + CMyComPtr< ISequentialOutStream > stream = new QIODeviceSequentialOutStream( new QFile( fi.absoluteFilePath() ) ); + *outStream = stream; + stream.Detach(); + } + + guard.release(); + return S_OK; + } + return E_FAIL; + } + + /* reimp */ STDMETHOD(PrepareOperation)( Int32 askExtractMode ) { + Q_UNUSED( askExtractMode ) + return S_OK; + } + + /* reimp */ STDMETHOD(SetOperationResult)( Int32 resultEOperationResult ) { + Q_UNUSED( resultEOperationResult ) + + if( !targetDir.isEmpty() ) + { + bool hasPerm; + const QFile::Permissions permissions = getPermissions( arc->Archive, currentIndex, &hasPerm ); + if( hasPerm ) + { + UString s; + throwIfNotOK( arc->GetItemPath( currentIndex, s ), QObject::tr( "Could not retrieve path of archive item %1" ).arg( currentIndex ) ); + const QString path = UString2QString( s ).replace( QLatin1Char( '\\' ), QLatin1Char( '/' ) ); + const QFileInfo fi( QString::fromLatin1( "%1/%2" ).arg( targetDir, path ) ); + QFile::setPermissions( fi.absoluteFilePath(), permissions ); + + // do we have a symlink? + const quint32 attributes = getUInt32Property( arc->Archive, currentIndex, kpidAttrib, 0 ); + struct stat stat_info; + stat_info.st_mode = attributes >> 16; + if( S_ISLNK( stat_info.st_mode ) ) + { + QFile f( fi.absoluteFilePath() ); + f.open( QIODevice::ReadOnly ); + const QByteArray path = f.readAll(); + f.close(); + f.remove(); +#ifdef Q_WS_WIN + if( !CreateHardLinkWrapper( fi.absoluteFilePath(), QLatin1String( path ) ) ) + throw SevenZipException( QObject::tr( "Could not create file system lik at %1" ).arg( fi.absoluteFilePath() ) ); +#else + if( !QFile::link( QString::fromLatin1( path ), fi.absoluteFilePath() ) ) + throw SevenZipException( QObject::tr( "Could not create softlink at %1" ).arg( fi.absoluteFilePath() ) ); +#endif + } + } + } + + return S_OK; + } + + /* reimp */ STDMETHOD(SetTotal)( UInt64 t ) { + total = t; + return S_OK; + } + + /* reimp */ STDMETHOD(SetCompleted)( const UInt64* c ) { + completed = *c; + if ( total > 0 ) { + return q->setCompleted( completed, total ); + } + return S_OK; + } + + void setArchive( const CArc* archive ) + { + arc = archive; + } + +private: + ExtractCallback* const q; + UInt32 currentIndex; + const CArc* arc; + UInt64 total; + UInt64 completed; + QPointer<QIODevice> device; + QString targetDir; +}; + + +class Lib7z::ExtractCallbackPrivate { +public: + explicit ExtractCallbackPrivate( ExtractCallback* qq ) { + impl = new ExtractCallbackImpl( qq ); + } + + CMyComPtr<ExtractCallbackImpl> impl; +}; + +ExtractCallback::ExtractCallback() : d( new ExtractCallbackPrivate( this ) ) { +} + +ExtractCallback::~ExtractCallback() { + delete d; +} + +ExtractCallbackImpl* ExtractCallback::impl() { + return d->impl; +} + +const ExtractCallbackImpl* ExtractCallback::impl() const { + return d->impl; +} + +void ExtractCallback::setTarget( QIODevice* dev ) +{ + d->impl->setTarget( dev ); +} + +void ExtractCallback::setTarget( const QString& dir ) +{ + d->impl->setTarget( dir ); +} + +HRESULT ExtractCallback::setCompleted( quint64, quint64 ) +{ + return S_OK; +} + +void ExtractCallback::setCurrentFile( const QString& ) +{ +} + +bool ExtractCallback::prepareForFile( const QString& ) +{ + return true; +} + +class Lib7z::ExtractCallbackJobImpl : public ExtractCallback { + public: + explicit ExtractCallbackJobImpl( ExtractItemJob* j ) : ExtractCallback(), job( j ) {} + private: + /* reimp */ HRESULT setCompleted( quint64 c, quint64 t ) { + emit job->progress( c, t ); + return S_OK; + } + + ExtractItemJob* const job; +}; + +class Lib7z::UpdateCallbackImpl : public IUpdateCallbackUI2, public CMyUnknownImp +{ +public: + MY_UNKNOWN_IMP + explicit UpdateCallbackImpl( UpdateCallback* qq ) + : q( qq ) + { + } + virtual ~UpdateCallbackImpl() + { + } + /** + * \reimp + */ + HRESULT SetTotal( UInt64 ) + { + return S_OK; + } + /** + * \reimp + */ + HRESULT SetCompleted( const UInt64* ) + { + return S_OK; + } + HRESULT SetRatioInfo( const UInt64*, const UInt64* ) + { + return S_OK; + } + HRESULT CheckBreak() + { + return S_OK; + } + HRESULT Finilize() + { + return S_OK; + } + HRESULT SetNumFiles( UInt64 ) + { + return S_OK; + } + HRESULT GetStream( const wchar_t*, bool ) + { + return S_OK; + } + HRESULT OpenFileError( const wchar_t*, DWORD ) + { + return S_OK; + } + HRESULT CryptoGetTextPassword2( Int32* passwordIsDefined, OLECHAR** password ) + { + *password = 0; + *passwordIsDefined = false; + return S_OK; + } + HRESULT CryptoGetTextPassword(OLECHAR**) + { + return E_NOTIMPL; + } + HRESULT OpenResult(const wchar_t*, LONG ) + { + return S_OK; + } + HRESULT StartScanning() + { + return S_OK; + } + HRESULT ScanProgress(UInt64, UInt64, const wchar_t*) + { + return S_OK; + } + HRESULT CanNotFindError(const wchar_t*, DWORD) + { + return S_OK; + } + HRESULT FinishScanning() + { + return S_OK; + } + HRESULT StartArchive(const wchar_t*, bool) + { + return S_OK; + } + HRESULT FinishArchive() + { + return S_OK; + } + + /** + * \reimp + */ + HRESULT SetOperationResult( Int32 ) + { + // TODO! + return S_OK; + } + void setSource( const QString& dir ) + { + sourceDir = dir; + } + void setTarget( QIODevice* archive ) + { + target = archive; + } + +private: + UpdateCallback* const q; + + QIODevice* target; + QString sourceDir; +}; + +class Lib7z::UpdateCallbackPrivate +{ +public: + explicit UpdateCallbackPrivate( UpdateCallback* qq ) + { + m_impl = new UpdateCallbackImpl( qq ); + } + + UpdateCallbackImpl* impl() + { + return m_impl; + } + +private: + CMyComPtr< UpdateCallbackImpl > m_impl; +}; + +UpdateCallback::UpdateCallback() + : d( new UpdateCallbackPrivate( this ) ) +{ +} + +UpdateCallback::~UpdateCallback() +{ + delete d; +} + +UpdateCallbackImpl* UpdateCallback::impl() +{ + return d->impl(); +} + + +void UpdateCallback::setSource( const QString& dir ) +{ + d->impl()->setSource( dir ); +} + +void UpdateCallback::setTarget( QIODevice* target ) +{ + d->impl()->setTarget( target ); +} + +class ExtractItemJob::Private { +public: + Private( ExtractItemJob* qq ) + : q( qq), + target( 0 ), + callback( new ExtractCallbackJobImpl( q ) ) + { + } + + ~Private() { + } + + ExtractItemJob* q; + File item; + QPointer<QIODevice> archive; + QString targetDirectory; + QIODevice* target; + ExtractCallback* callback; +}; + +ExtractItemJob::ExtractItemJob( QObject* parent ) : Job( parent ), d( new Private( this ) ) { +} + +ExtractItemJob::~ExtractItemJob() { + delete d; +} + +File ExtractItemJob::item() const { + return d->item; +} + +void ExtractItemJob::setItem( const File& item ) { + d->item = item; +} + +QIODevice* ExtractItemJob::archive() const { + return d->archive; +} + +void ExtractItemJob::setArchive( QIODevice* archive ) { + d->archive = archive; +} + +QString ExtractItemJob::targetDirectory() const { + return d->targetDirectory; +} + +void ExtractItemJob::setTargetDirectory( const QString& dir ) { + d->targetDirectory = dir; + d->target = 0; +} + +void ExtractItemJob::setTarget( QIODevice* dev ) +{ + d->target = dev; +} + +void Lib7z::createArchive( QIODevice* archive, const QString& sourceDirectory, UpdateCallback* callback ) +{ + assert( archive ); + + std::auto_ptr< UpdateCallback > dummyCallback( callback ? 0 : new UpdateCallback ); + if ( !callback ) + callback = dummyCallback.get(); + + try + { + std::auto_ptr< CCodecs > codecs( new CCodecs ); + throwIfNotOK( codecs->Load(), QObject::tr( "Could not load codecs" ) ); + + CIntVector formatIndices; + + if( !codecs.get()->FindFormatForArchiveType( L"", formatIndices ) ) + throw SevenZipException( QObject::tr( "Could not retrieve default format" ) ); + + // yes this is crap, but there seems to be no streaming solution to this... + + const QString tempFile = generateTempFileName(); + + NWildcard::CCensor censor; + const UString sourceDirectoryPath = QString2UString( QDir::toNativeSeparators( sourceDirectory ) ); + if( UString2QString( sourceDirectoryPath ) != QDir::toNativeSeparators( sourceDirectory ) ) + throw UString2QString( sourceDirectoryPath ).toLatin1().data(); + censor.AddItem( true, sourceDirectoryPath, true ); + + CUpdateOptions options; + CArchivePath archivePath; + archivePath.ParseFromPath( QString2UString( tempFile ) ); + CUpdateArchiveCommand command; + command.ArchivePath = archivePath; + command.ActionSet = NUpdateArchive::kAddActionSet; + options.Commands.Add( command ); + options.ArchivePath = archivePath; + options.MethodMode.FormatIndex = codecs->FindFormatForArchiveType( L"7z" ); + + CUpdateErrorInfo errorInfo; + + callback->setTarget( archive ); + callback->setSource( sourceDirectory ); + const HRESULT res = UpdateArchive( codecs.get(), censor, options, errorInfo, 0, callback->impl() ); + if( res != S_OK || !QFile::exists( tempFile ) ) + throw SevenZipException( QObject::tr( "Could not create archive %1" ).arg( tempFile ) ); + { + //TODO remove temp file even if one the following throws + QFile file( tempFile ); + QInstaller::openForRead( &file, tempFile ); + QInstaller::blockingCopy( &file, archive, file.size() ); + } + QFile file( tempFile ); + if ( !file.remove() ) + qWarning("%s: Could not remove temporary file %s: %s", Q_FUNC_INFO, qPrintable(tempFile), qPrintable(file.errorString()) ); + } + catch(const char *err) + { + std::cout << err << std::endl; + throw SevenZipException( err ); + } + catch (const QInstaller::Error &err ) + { + throw SevenZipException( err.message() ); + } + catch( ... ) + { + throw SevenZipException( QObject::tr("Unknown exception caught (%1)").arg(QString::fromLatin1(Q_FUNC_INFO)) ); + } +} + +void Lib7z::extractArchive( QIODevice* archive, const File& item, QIODevice* target, ExtractCallback* callback ) { + assert( archive ); + assert( target ); + + std::auto_ptr<ExtractCallback> dummyCallback( callback ? 0 : new ExtractCallback ); + if ( !callback ) + callback = dummyCallback.get(); + + try { + const OpenArchiveInfo* const openArchive = OpenArchiveInfo::instance( archive ); + + const int arcIdx = item.archiveIndex.x(); + if ( arcIdx < 0 || arcIdx >= openArchive->archiveLink.Arcs.Size() ) + throw SevenZipException( QObject::tr("CArc index %1 out of bounds [0, %2]").arg( openArchive->archiveLink.Arcs.Size() - 1 ) ); + const CArc& arc = openArchive->archiveLink.Arcs[arcIdx]; + IInArchive* const parchive = arc.Archive; + + const UInt32 itemIdx = item.archiveIndex.y(); + UInt32 numItems = 0; + throwIfNotOK( parchive->GetNumberOfItems(&numItems), QObject::tr("Could not retrieve number of items in archive") ); + if ( itemIdx >= numItems ) + throw SevenZipException( QObject::tr("Item index %1 out of bounds [0, %2]").arg( itemIdx ).arg( numItems - 1 ) ); + + UString s; + throwIfNotOK( arc.GetItemPath( itemIdx, s ), QObject::tr( "Could not retrieve path of archive item %1" ).arg( itemIdx ) ); + assert( item.path == UString2QString( s ).replace( QLatin1Char( '\\' ), QLatin1Char( '/' ) ) ); + + callback->setTarget( target ); + const LONG extractResult = parchive->Extract( &itemIdx, 1, /*testmode=*/1, callback->impl() ); + //TODO: how to interpret result? + throwIfNotOK( extractResult, QObject::tr("Extracting %1 failed.").arg( item.path ) ); + } catch(const char *err) { + throw SevenZipException( err ); + } catch ( ... ) { + throw SevenZipException( QObject::tr("Unknown exception caught (%1)").arg(QString::fromLatin1(Q_FUNC_INFO)) ); + } +} + +void Lib7z::extractArchive( QIODevice* archive, const File& item, const QString& targetDirectory, ExtractCallback* callback ) { + assert( archive ); + + std::auto_ptr<ExtractCallback> dummyCallback( callback ? 0 : new ExtractCallback ); + if ( !callback ) + callback = dummyCallback.get(); + + QFileInfo fi( targetDirectory + QLatin1String( "/" ) + item.path ); + DirectoryGuard outDir( fi.absolutePath() ); + outDir.tryCreate(); + QFile out( fi.absoluteFilePath() ); + if ( !out.open( QIODevice::WriteOnly ) ) //TODO use tmp file + throw SevenZipException( QObject::tr("Could not create output file for writing: %1").arg( fi.absoluteFilePath() ) ); + if( item.permissions ) + out.setPermissions( item.permissions ); + callback->setTarget( &out ); + extractArchive( archive, item, &out, callback ); + outDir.release(); +} + +void Lib7z::extractArchive( QIODevice* archive, const QString& targetDirectory, ExtractCallback* callback ) +{ + assert( archive ); + + std::auto_ptr<ExtractCallback> dummyCallback( callback ? 0 : new ExtractCallback ); + if ( !callback ) + callback = dummyCallback.get(); + + callback->setTarget( targetDirectory ); + + const QFileInfo fi( targetDirectory ); + DirectoryGuard outDir( fi.absolutePath() ); + outDir.tryCreate(); + + const OpenArchiveInfo* const openArchive = OpenArchiveInfo::instance( archive ); + + for( int a = 0; a < openArchive->archiveLink.Arcs.Size(); ++a ) + { + const CArc& arc = openArchive->archiveLink.Arcs[ a ]; + IInArchive* const arch = arc.Archive; + callback->impl()->setArchive( &arc ); + const LONG extractResult = arch->Extract( 0, static_cast< UInt32 >( -1 ), false, callback->impl() ); + throwIfNotOK( extractResult, QObject::tr("Extraction failed.") ); //TODO is it possible to get a more detailed error? + } + + outDir.release(); +} + +bool Lib7z::isSupportedArchive( const QString& archive ) +{ + QFile file( archive ); + if( !file.open( QIODevice::ReadOnly ) ) + return false; + + return isSupportedArchive( &file ); +} + +bool Lib7z::isSupportedArchive( QIODevice* archive ) { + assert( archive ); + assert( !archive->isSequential() ); + const qint64 initialPos = archive->pos(); + try { + std::auto_ptr<CCodecs> codecs( new CCodecs ); + throwIfNotOK( codecs->Load(), QObject::tr("Could not load codecs") ); + + CIntVector formatIndices; + + if ( !codecs.get()->FindFormatForArchiveType(L"", formatIndices) ) + throw SevenZipException( QObject::tr("Could not retrieve default format") ); + + CArchiveLink archiveLink; + const CMyComPtr<IInStream> stream = new QIODeviceInStream( archive ); //CMyComPtr is needed, otherwise it crashes in OpenStream() + + const HRESULT result = archiveLink.Open2(codecs.get(), formatIndices, /*stdInMode*/false, stream, UString(), 0); + + archive->seek( initialPos ); + return result == S_OK; + } catch ( const SevenZipException& e ) { + archive->seek( initialPos ); + throw e; + } catch(const char *err) { + archive->seek( initialPos ); + throw SevenZipException( err ); + } catch( ... ) { + archive->seek( initialPos ); + throw SevenZipException( QObject::tr("Unknown exception caught (%1)").arg(QString::fromLatin1(Q_FUNC_INFO)) ); + } + return false; // never reached +} + +void ExtractItemJob::doStart() { + try { + if ( !d->archive ) + throw SevenZipException( tr("Could not list archive: QIODevice not set or already destroyed") ); + if ( d->target ) + extractArchive( d->archive, d->item, d->target, d->callback ); + else if( !d->item.path.isEmpty() ) + extractArchive( d->archive, d->item, d->targetDirectory, d->callback ); + else + extractArchive( d->archive, d->targetDirectory, d->callback ); + } catch ( const SevenZipException& e ) { + setError( Failed ); + setErrorString( e.message() ); + } catch ( ... ) { + setError( Failed ); + setErrorString( QObject::tr("Unknown exception caught (%1)").arg( QObject::tr( "Failed" )) ); + } + emitResult(); +} |