diff options
Diffstat (limited to 'src/libs/installer/lib7z_facade.cpp')
-rw-r--r-- | src/libs/installer/lib7z_facade.cpp | 1856 |
1 files changed, 693 insertions, 1163 deletions
diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp index 8f21343db..86dc86e7a 100644 --- a/src/libs/installer/lib7z_facade.cpp +++ b/src/libs/installer/lib7z_facade.cpp @@ -30,106 +30,199 @@ ** $QT_END_LICENSE$ ** **************************************************************************/ + #include "lib7z_facade.h" #include "errors.h" #include "fileio.h" +#include "lib7z_create.h" +#include "lib7z_extract.h" +#include "lib7z_list.h" +#include "lib7z_guid.h" + #ifndef Q_OS_WIN # include "StdAfx.h" #endif -#include "Common/MyInitGuid.h" +#include <7zCrc.h> + +#include <7zip/Archive/IArchive.h> -#include "7zip/Archive/IArchive.h" -#include "7zip/UI/Common/OpenArchive.h" -#include "7zip/UI/Common/Update.h" +#include <7zip/UI/Common/ArchiveCommandLine.h> +#include <7zip/UI/Common/OpenArchive.h> -#include "Windows/FileIO.h" -#include "Windows/PropVariant.h" -#include "Windows/PropVariantConversions.h" +#include <Windows/FileDir.h> +#include <Windows/FileIO.h> +#include <Windows/PropVariant.h> +#include <Windows/PropVariantConv.h> +#include <QCoreApplication> #include <QDir> #include <QFileInfo> #include <QIODevice> -#include <QtCore/QMutexLocker> #include <QPointer> -#include <QTemporaryFile> #include <QReadWriteLock> +#include <QTemporaryFile> -#ifdef _MSC_VER -#pragma warning(disable:4297) -#endif +#include <mutex> +#include <memory> #ifdef Q_OS_WIN +HINSTANCE g_hInstance = 0; + +# define S_IFMT 00170000 +# define S_IFLNK 0120000 +# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +# define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */ +# if !defined(Q_CC_MINGW) +# include <time.h> // for localtime_s +# endif +#else +extern "C" int global_use_utf16_conversion; + +#include <myWindows/config.h> +#include <sys/stat.h> +#endif + +namespace NArchive { + namespace N7z { + void registerArcDec7z(); + } + namespace NXz { + void registerArcxz(); + } + namespace NSplit { + void registerArcSplit(); + } + namespace NLzma { + namespace NLzmaAr { + void registerArcLzma(); + } + namespace NLzma86Ar { + void registerArcLzma86(); + } + } +} +using namespace NWindows; + +void registerCodecBCJ(); +void registerCodecBCJ2(); + +void registerCodecLZMA(); +void registerCodecLZMA2(); + +void registerCodecCopy(); +void registerCodecDelta(); +void registerCodecBranch(); +void registerCodecByteSwap(); + +namespace Lib7z { + -#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) +// -- 7z init codecs, archives -typedef BOOL (WINAPI *CREATEHARDLINK)(LPCSTR dst, LPCSTR str, LPSECURITY_ATTRIBUTES sa); +std::once_flag gOnceFlag; -bool CreateHardLinkWrapper(const QString &dest, const QString &file) +void initSevenZ() { - static HMODULE module = 0; - static CREATEHARDLINK proc = 0; + std::call_once(gOnceFlag, [] { + CrcGenerateTable(); - 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); -} + registerCodecBCJ(); + registerCodecBCJ2(); -#else -#include <sys/stat.h> + registerCodecLZMA(); + registerCodecLZMA2(); + + registerCodecCopy(); + registerCodecDelta(); + registerCodecBranch(); + registerCodecByteSwap(); + + NArchive::N7z::registerArcDec7z(); + NArchive::NXz::registerArcxz(); + NArchive::NSplit::registerArcSplit(); + NArchive::NLzma::NLzmaAr::registerArcLzma(); + NArchive::NLzma::NLzma86Ar::registerArcLzma86(); + +#ifndef Q_OS_WIN +# ifdef ENV_HAVE_LOCALE + const QByteArray locale = qgetenv("LC_ALL").toUpper(); + if (!locale.isEmpty() && (locale != "C") && (locale != "POSIX")) + global_use_utf16_conversion = 1; +# elif defined(LOCALE_IS_UTF8) + global_use_utf16_conversion = 1; +# else + global_use_utf16_conversion = 0; +# endif #endif + }); +} -#include <memory> -#include <cassert> +// -- error handling -using namespace Lib7z; -using namespace NWindows; +Q_GLOBAL_STATIC(QString, getLastErrorString) +Q_GLOBAL_STATIC(QReadWriteLock, lastErrorReadWriteLock) +QString lastError() +{ + QReadLocker locker(lastErrorReadWriteLock()); + Q_UNUSED(locker) + return *getLastErrorString(); +} -namespace Lib7z { - Q_GLOBAL_STATIC(QReadWriteLock, lastErrorReadWriteLock) - Q_GLOBAL_STATIC(QString, getLastErrorString) +void setLastError(const QString &errorString) +{ + QWriteLocker locker(lastErrorReadWriteLock()); + Q_UNUSED(locker) + *getLastErrorString() = errorString; +} - QString lastError() - { - QReadLocker locker(lastErrorReadWriteLock()); - Q_UNUSED(locker) - return *getLastErrorString(); - } +QString errorMessageFrom7zResult(const LONG &extractResult) +{ + if (!lastError().isEmpty()) + return lastError(); - void setLastError(const QString &errorString) - { - QWriteLocker locker(lastErrorReadWriteLock()); - Q_UNUSED(locker) - *getLastErrorString() = errorString; + QString errorMessage = QCoreApplication::translate("Lib7z", "internal code: %1"); + switch (extractResult) { + case S_OK: + qFatal("S_OK value is not a valid error code."); + break; + case E_NOTIMPL: + errorMessage = errorMessage.arg(QLatin1String("E_NOTIMPL")); + break; + case E_NOINTERFACE: + errorMessage = errorMessage.arg(QLatin1String("E_NOINTERFACE")); + break; + case E_ABORT: + errorMessage = errorMessage.arg(QLatin1String("E_ABORT")); + break; + case E_FAIL: + errorMessage = errorMessage.arg(QLatin1String("E_FAIL")); + break; + case STG_E_INVALIDFUNCTION: + errorMessage = errorMessage.arg(QLatin1String("STG_E_INVALIDFUNCTION")); + break; + case E_OUTOFMEMORY: + errorMessage = QCoreApplication::translate("Lib7z", "not enough memory"); + break; + case E_INVALIDARG: + errorMessage = errorMessage.arg(QLatin1String("E_INVALIDARG")); + break; + default: + errorMessage = QCoreApplication::translate("Lib7z", "Error: %1").arg(extractResult); + break; } + return errorMessage; } -namespace { -/** -* RAII class to create a directory (tryCreate()) and delete it on destruction unless released. +/*! + RAII class to create a directory (tryCreate()) and delete it on destruction unless released. */ -struct DirectoryGuard { +struct DirectoryGuard +{ explicit DirectoryGuard(const QString &path) : m_path(path) , m_created(false) @@ -144,14 +237,15 @@ struct DirectoryGuard { return; QDir dir(m_path); if (!dir.rmdir(m_path)) - qWarning() << "Could not delete directory " << m_path; + qWarning() << "Cannot delete directory " << m_path; } - /** - * Tries to create the directorie structure. - * Returns a list of every directory created. + /*! + Tries to create the directory structure. + Returns a list of every directory created. */ - QStringList tryCreate() { + QStringList tryCreate() + { if (m_path.isEmpty()) return QStringList(); @@ -160,13 +254,12 @@ struct DirectoryGuard { return QStringList(); if (fi.exists() && !fi.isDir()) { throw SevenZipException(QCoreApplication::translate("DirectoryGuard", - "Path exists but is not a folder: %1").arg(m_path)); + "Path \"%1\" exists but is not a directory.").arg(QDir::toNativeSeparators(m_path))); } QStringList created; QDir toCreate(m_path); - while (!toCreate.exists()) - { + while (!toCreate.exists()) { QString p = toCreate.absolutePath(); created.push_front(p); p = p.section(QLatin1Char('/'), 0, -2); @@ -177,12 +270,13 @@ struct DirectoryGuard { m_created = dir.mkpath(m_path); if (!m_created) { throw SevenZipException(QCoreApplication::translate("DirectoryGuard", - "Could not create folder: %1").arg(m_path)); + "Cannot create directory \"%1\".").arg(QDir::toNativeSeparators(m_path))); } return created; } - void release() { + void release() + { m_released = true; } @@ -190,7 +284,6 @@ struct DirectoryGuard { bool m_created; bool m_released; }; -} static UString QString2UString(const QString &str) { @@ -202,32 +295,12 @@ 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(QCoreApplication::translate("QInstaller", - "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) +static NCOM::CPropVariant readProperty(IInArchive *archive, int index, int propId) { NCOM::CPropVariant prop; if (archive->GetProperty(index, propId, &prop) != S_OK) { - throw SevenZipException(QCoreApplication::translate("QInstaller", - "Could not retrieve property %1 for item %2").arg(QString::number(propId), + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Cannot retrieve property %1 for item %2.").arg(QString::number(propId), QString::number(index))); } return prop; @@ -238,193 +311,134 @@ 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()); -#if defined(Q_OS_WIN) && !defined(Q_CC_MINGW) - struct tm t; - localtime_s(&t, &seconds); -#else - const struct tm &t = *localtime(&seconds); -#endif - return t.tm_isdst; -} - -static bool getFileTimeFromProperty(IInArchive* archive, int index, int propId, FILETIME *fileTime) +static bool getFileTimeFromProperty(IInArchive* archive, int index, int propId, FILETIME *ft) { const NCOM::CPropVariant prop = readProperty(archive, index, propId); if (prop.vt != VT_FILETIME) { - throw SevenZipException(QCoreApplication::translate("QInstaller", - "Property %1 for item %2 not of type VT_FILETIME but %3").arg(QString::number(propId), + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Property %1 for item %2 not of type VT_FILETIME but %3.").arg(QString::number(propId), QString::number(index), QString::number(prop.vt))); } - *fileTime = prop.filetime; - - if (IsFileTimeZero(fileTime)) - return false; - return true; + *ft = prop.filetime; + return !IsFileTimeZero(ft); } -static -QDateTime getDateTimeProperty(IInArchive* archive, int index, int propId, const QDateTime& defaultValue) +static bool getDateTimeProperty(IInArchive *arc, int index, int id, QDateTime *value) { - FILETIME fileTime; - if (!getFileTimeFromProperty(archive, index, propId, &fileTime)) - return defaultValue; + FILETIME ft7z; + if (!getFileTimeFromProperty(arc, index, id, &ft7z)) + return false; - FILETIME localFileTime; - if (!FileTimeToLocalFileTime(&fileTime, &localFileTime)) { - throw SevenZipException(QCoreApplication::translate("QInstaller", - "Could not convert file time to local time")); - } SYSTEMTIME st; - if (!BOOLToBool(FileTimeToSystemTime(&localFileTime, &st))) { - throw SevenZipException(QCoreApplication::translate("QInstaller", - "Could not convert local file time to system time")); + if (!BOOLToBool(FileTimeToSystemTime(&ft7z, &st))) { + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Cannot convert UTC 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; + *value = QDateTime(QDate(st.wYear, st.wMonth, st.wDay), QTime(st.wHour, st.wMinute, + st.wSecond), Qt::UTC); + return value->isValid(); } -static quint64 getUInt64Property(IInArchive* archive, int index, int propId, quint64 defaultValue=0) +static quint64 getUInt64Property(IInArchive *archive, int index, int propId, quint64 defaultValue) { - const NCOM::CPropVariant prop = readProperty(archive, index, propId); - if (prop.vt == VT_EMPTY) - return defaultValue; - return static_cast<quint64>(ConvertPropVariantToUInt64(prop)); + UInt64 value; + if (ConvertPropVariantToUInt64(readProperty(archive, index, propId), value)) + return value; + return defaultValue; } -static quint32 getUInt32Property(IInArchive* archive, int index, int propId, quint32 defaultValue=0) +static quint32 getUInt32Property(IInArchive *archive, int index, int propId, quint32 defaultValue) { const NCOM::CPropVariant prop = readProperty(archive, index, propId); if (prop.vt == VT_EMPTY) return defaultValue; - return static_cast< quint32 >(prop.ulVal); + return static_cast<quint32>(prop.ulVal); } -static QFile::Permissions getPermissions(IInArchive* archive, int index, bool* hasPermissions = 0) +static QFile::Permissions getPermissions(IInArchive *archive, int index, bool *hasPermissions) { 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 + // 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 + 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 { +#define LIB7Z_ASSERTS(X, MODE) \ + Q_ASSERT(X); \ + Q_ASSERT(X->isOpen()); \ + Q_ASSERT(X->is ## MODE()); \ + Q_ASSERT(!X->isSequential()); + class QIODeviceSequentialOutStream : public ISequentialOutStream, public CMyUnknownImp { -public: - enum Behavior { - KeepDeviceUntouched, - CloseAndDeleteDevice - }; + Q_DISABLE_COPY(QIODeviceSequentialOutStream) +public: MY_UNKNOWN_IMP - explicit QIODeviceSequentialOutStream(QIODevice* device, Behavior behavior); - ~QIODeviceSequentialOutStream(); - QString errorString() const; - /* reimp */ STDMETHOD(Write)(const void* data, UInt32 size, UInt32* processedSize); - -private: - Behavior m_behavior; - QString m_errorString; - QPointer<QIODevice> m_device; -}; - -QIODeviceSequentialOutStream::QIODeviceSequentialOutStream(QIODevice* device, Behavior behavior) - : ISequentialOutStream() - , CMyUnknownImp() - , m_behavior(behavior) - , m_device(device) -{ - Q_ASSERT(m_device); - - if (!device->isOpen() && !m_device->open(QIODevice::WriteOnly)) - m_errorString = m_device->errorString(); -} - -QIODeviceSequentialOutStream::~QIODeviceSequentialOutStream() -{ - if (m_behavior == CloseAndDeleteDevice) { - m_device->close(); - delete m_device; - m_device = 0; + explicit QIODeviceSequentialOutStream(std::unique_ptr<QIODevice> device) + : ISequentialOutStream() + , m_device(std::move(device)) + { + LIB7Z_ASSERTS(m_device, Writable) } -} -QString QIODeviceSequentialOutStream::errorString() const -{ - return m_errorString; -} + QString errorString() const { + return m_errorString; + } -HRESULT QIODeviceSequentialOutStream::Write(const void* data, UInt32 size, UInt32* processedSize) -{ - if (!m_device) { + STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize) + { if (processedSize) *processedSize = 0; - m_errorString = QCoreApplication::translate("QIODeviceSequentialOutStream", - "No device set for output stream"); - return E_FAIL; - } - if (!m_device->isOpen()) { - const bool opened = m_device->open(QIODevice::WriteOnly); - if (!opened) { - if (processedSize) - *processedSize = 0; + + const qint64 written = m_device->write(reinterpret_cast<const char*>(data), size); + if (written == -1) { m_errorString = m_device->errorString(); return E_FAIL; } - } - const qint64 written = m_device->write(reinterpret_cast<const char*>(data), size); - if (written == -1) { if (processedSize) - *processedSize = 0; - m_errorString = m_device->errorString(); - return E_FAIL; + *processedSize = written; + m_errorString.clear(); + return S_OK; } - if (processedSize) - *processedSize = written; - m_errorString = QString(); - return S_OK; -} +private: + QString m_errorString; + std::unique_ptr<QIODevice> m_device; +}; class QIODeviceInStream : public IInStream, public CMyUnknownImp { + Q_DISABLE_COPY(QIODeviceInStream) + public: MY_UNKNOWN_IMP - explicit QIODeviceInStream(QIODevice* device) : IInStream(), CMyUnknownImp(), m_device(device) + explicit QIODeviceInStream(QIODevice *device) + : IInStream() + , CMyUnknownImp() + , m_device(device) { - assert(m_device); - assert(!m_device->isSequential()); + LIB7Z_ASSERTS(m_device, Readable) } - /* reimp */ STDMETHOD(Read)(void* data, UInt32 size, UInt32* processedSize) + STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize) { - assert(m_device); - assert(m_device->isReadable()); + if (m_device.isNull()) + return E_FAIL; + const qint64 actual = m_device->read(reinterpret_cast<char*>(data), size); Q_ASSERT(actual != 0 || m_device->atEnd()); if (processedSize) @@ -432,29 +446,28 @@ public: return actual >= 0 ? S_OK : E_FAIL; } - /* reimp */ STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { - assert(m_device); - assert(!m_device->isSequential()); - assert(m_device->isReadable()); + if (m_device.isNull()) + return E_FAIL; 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; + 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)); + np = qBound(static_cast<UInt64>(0), np, static_cast<UInt64>(m_device->size())); const bool ok = m_device->seek(np); if (newPosition) *newPosition = np; @@ -464,1129 +477,646 @@ public: 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) +bool operator==(const File &lhs, const File &rhs) { - d->errorString = str; + return lhs.path == rhs.path + && lhs.utcTime == rhs.utcTime + && lhs.isDirectory == rhs.isDirectory + && lhs.compressedSize == rhs.compressedSize + && lhs.uncompressedSize == rhs.uncompressedSize + && (lhs.permissions == rhs.permissions + || lhs.permissions == static_cast<QFile::Permissions>(-1) + || rhs.permissions == static_cast<QFile::Permissions>(-1)); } -void Job::emitProgress(qint64 completed, qint64 total) +QVector<File> listArchive(QFileDevice *archive) { - emit progress(completed, total); -} - -int Job::error() const -{ - return d->error; -} + LIB7Z_ASSERTS(archive, Readable) -bool Job::hasError() const -{ - return d->error != NoError; -} - -void Job::run() -{ - doStart(); -} - -QString Job::errorString() const -{ - return d->errorString; -} + const qint64 initialPos = archive->pos(); + try { + CCodecs codecs; + if (codecs.Load() != S_OK) + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot load codecs.")); -void Job::start() -{ - QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); -} + COpenOptions op; + op.codecs = &codecs; -class ListArchiveJob::Private -{ -public: - QPointer<QFileDevice> archive; - QVector<File> files; -}; + CObjectVector<COpenType> types; + op.types = &types; // Empty, because we use a stream. -ListArchiveJob::ListArchiveJob(QObject* parent) - : Job(parent) - , d(new Private) -{ -} + CIntVector excluded; + op.excludedFormats = &excluded; -ListArchiveJob::~ListArchiveJob() -{ - delete d; -} - -QFileDevice* ListArchiveJob::archive() const -{ - return d->archive; -} - -void ListArchiveJob::setArchive(QFileDevice* device) -{ - d->archive = device; -} + const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); + op.stream = stream; // CMyComPtr is needed, otherwise it crashes in OpenStream(). -QVector<File> ListArchiveJob::index() const -{ - return d->files; -} + CObjectVector<CProperty> properties; + op.props = &properties; -class OpenArchiveInfo -{ -private: - OpenArchiveInfo(QFileDevice* device) - : codecs(new CCodecs) - { - if (codecs->Load() != S_OK) { - throw SevenZipException(QCoreApplication::translate("OpenArchiveInfo", - "Could not load codecs")); - } - if (!codecs->FindFormatForArchiveType(L"", formatIndices)) { - throw SevenZipException(QCoreApplication::translate("OpenArchiveInfo", - "Could not retrieve default format")); - } - stream = new QIODeviceInStream(device); - if (archiveLink.Open2(codecs.data(), formatIndices, false, stream, UString(), 0) != S_OK) { - throw SevenZipException(QCoreApplication::translate("OpenArchiveInfo", - "Could not open archive")); + CArchiveLink archiveLink; + if (archiveLink.Open2(op, nullptr) != S_OK) { + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Cannot open archive \"%1\".").arg(archive->fileName())); } - if (archiveLink.Arcs.Size() == 0) - throw SevenZipException(QCoreApplication::translate("OpenArchiveInfo", "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 OpenArchiveInfo* value(QFileDevice* device) - { - QMutexLocker _(&m_mutex); - if (!instances.contains(device)) - instances.insert(device, new OpenArchiveInfo(device)); - return instances.value(device); - } - - static OpenArchiveInfo* take(QFileDevice *device) - { - QMutexLocker _(&m_mutex); - if (instances.contains(device)) - return instances.take(device); - return 0; - } - - CArchiveLink archiveLink; - -private: - CIntVector formatIndices; - CMyComPtr<IInStream> stream; - QScopedPointer<CCodecs> codecs; - OpenArchiveInfoCleaner *m_cleaner; - - static QMutex m_mutex; - static QHash< QIODevice*, OpenArchiveInfo* > instances; -}; - -QMutex OpenArchiveInfo::m_mutex; -QHash< QIODevice*, OpenArchiveInfo* > OpenArchiveInfo::instances; - -void OpenArchiveInfoCleaner::deviceDestroyed(QObject* dev) -{ - QFileDevice* device = static_cast<QFileDevice*>(dev); - Q_ASSERT(device); - delete OpenArchiveInfo::take(device); -} - -QVector<File> Lib7z::listArchive(QFileDevice* archive) -{ - assert(archive); - try { - const OpenArchiveInfo* const openArchive = OpenArchiveInfo::value(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; - + for (unsigned i = 0; i < archiveLink.Arcs.Size(); ++i) { + IInArchive *const arch = archiveLink.Arcs[i].Archive; UInt32 numItems = 0; if (arch->GetNumberOfItems(&numItems) != S_OK) { throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not retrieve number of items in archive")); + "Cannot retrieve number of items in archive.")); } flat.reserve(flat.size() + numItems); for (uint item = 0; item < numItems; ++item) { UString s; - if (arc.GetItemPath(item, s) != S_OK) { + if (archiveLink.Arcs[i].GetItemPath(item, s) != S_OK) { throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not retrieve path of archive item %1").arg(item)); + "Cannot 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()); + Archive_IsItem_Folder(arch, item, f.isDirectory); + f.permissions = getPermissions(arch, item, 0); + getDateTimeProperty(arch, item, kpidMTime, &(f.utcTime)); f.uncompressedSize = getUInt64Property(arch, item, kpidSize, 0); f.compressedSize = getUInt64Property(arch, item, kpidPackSize, 0); - flat.push_back(f); - qApp->processEvents(); + flat.append(f); } } return flat; - } catch (const SevenZipException& e) { - throw e; } catch (const char *err) { + archive->seek(initialPos); throw SevenZipException(err); + } catch (const SevenZipException &e) { + archive->seek(initialPos); + throw e; // re-throw unmodified } catch (...) { + archive->seek(initialPos); throw SevenZipException(QCoreApplication::translate("Lib7z", - "Unknown exception caught (%1)").arg(QString::fromLatin1(Q_FUNC_INFO))); + "Unknown exception caught (%1).").arg(QString::fromLatin1(Q_FUNC_INFO))); } return QVector<File>(); // never reached } -void ListArchiveJob::doStart() + +// -- ExtractCallback + +STDMETHODIMP ExtractCallback::SetTotal(UInt64 t) { - 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(tr("Unknown exception caught (%1)").arg(tr("Failed"))); - } - emitResult(); + total = t; + return S_OK; } -class Lib7z::ExtractCallbackImpl : public IArchiveExtractCallback, public CMyUnknownImp +STDMETHODIMP ExtractCallback::SetCompleted(const UInt64 *c) { -public: - MY_UNKNOWN_IMP - explicit ExtractCallbackImpl(ExtractCallback* qq) - : q(qq) - , currentIndex(0) - , arc(0) - , total(0) - , completed(0) - , device(0) - { - } + completed = *c; + if (total > 0) + return setCompleted(completed, total); + return S_OK; +} - void setTarget(QIODevice* dev) - { - device = dev; - } +// this method will be called by CFolderOutStream::OpenFile to stream via +// CDecoder::CodeSpec extracted content to an output stream. +STDMETHODIMP ExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 /*askExtractMode*/) +{ + *outStream = 0; + if (targetDir.isEmpty()) + return E_FAIL; - void setTarget(const QString &targetDirectory) - { - targetDir = targetDirectory; - } + Q_ASSERT(arc); + currentIndex = index; - // 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) { - QIODeviceSequentialOutStream *qOutStream = new QIODeviceSequentialOutStream(device, - QIODeviceSequentialOutStream::KeepDeviceUntouched); - if (!qOutStream->errorString().isEmpty()) { - Lib7z::setLastError(qOutStream->errorString()); - return E_FAIL; - } - CMyComPtr<ISequentialOutStream> stream = qOutStream; - *outStream = stream.Detach(); - return S_OK; - } else if (!targetDir.isEmpty()) { - assert(arc); - - currentIndex = index; - - UString s; - if (arc->GetItemPath(index, s) != S_OK) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not retrieve path of archive item %1").arg(index)); - return E_FAIL; - } + UString s; + if (arc->GetItemPath(index, s) != S_OK) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot retrieve path of archive item %1.").arg(index)); + return E_FAIL; + } - const QString path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')); - const QFileInfo fi(QString::fromLatin1("%1/%2").arg(targetDir, path)); + const QFileInfo fi(QString::fromLatin1("%1/%2").arg(targetDir, UString2QString(s))); - DirectoryGuard guard(fi.absolutePath()); - const QStringList directories = guard.tryCreate(); + 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()); + bool isDir = false; + Archive_IsItem_Folder(arc->Archive, index, isDir); + if (isDir) + QDir(fi.absolutePath()).mkdir(fi.fileName()); - // this makes sure that all directories created get removed as well - foreach (const QString &directory, directories) - q->setCurrentFile(directory); + // this makes sure that all directories created get removed as well + foreach (const QString &directory, directories) + setCurrentFile(directory); - if (!isDir && !q->prepareForFile(fi.absoluteFilePath())) - return E_FAIL; + if (!isDir && !prepareForFile(fi.absoluteFilePath())) + return E_FAIL; - q->setCurrentFile(fi.absoluteFilePath()); + setCurrentFile(fi.absoluteFilePath()); - if (!isDir) { + if (!isDir) { #ifndef Q_OS_WIN - // do not follow symlinks, so we need to remove an existing one - if (fi.isSymLink() && (!QFile::remove(fi.absoluteFilePath()))) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not remove already existing symlink. %1").arg(fi.absoluteFilePath())); - return E_FAIL; - } + // do not follow symlinks, so we need to remove an existing one + if (fi.isSymLink() && (!QFile::remove(fi.absoluteFilePath()))) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot remove already existing symlink %1.").arg(fi.absoluteFilePath())); + return E_FAIL; + } #endif - QIODeviceSequentialOutStream *qOutStream = new QIODeviceSequentialOutStream( - new QFile(fi.absoluteFilePath()), QIODeviceSequentialOutStream::CloseAndDeleteDevice); - if (!qOutStream->errorString().isEmpty()) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not open file: %1 (%2)").arg(fi.absoluteFilePath(), - qOutStream->errorString())); - return E_FAIL; - } - CMyComPtr<ISequentialOutStream> stream = qOutStream; - *outStream = stream; - stream.Detach(); - } - - guard.release(); - return S_OK; + std::unique_ptr<QFile> file(new QFile(fi.absoluteFilePath())); + if (!file->open(QIODevice::WriteOnly)) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot open file \"%1\" for writing: %2").arg( + QDir::toNativeSeparators(fi.absoluteFilePath()), file->errorString())); + return E_FAIL; } - return E_FAIL; + CMyComPtr<ISequentialOutStream> stream = + new QIODeviceSequentialOutStream(std::move(file)); + *outStream = stream.Detach(); // CMyComPtr is needed, otherwise it crashes in Write(). } - /* reimp */ STDMETHOD(PrepareOperation)(Int32 askExtractMode) - { - Q_UNUSED(askExtractMode) + guard.release(); + return S_OK; +} + +STDMETHODIMP ExtractCallback::PrepareOperation(Int32 /*askExtractMode*/) +{ + return S_OK; +} + +STDMETHODIMP ExtractCallback::SetOperationResult(Int32 /*resultEOperationResult*/) +{ + if (targetDir.isEmpty()) return S_OK; - } - /* reimp */ STDMETHOD(SetOperationResult)(Int32 resultEOperationResult) - { - Q_UNUSED(resultEOperationResult) + UString s; + if (arc->GetItemPath(currentIndex, s) != S_OK) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot retrieve path of archive item %1.").arg(currentIndex)); + return E_FAIL; + } - if (!targetDir.isEmpty()) { - bool hasPerm = false; - const QFile::Permissions permissions = getPermissions(arc->Archive, currentIndex, &hasPerm); + const QString absFilePath = QFileInfo(QString::fromLatin1("%1/%2").arg(targetDir, + UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')))).absoluteFilePath(); - UString s; - if (arc->GetItemPath(currentIndex, s) != S_OK) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not retrieve path of archive item %1").arg(currentIndex)); - return E_FAIL; - } - const QString path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')); - const QString absFilePath = QFileInfo(QString::fromLatin1("%1/%2").arg(targetDir, path)) - .absoluteFilePath(); - - // 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)) { + // 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)) { #ifdef Q_OS_WIN - qFatal(QString::fromLatin1("Creating a link from archive is not implemented for windows. " - "Link filename: %1").arg(absFilePath).toLatin1()); - // TODO -// if (!CreateHardLinkWrapper(absFilePath, QLatin1String(symlinkTarget))) { -// return S_FALSE; -// } + qFatal(QString::fromLatin1("Creating a link from archive is not implemented for " + "windows. Link filename: %1").arg(absFilePath).toLatin1()); + // TODO + //if (!NFile::NDir::MyCreateHardLink(CFSTR(QDir::toNativeSeparators(absFilePath).utf16()), + // CFSTR(QDir::toNativeSeparators(symlinkTarget).utf16())) { + // return S_FALSE; + //} #else - QFileInfo symlinkPlaceHolderFileInfo(absFilePath); - if (symlinkPlaceHolderFileInfo.isSymLink()) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not create symlink at '%1'. Another one is already existing.") - .arg(absFilePath)); - return E_FAIL; - } - QFile symlinkPlaceHolderFile(absFilePath); - if (!symlinkPlaceHolderFile.open(QIODevice::ReadOnly)) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not read symlink target from file '%1'.").arg(absFilePath)); - return E_FAIL; - } + QFileInfo symlinkPlaceHolderFileInfo(absFilePath); + if (symlinkPlaceHolderFileInfo.isSymLink()) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot create symlink at \"%1\". Another one is already existing.") + .arg(absFilePath)); + return E_FAIL; + } + QFile symlinkPlaceHolderFile(absFilePath); + if (!symlinkPlaceHolderFile.open(QIODevice::ReadOnly)) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot read symlink target from file \"%1\".").arg(absFilePath)); + return E_FAIL; + } - const QByteArray symlinkTarget = symlinkPlaceHolderFile.readAll(); - symlinkPlaceHolderFile.close(); - symlinkPlaceHolderFile.remove(); - QFile targetFile(QString::fromLatin1(symlinkTarget)); - if (!targetFile.link(absFilePath)) { - Lib7z::setLastError(QCoreApplication::translate("ExtractCallbackImpl", - "Could not create symlink at %1. %2").arg(absFilePath, - targetFile.errorString())); - return E_FAIL; - } - return S_OK; + const QByteArray symlinkTarget = symlinkPlaceHolderFile.readAll(); + symlinkPlaceHolderFile.close(); + symlinkPlaceHolderFile.remove(); + QFile targetFile(QString::fromLatin1(symlinkTarget)); + if (!targetFile.link(absFilePath)) { + setLastError(QCoreApplication::translate("ExtractCallbackImpl", + "Cannot create symlink at %1: %2").arg(absFilePath, + targetFile.errorString())); + return E_FAIL; + } + return S_OK; #endif - } + } - try { - if (!absFilePath.isEmpty()) { - // This might fail for archives without all properties, we can only be sure about - // modification time, as it's always stored by default in 7z archives. Also note that - // we restore modification time on Unix only, as access time and change time are - // supposed to be set to the time of installation. - FILETIME mTime; - if (getFileTimeFromProperty(arc->Archive, currentIndex, kpidMTime, &mTime)) { - NWindows::NFile::NIO::COutFile file; - if (file.Open(QString2UString(absFilePath), 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL)) - file.SetTime(&mTime, &mTime, &mTime); - } + try { // Note: This part might also fail while running a elevated installation. + if (!absFilePath.isEmpty()) { + // This might fail for archives without all properties, we can only be sure + // about modification time, as it's always stored by default in 7z archives. + // Also note that we restore modification time on Unix only, as access time + // and change time are supposed to be set to the time of installation. + FILETIME mTime; + const UString fileName = QString2UString(absFilePath); + if (getFileTimeFromProperty(arc->Archive, currentIndex, kpidMTime, &mTime)) { + NWindows::NFile::NIO::COutFile file; + if (file.Open(fileName, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL)) + file.SetTime(&mTime, &mTime, &mTime); + } #ifdef Q_OS_WIN - FILETIME cTime, aTime; - bool success = getFileTimeFromProperty(arc->Archive, currentIndex, kpidCTime, &cTime); - if (success && getFileTimeFromProperty(arc->Archive, currentIndex, kpidATime, &aTime)) { - NWindows::NFile::NIO::COutFile file; - if (file.Open(QString2UString(absFilePath), 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL)) - file.SetTime(&cTime, &aTime, &mTime); - } + FILETIME cTime, aTime; + if (getFileTimeFromProperty(arc->Archive, currentIndex, kpidCTime, &cTime) + && getFileTimeFromProperty(arc->Archive, currentIndex, kpidATime, &aTime)) { + NWindows::NFile::NIO::COutFile file; + if (file.Open(fileName, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL)) + file.SetTime(&cTime, &aTime, &mTime); + } #endif - } - } catch (...) {} - - if (hasPerm) - QFile::setPermissions(absFilePath, permissions); } + } catch (...) {} - 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; -}; + bool hasPerm = false; + const QFile::Permissions permissions = getPermissions(arc->Archive, currentIndex, &hasPerm); + if (hasPerm) + QFile::setPermissions(absFilePath, permissions); + return S_OK; +} +/*! + \fn bool ExtractCallback::prepareForFile(const QString &filename) -class Lib7z::ExtractCallbackPrivate -{ -public: - explicit ExtractCallbackPrivate(ExtractCallback* qq) - { - impl = new ExtractCallbackImpl(qq); - } + Implement to prepare for file \a filename to be extracted, e.g. by renaming existing files. + Return \c true if the preparation was successful and extraction can be continued. If \c false + is returned, the extraction will be aborted. The default implementation returns \c true. +*/ - CMyComPtr<ExtractCallbackImpl> impl; -}; -ExtractCallback::ExtractCallback() - : d(new ExtractCallbackPrivate(this)) -{ -} +// -- UpdateCallback -ExtractCallback::~ExtractCallback() +HRESULT UpdateCallback::SetTotal(UInt64) { - delete d; + return S_OK; } -ExtractCallbackImpl* ExtractCallback::impl() +HRESULT UpdateCallback::SetCompleted(const UInt64*) { - return d->impl; + return S_OK; } -const ExtractCallbackImpl* ExtractCallback::impl() const +HRESULT UpdateCallback::SetRatioInfo(const UInt64*, const UInt64*) { - return d->impl; + return S_OK; } -void ExtractCallback::setTarget(QFileDevice* dev) +HRESULT UpdateCallback::CheckBreak() { - d->impl->setTarget(dev); + return S_OK; } -void ExtractCallback::setTarget(const QString &dir) +HRESULT UpdateCallback::Finilize() { - d->impl->setTarget(dir); + return S_OK; } -HRESULT ExtractCallback::setCompleted(quint64, quint64) +HRESULT UpdateCallback::SetNumFiles(UInt64) { return S_OK; } -void ExtractCallback::setCurrentFile(const QString&) +HRESULT UpdateCallback::GetStream(const wchar_t*, bool) { + return S_OK; } -bool ExtractCallback::prepareForFile(const QString&) +HRESULT UpdateCallback::OpenFileError(const wchar_t*, DWORD) { - return true; + return S_OK; } -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 - UpdateCallbackImpl() - { - } - 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 setSourcePaths(const QStringList &paths) - { - sourcePaths = paths; - } - void setTarget(QIODevice* archive) - { - target = archive; - } - -private: - QIODevice* target; - QStringList sourcePaths; -}; - -class Lib7z::UpdateCallbackPrivate -{ -public: - UpdateCallbackPrivate() - { - m_impl = new UpdateCallbackImpl; - } - - UpdateCallbackImpl* impl() - { - return m_impl; - } - -private: - CMyComPtr< UpdateCallbackImpl > m_impl; -}; - -UpdateCallback::UpdateCallback() - : d(new UpdateCallbackPrivate) +HRESULT UpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) { + *password = 0; + *passwordIsDefined = false; + return S_OK; } -UpdateCallback::~UpdateCallback() +HRESULT UpdateCallback::CryptoGetTextPassword(BSTR *password) { - delete d; + *password = 0; + return E_NOTIMPL; } -UpdateCallbackImpl* UpdateCallback::impl() +HRESULT UpdateCallback::OpenResult(const wchar_t*, HRESULT, const wchar_t*) { - return d->impl(); + return S_OK; } -void UpdateCallback::setSourcePaths(const QStringList &paths) +HRESULT UpdateCallback::StartScanning() { - d->impl()->setSourcePaths(paths); + return S_OK; } -void UpdateCallback::setTarget(QFileDevice* target) +HRESULT UpdateCallback::ScanProgress(UInt64, UInt64, UInt64, const wchar_t*, bool) { - d->impl()->setTarget(target); + return S_OK; } -class ExtractItemJob::Private -{ -public: - Private(ExtractItemJob* qq) - : q(qq) - , target(0) - , callback(new ExtractCallbackJobImpl(q)) - { - } - - ExtractItemJob* q; - File item; - QPointer<QFileDevice> archive; - QString targetDirectory; - QFileDevice* target; - ExtractCallback* callback; -}; - -ExtractItemJob::ExtractItemJob(QObject* parent) - : Job(parent) - , d(new Private(this)) +HRESULT UpdateCallback::CanNotFindError(const wchar_t*, DWORD) { + return S_OK; } -ExtractItemJob::~ExtractItemJob() +HRESULT UpdateCallback::FinishScanning() { - delete d; + return S_OK; } -File ExtractItemJob::item() const +HRESULT UpdateCallback::StartArchive(const wchar_t*, bool) { - return d->item; + return S_OK; } -void ExtractItemJob::setItem(const File& item) +HRESULT UpdateCallback::FinishArchive() { - d->item = item; + return S_OK; } -QFileDevice* ExtractItemJob::archive() const +HRESULT UpdateCallback::SetOperationResult(Int32) { - return d->archive; + return S_OK; } -void ExtractItemJob::setArchive(QFileDevice* archive) +/*! + Function to create an empty 7z container. Using a temporary file only is not working, since + 7z checks the output file for a valid signature, otherwise it rejects overwriting the file. +*/ +static QString createTmp7z() { - d->archive = archive; -} + QTemporaryFile file; + if (!file.open()) { + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot create " + "temporary file: %1").arg(file.errorString())); + } -QString ExtractItemJob::targetDirectory() const -{ - return d->targetDirectory; + file.write(QByteArray::fromHex("377A.BCAF.271C" // Signature. + ".0003.8D9B.D50F.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000" // Crc + Data. + )); + file.setAutoRemove(false); + return file.fileName(); } -void ExtractItemJob::setTargetDirectory(const QString &dir) -{ - d->targetDirectory = dir; - d->target = 0; -} +/*! + Creates an archive using the given file device \a archive. \a sourcePaths can contain one or + more files, one or more directories or a combination of files and folders. The \c * wildcard + is supported also. The value of \a level specifies the compression ratio, the default is set + to \c 5 (Normal compression). The \a callback can be used to get information about the archive + creation process. If no \a callback is given, an empty implementation is used. -void ExtractItemJob::setTarget(QFileDevice* dev) + \note Throws SevenZipException on error. + \note Filenames are stored case-sensitive with UTF-8 encoding. + \note The ownership of \a callback is transferred to the function and gets delete on exit. +*/ +void INSTALLER_EXPORT createArchive(QFileDevice *archive, const QStringList &sources, + Compression level, UpdateCallback *callback) { - d->target = dev; -} + LIB7Z_ASSERTS(archive, Writable) -namespace{ - QString errorMessageFrom7zResult(const LONG & extractResult) - { - if (!Lib7z::lastError().isEmpty()) - return Lib7z::lastError(); - - QString errorMessage = QCoreApplication::translate("Lib7z", "internal code: %1"); - switch (extractResult) { - case S_OK: - qFatal("S_OK value is not a valid error code."); - break; - case E_NOTIMPL: - errorMessage = errorMessage.arg(QLatin1String("E_NOTIMPL")); - break; - case E_NOINTERFACE: - errorMessage = errorMessage.arg(QLatin1String("E_NOINTERFACE")); - break; - case E_ABORT: - errorMessage = errorMessage.arg(QLatin1String("E_ABORT")); - break; - case E_FAIL: - errorMessage = errorMessage.arg(QLatin1String("E_FAIL")); - break; - case STG_E_INVALIDFUNCTION: - errorMessage = errorMessage.arg(QLatin1String("STG_E_INVALIDFUNCTION")); - break; - case E_OUTOFMEMORY: - errorMessage = QCoreApplication::translate("Lib7z", "not enough memory"); - break; - case E_INVALIDARG: - errorMessage = errorMessage.arg(QLatin1String("E_INVALIDARG")); - break; - default: - errorMessage = QCoreApplication::translate("Lib7z", "Error: %1").arg(extractResult); - break; - } - return errorMessage; - } -} + const QString tmpArchive = createTmp7z(); + Lib7z::createArchive(tmpArchive, sources, QTmpFile::No, level, callback); -void Lib7z::createArchive(QFileDevice* archive, const QStringList &sourcePaths, UpdateCallback* callback) + try { + QFile source(tmpArchive); + QInstaller::openForRead(&source); + QInstaller::blockingCopy(&source, archive, source.size()); + } catch (const QInstaller::Error &error) { + throw SevenZipException(error.message()); + } +} + +/*! + Creates an archive with the given filename \a archive. \a sourcePaths can contain one or more + files, one or more directories or a combination of files and folders. Also the \c * wildcard + is supported. To be able to use the function during an elevated installation, set \a mode to + \c QTmpFile::Yes. The value of \a level specifies the compression ratio, the default is set + to \c 5 (Normal compression). The \a callback can be used to get information about the archive + creation process. If no \a callback is given, an empty implementation is used. + + \note Throws SevenZipException on error. + \note If \a archive exists, it will be overwritten. + \note Filenames are stored case-sensitive with UTF-8 encoding. + \note The ownership of \a callback is transferred to the function and gets delete on exit. +*/ +void createArchive(const QString &archive, const QStringList &sources, QTmpFile mode, + Compression level, UpdateCallback *callback) { - assert(archive); - - QScopedPointer<UpdateCallback> dummyCallback(callback ? 0 : new UpdateCallback); - if (!callback) - callback = dummyCallback.data(); - try { - callback->setTarget(archive); - - QScopedPointer<CCodecs> codecs(new CCodecs); - if (codecs->Load() != S_OK) - throw SevenZipException(QCoreApplication::translate("Lib7z", "Could not load codecs")); - - CIntVector formatIndices; - - if (!codecs.data()->FindFormatForArchiveType(L"", formatIndices)) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "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; - foreach (const QString &path, sourcePaths) { - const QString cleanPath = QDir::toNativeSeparators(QDir::cleanPath(path)); - const UString nativePath = QString2UString(cleanPath); - if (UString2QString(nativePath) != cleanPath) { - throw SevenZipException(QCoreApplication::translate("Lib7z", "Could not convert" - "path: %1.").arg(path)); - } - censor.AddItem(true /* always include item */, nativePath, false /* never recurse*/); + QString target = archive; + if (mode == QTmpFile::Yes) + target = createTmp7z(); + + CArcCmdLineOptions options; + try { + UStringVector commandStrings; + commandStrings.Add(L"a"); // mode: add + commandStrings.Add(L"-t7z"); // type: 7z + commandStrings.Add(L"-mtm=on"); // time: modeifier|creation|access + commandStrings.Add(L"-mtc=on"); + commandStrings.Add(L"-mta=on"); + commandStrings.Add(L"-mmt=on"); // threads: multi-threaded +#ifdef Q_OS_WIN + commandStrings.Add(L"-sccUTF-8"); // files: case-sensitive|UTF8 +#endif + commandStrings.Add(QString2UString(QString::fromLatin1("-mx=%1").arg(int(level)))); // compression: level + commandStrings.Add(QString2UString(QDir::toNativeSeparators(target))); + foreach (const QString &source, sources) + commandStrings.Add(QString2UString(source)); + + CArcCmdLineParser parser; + parser.Parse1(commandStrings, options); + parser.Parse2(options); + } catch (const CArcCmdLineException &e) { + throw SevenZipException(UString2QString(e)); } - callback->setSourcePaths(sourcePaths); - - CArchivePath archivePath; - archivePath.ParseFromPath(QString2UString(tempFile)); - CUpdateArchiveCommand command; - command.ArchivePath = archivePath; - command.ActionSet = NUpdateArchive::kAddActionSet; + CCodecs codecs; + if (codecs.Load() != S_OK) + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot load codecs.")); - CUpdateOptions options; - options.Commands.Add(command); - options.ArchivePath = archivePath; - options.MethodMode.FormatIndex = codecs->FindFormatForArchiveType(L"7z"); - - // preserve creation time - CProperty tc; - tc.Name = UString(L"TC"); - tc.Value = UString(L"ON"); - options.MethodMode.Properties.Add(tc); - - // preserve access time - CProperty ta; - ta.Name = UString(L"TA"); - ta.Value = UString(L"ON"); - options.MethodMode.Properties.Add(ta); + CObjectVector<COpenType> types; + if (!ParseOpenTypes(codecs, options.ArcType, types)) + throw SevenZipException(QCoreApplication::translate("Lib7z", "Unsupported archive type.")); CUpdateErrorInfo errorInfo; - const HRESULT res = UpdateArchive(codecs.data(), censor, options, errorInfo, 0, callback->impl()); - if (res != S_OK || !QFile::exists(tempFile)) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not create archive %1. %2").arg(tempFile, errorMessageFrom7zResult(res))); + CMyComPtr<UpdateCallback> comCallback = callback == 0 ? new UpdateCallback : callback; + const HRESULT res = UpdateArchive(&codecs, types, options.ArchiveName, options.Censor, + options.UpdateOptions, errorInfo, nullptr, comCallback, true); + + const QFile tempFile(UString2QString(options.ArchiveName)); + if (res != S_OK || !tempFile.exists()) { + QString errorMsg; + if (res == S_OK) { + errorMsg = QCoreApplication::translate("Lib7z", "Cannot create archive \"%1\"") + .arg(QDir::toNativeSeparators(tempFile.fileName())); + } else { + errorMsg = QCoreApplication::translate("Lib7z", "Cannot create archive \"%1\": %2") + .arg(QDir::toNativeSeparators(tempFile.fileName()), errorMessageFrom7zResult(res)); + } + throw SevenZipException(errorMsg); } - { - //TODO remove temp file even if one the following throws - QFile file(tempFile); - QInstaller::openForRead(&file); - QInstaller::blockingCopy(&file, archive, file.size()); - } + if (mode == QTmpFile::Yes) { + QFile org(archive); + if (org.exists() && !org.remove()) { + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot remove " + "old archive \"%1\": %2").arg(QDir::toNativeSeparators(org.fileName()), + org.errorString())); + } - QFile file(tempFile); - if (!file.remove()) { - qWarning("%s: Could not remove temporary file %s: %s", Q_FUNC_INFO, qPrintable(tempFile), - qPrintable(file.errorString())); + QFile arc(UString2QString(options.ArchiveName)); + if(!arc.rename(archive)) { + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot rename " + "temporary archive \"%1\" to \"%2\": %3").arg( + QDir::toNativeSeparators(arc.fileName()), + QDir::toNativeSeparators(archive), + arc.errorString())); + } } } catch (const char *err) { - qDebug() << err; throw SevenZipException(err); + } catch (SevenZipException &e) { + throw e; // re-throw unmodified } catch (const QInstaller::Error &err) { throw SevenZipException(err.message()); } catch (...) { - throw SevenZipException(QCoreApplication::translate("Lib7z", "Unknown exception caught (%1)") - .arg(QString::fromLatin1(Q_FUNC_INFO))); + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Unknown exception caught (%1)").arg(QString::fromLatin1(Q_FUNC_INFO))); } } -void Lib7z::extractFileFromArchive(QFileDevice* archive, const File& item, QFileDevice* target, - ExtractCallback* callback) +/*! + Extracts the given \a archive content into target directory \a directory using the provided + extract callback \a callback. The output filenames are deduced from the \a archive content. + + \note Throws SevenZipException on error. + \note The ownership of \a callback is not transferred to the function. +*/ +void extractArchive(QFileDevice *archive, const QString &directory, ExtractCallback *callback) { - assert(archive); - assert(target); + LIB7Z_ASSERTS(archive, Readable) - std::auto_ptr<ExtractCallback> dummyCallback(callback ? 0 : new ExtractCallback); - if (!callback) - callback = dummyCallback.get(); + // Guard a given object against unwanted delete. + CMyComPtr<ExtractCallback> externCallback = callback; + + CMyComPtr<ExtractCallback> localCallback; + if (!externCallback) { + callback = new ExtractCallback; + localCallback = callback; + } + DirectoryGuard outDir(QFileInfo(directory).absolutePath()); try { - const OpenArchiveInfo* const openArchive = OpenArchiveInfo::value(archive); + outDir.tryCreate(); - const int arcIdx = item.archiveIndex.x(); - if (arcIdx < 0 || arcIdx >= openArchive->archiveLink.Arcs.Size()) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "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; + CCodecs codecs; + if (codecs.Load() != S_OK) + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot load codecs.")); - const UInt32 itemIdx = item.archiveIndex.y(); - UInt32 numItems = 0; - if (parchive->GetNumberOfItems(&numItems) != S_OK) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not retrieve number of items in archive")); - } + COpenOptions op; + op.codecs = &codecs; - if (itemIdx >= numItems) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "Item index %1 out of bounds [0, %2]").arg(itemIdx).arg(numItems - 1)); - } + CObjectVector<COpenType> types; + op.types = &types; // Empty, because we use a stream. - UString s; - if (arc.GetItemPath(itemIdx, s) != S_OK) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not retrieve path of archive item %1").arg(itemIdx)); - } - assert(item.path == UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/'))); + CIntVector excluded; + op.excludedFormats = &excluded; - callback->setTarget(target); - const LONG extractResult = parchive->Extract(&itemIdx, 1, /*testmode=*/0, callback->impl()); + const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); + op.stream = stream; // CMyComPtr is needed, otherwise it crashes in OpenStream(). - if (extractResult != S_OK) - throw SevenZipException(errorMessageFrom7zResult(extractResult)); + CObjectVector<CProperty> properties; + op.props = &properties; - } catch (const char *err) { - throw SevenZipException(err); - } catch (const Lib7z::SevenZipException& e) { - throw e; - } catch (...) { - throw SevenZipException(QCoreApplication::translate("Lib7z", "Unknown exception caught (%1)") - .arg(QString::fromLatin1(Q_FUNC_INFO))); - } -} - -void Lib7z::extractFileFromArchive(QFileDevice* archive, const File& item, - const QString &targetDirectory, ExtractCallback* callback) -{ - assert(archive); + CArchiveLink archiveLink; + if (archiveLink.Open2(op, nullptr) != S_OK) { + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Cannot open archive \"%1\".").arg(archive->fileName())); + } - QScopedPointer<ExtractCallback> dummyCallback(callback ? 0 : new ExtractCallback); - if (!callback) - callback = dummyCallback.data(); + callback->setTarget(directory); + for (unsigned a = 0; a < archiveLink.Arcs.Size(); ++a) { + callback->setArchive(&archiveLink.Arcs[a]); + IInArchive *const arch = archiveLink.Arcs[a].Archive; - 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 + const LONG result = arch->Extract(0, static_cast<UInt32>(-1), false, callback); + if (result != S_OK) + throw SevenZipException(errorMessageFrom7zResult(result)); + } + } catch (const SevenZipException &e) { + externCallback.Detach(); + throw e; // re-throw unmodified + } catch (...) { + externCallback.Detach(); throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not create output file for writing: %1").arg(fi.absoluteFilePath())); + "Unknown exception caught (%1).").arg(QString::fromLatin1(Q_FUNC_INFO))); } - callback->setTarget(&out); - extractFileFromArchive(archive, item, &out, callback); - if (item.permissions) - out.setPermissions(item.permissions); outDir.release(); + externCallback.Detach(); } -void Lib7z::extractArchive(QFileDevice* archive, const QString &targetDirectory, - ExtractCallback* callback) -{ - assert(archive); - - QScopedPointer<ExtractCallback> dummyCallback(callback ? 0 : new ExtractCallback); - if (!callback) - callback = dummyCallback.data(); - - callback->setTarget(targetDirectory); - - const QFileInfo fi(targetDirectory); - DirectoryGuard outDir(fi.absolutePath()); - outDir.tryCreate(); - - const OpenArchiveInfo* const openArchive = OpenArchiveInfo::value(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()); - - if (extractResult != S_OK) - throw SevenZipException(errorMessageFrom7zResult(extractResult)); - } - - outDir.release(); -} +/*! + Returns \c true if the given \a archive is supported; otherwise returns \c false. -bool Lib7z::isSupportedArchive(const QString &archive) + \note Throws SevenZipException on error. +*/ +bool isSupportedArchive(QFileDevice *archive) { - QFile file(archive); - if (!file.open(QIODevice::ReadOnly)) - return false; + LIB7Z_ASSERTS(archive, Readable) - return isSupportedArchive(&file); -} - -bool Lib7z::isSupportedArchive(QFileDevice* archive) -{ - assert(archive); - assert(!archive->isSequential()); const qint64 initialPos = archive->pos(); try { - QScopedPointer<CCodecs> codecs(new CCodecs); - if (codecs->Load() != S_OK) - throw SevenZipException(QCoreApplication::translate("Lib7z", "Could not load codecs")); + CCodecs codecs; + if (codecs.Load() != S_OK) + throw SevenZipException(QCoreApplication::translate("Lib7z", "Cannot load codecs.")); - CIntVector formatIndices; + COpenOptions op; + op.codecs = &codecs; + + CObjectVector<COpenType> types; + op.types = &types; // Empty, because we use a stream. + + CIntVector excluded; + op.excludedFormats = &excluded; - if (!codecs->FindFormatForArchiveType(L"", formatIndices)) { - throw SevenZipException(QCoreApplication::translate("Lib7z", - "Could not retrieve default format")); - } - CArchiveLink archiveLink; - //CMyComPtr is needed, otherwise it crashes in OpenStream() const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); + op.stream = stream; // CMyComPtr is needed, otherwise it crashes in OpenStream(). + + CObjectVector<CProperty> properties; + op.props = &properties; - const HRESULT result = archiveLink.Open2(codecs.data(), formatIndices, /*stdInMode*/false, stream, - UString(), 0); + CArchiveLink archiveLink; + const HRESULT result = archiveLink.Open2(op, nullptr); 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 (const SevenZipException &e) { + archive->seek(initialPos); + throw e; // re-throw unmodified } catch (...) { archive->seek(initialPos); - throw SevenZipException(QCoreApplication::translate("Lib7z", "Unknown exception caught (%1)") - .arg(QString::fromLatin1(Q_FUNC_INFO))); + throw SevenZipException(QCoreApplication::translate("Lib7z", + "Unknown exception caught (%1).").arg(QString::fromLatin1(Q_FUNC_INFO))); } return false; // never reached } -void ExtractItemJob::doStart() +/*! + Returns \c true if the given \a archive is supported; otherwise returns \c false. + + \note Throws SevenZipException on error. +*/ +bool isSupportedArchive(const QString &archive) { - try { - if (!d->archive) - throw SevenZipException(tr("Could not list archive: QIODevice not set or already destroyed.")); - if (d->target) - extractFileFromArchive(d->archive, d->item, d->target, d->callback); - else if (!d->item.path.isEmpty()) - extractFileFromArchive(d->archive, d->item, d->targetDirectory, d->callback); - else - extractArchive(d->archive, d->targetDirectory, d->callback); - } catch (const SevenZipException& e) { - setError(Failed); - setErrorString(tr("Error while extracting '%1': %2").arg(d->item.path, e.message())); - } catch (...) { - setError(Failed); - setErrorString(tr("Unknown exception caught (%1)").arg(tr("Failed"))); - } - emitResult(); + QFile file(archive); + if (!file.open(QIODevice::ReadOnly)) + return false; + return isSupportedArchive(&file); } + +} // namespace Lib7z |