Files
qt-creator/installer/qinstaller.cpp

1873 lines
54 KiB
C++

/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/
#include "qinstaller.h"
#include <QtCore/QBuffer>
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QDirIterator>
#include <QtCore/QFile>
#include <QtCore/QHash>
#include <QtCore/QProcess>
#include <QtCore/QSettings>
#ifdef Q_OS_WIN
#include "qt_windows.h"
#include <shlobj.h>
#endif
QT_BEGIN_NAMESPACE
/*
FIXME: Documentation
NAME = "Name";
DISPLAY_NAME = "DisplayName";
DESCRIPTION = "Description";
TASK_COUNT = "TaskCount";
SIZE = "ComponentSize";
OUTPUT_FILE = "OutputFile";
WANTED_STATE = "WantedState";
SUGGESTED_STATE = "SuggestedState";
PRODUCT_NAME = "ProductName";
GUI_START_PAGE = "GuiStartPage";
RUN_PROGRAM = "RunProgram";
COMMENTS = "Comments";
CONTACT = "Contact";
DISPLAY_VERSION = "DisplayVersion";
ESTIMATED_SIZE = "EstimatedSize";
HELP_LINK = "HelpLink";
INSTALL_DATE = "InstallDate";
INSTALL_LOCATION = "InstallLocation";
NO_MODIFY = "NoModify";
NO_REPAIR = "NoRepair";
PUBLISHER = "Publisher";
UNINSTALL_STRING = "UninstallString";
URL_INFO_ABOUT = "UrlInfoAbout";
LOGO_PIXMAP
WATERMARK_PIXMAP
*/
//static qint64 magicInstallerMarker = (0xdea0d345UL << 32) + 0x12023233UL;
//static qint64 magicUninstallerMarker = (0xdea0d345UL << 32) + 0x12023234UL;
static const qint64 magicInstallerMarker = 0x12023233UL;
static const qint64 magicUninstallerMarker = 0x12023234UL;
static const qint64 magicTempUninstallerMarker = 0x12023235UL;
// Installer Layout:
//
// 0000: <binary: installer code>
// ...
// $comptask[0]: <int: $ctc[0]: 1st component task count>
// $comptask[0][0]: <task: first component task data>
// ...
// $comptask[0][$ctc0-1]: <task: first component task data>
// ...
// $comptask[$ncomp-1]: <int : $cnn: last component task data>
// $comptask[$ncomp-1][0]: <task: last component task data>
// ...
// $comtaskp[$ncomp-1][$ctc-1]: <task: last component task data>
// $compvars[0]: <dict: $cn0: first component variables>
// ...
// $compvars[$ncomp-1]: <dict: $cnn: last component var data>
// $comptaskoffsets: <int: $comptask[0]: offset>
// <int: $comptask[1]: offset>
// ...
// $compvarsoffsets: <int: $compvars[0]: offset>
// <int: $compvars[1]: offset>
// ...
// $installervars: <dict: installer variable data>
// ...
// end - 7: <int: $comptask[0]: start of comp.tasks>
// end - 6: <int: $compvars[0]: start of .vars>
// end - 5: <int: $ncomp: number of components>
// end - 4: <int: $comptaskoffsets>
// end - 3: <int: $compvarsoffsets>
// end - 2: <int: $installervars: offset installer vars>
// end - 1: <int: magic marker>
//
// The stuff after the binary is not present in the "Creator" incarnation
////////////////////////////////////////////////////////////////////
//
// Misc helpers
//
////////////////////////////////////////////////////////////////////
namespace {
#define ifVerbose(s) if (!installer()->isVerbose()) {} else { qDebug() << s; }
QDebug &operator<<(QDebug &os, QInstallerTask *task)
{
task->dump(os);
return os;
}
class Dictionary : public QHash<QString, QString>
{
public:
typedef QHash<QString, QString> BaseType;
void setValue(const QString &key, const QString &val)
{
insert(key, val);
}
void removeTemporaryKeys()
{
foreach (const QString &key, keys())
if (key.startsWith('@'))
remove(key);
}
};
//////////////////////////////////////////////////////////////////////
class Error : public QInstallerError
{
public:
Error(const QString &m)
: QInstallerError(m) {}
// convenience
Error(const char *m, int n)
: QInstallerError(QString(QLatin1String(m)).arg(n)) {}
Error(const char *m, const QString & n)
: QInstallerError(QString(QLatin1String(m)).arg(n)) {}
private:
private:
QString m_message;
};
} // anonymous namespace
static void openForWrite(QFile &file)
{
if (!file.open(QIODevice::WriteOnly))
throw Error("Cannot open file %1 for writing", file.fileName());
}
static void openForRead(QFile &file)
{
if (!file.open(QIODevice::ReadOnly))
throw Error("Cannot open file %1 for reading", file.fileName());
}
static void rawWrite(QIODevice *out, const char *buffer, qint64 size)
{
while (size > 0) {
qint64 n = out->write(buffer, size);
if (n == -1)
throw Error("RAW WRITE FAILED: %1", size);
size -= n;
}
}
static void rawRead(QIODevice *in, char *buffer, qint64 size)
{
while (size > 0) {
qint64 n = in->read(buffer, size);
size -= n;
buffer += n;
if (size != 0)
qDebug() << "COULD ONLY READ " << n << "OF" << size + n << "BYTES";
}
}
static inline QByteArray &theBuffer(int size)
{
static QByteArray b;
if (size > b.size())
b.reserve(size * 3 / 2);
return b;
}
#if 0
// Faster or not?
static void appendFileData(QIODevice *out, const QString &fileName)
{
QFile file(fileName);
openForRead(file);
qint64 size = file.size();
QInstaller::appendInt(out, size);
if (size == 0)
return;
uchar *data = file.map(0, size);
if (!data)
throw Error(QInstaller::tr("Cannot map file %1").arg(file.fileName()));
rawWrite(out, (const char *)data, size);
if (!file.unmap(data))
throw Error(QInstaller::tr("Cannot unmap file %1").arg(file.fileName()));
}
#endif
static void appendFileData(QIODevice *out, QIODevice *in)
{
QTC_ASSERT(!in->isSequential(), return);
qint64 size = in->size();
QByteArray &b = theBuffer(size);
rawRead(in, b.data(), size);
QInstaller::appendInt(out, size);
rawWrite(out, b.constData(), size);
}
static void retrieveFileData(QIODevice *out, QIODevice *in)
{
qint64 size = QInstaller::retrieveInt(in);
QByteArray &b = theBuffer(size);
rawRead(in, b.data(), size);
rawWrite(out, b.constData(), size);
}
static void appendData(QIODevice *out, QIODevice *in, qint64 size)
{
QByteArray &b = theBuffer(size);
rawRead(in, b.data(), size);
rawWrite(out, b.constData(), size);
}
static void appendInt(QIODevice *out, qint64 n)
{
rawWrite(out, (char*)&n, sizeof(n));
}
static void appendString(QIODevice *out, const QString &str)
{
int n = str.size();
appendInt(out, n);
rawWrite(out, (char*)str.utf16(), n * sizeof(QChar));
}
static void appendByteArray(QIODevice *out, const QByteArray &ba)
{
appendInt(out, ba.size());
rawWrite(out, ba.constData(), ba.size());
}
static void appendDictionary(QIODevice *out, const Dictionary &dict)
{
appendInt(out, dict.size());
foreach (const QString &key, dict.keys()) {
appendString(out, key);
appendString(out, dict.value(key));
}
}
static qint64 retrieveInt(QIODevice *in)
{
qint64 n = 0;
in->read((char*)&n, sizeof(n));
return n;
}
static QString retrieveString(QIODevice *in)
{
static QByteArray b;
qint64 n = retrieveInt(in);
if (n * int(sizeof(QChar)) > b.size())
b.reserve(n * sizeof(QChar) * 3 / 2);
in->read(b.data(), n * sizeof(QChar));
QString str((QChar *)b.data(), n);
return str;
}
static QByteArray retrieveByteArray(QIODevice *in)
{
QByteArray ba;
qint64 n = retrieveInt(in);
ba.resize(n);
rawRead(in, ba.data(), n);
return ba;
}
static Dictionary retrieveDictionary(QIODevice *in)
{
Dictionary dict;
for (qint64 i = retrieveInt(in); --i >= 0; ) {
QString key = retrieveString(in);
dict.insert(key, retrieveString(in));
}
return dict;
}
////////////////////////////////////////////////////////////////////
//
// QInstallerComponent::Private
//
////////////////////////////////////////////////////////////////////
class QInstallerComponent::Private
{
public:
QInstaller *m_installer;
Dictionary m_vars;
QList<QInstallerTask *> m_tasks;
// filled before intaller runs
qint64 m_offsetInInstaller;
};
#if 0
// this is dead slow as QDirIterator::Private::advance needlessly
// creates tons of QFileInfo objects
static void listDir
(const QString &sourcePath0, const QString &targetPath0,
QList<QInstallerTask *> *copyTasks,
QList<QInstallerTask *> *linkTasks,
int sourcePathLength)
{
Q_UNUSED(sourcePathLength);
QDirIterator it(sourcePath0, QDir::Files, QDirIterator::Subdirectories);
const int pos = sourcePath0.size();
while (it.hasNext()) {
QString sourcePath = it.next();
QFileInfo sourceInfo = it.fileInfo();
if (sourceInfo.isSymLink()) {
QFileInfo absSourceInfo(sourceInfo.absoluteFilePath());
//QString linkTarget = sourceInfo.symLinkTarget();
QString absLinkTarget = absSourceInfo.symLinkTarget();
//QString relPath = sourceInfo.dir().relativeFilePath(linkTarget);
QString absRelPath = absSourceInfo.dir().relativeFilePath(absLinkTarget);
if (0) {
ifVerbose("\n\nCREATING LINK: "
<< "\nSOURCE : " << sourceInfo.filePath()
<< "\nSOURCE ABS: " << absSourceInfo.filePath()
//<< "\nLINK : " << linkTarget
<< "\nLINK ABS: " << absLinkTarget
//<< "\nREL : " << relPath
<< "\nREL ABS: " << absRelPath);
}
QString targetPath = targetPath0;
targetPath += sourcePath.midRef(pos);
//qDebug() << "LINK " << absRelPath << targetPath << targetPath0;
QInstallerLinkFileTask *task = new QInstallerLinkFileTask(m_installer);
task->setLinkTargetPath(absRelPath);
task->setTargetPath(targetPath);
task->setPermissions(sourceInfo.permissions());
linkTasks->append(task);
} else {
QInstallerCopyFileTask *task = new QInstallerCopyFileTask(m_installer);
QString targetPath = targetPath0;
targetPath += sourcePath.midRef(pos);
//qDebug() << "COPY " << sourcePath << targetPath << targetPath0;
task->setSourcePath(sourcePath);
task->setTargetPath(targetPath);
task->setPermissions(sourceInfo.permissions());
copyTasks.append(task);
}
}
}
#else
static void listDir
(const QString &sourcePath, const QString &targetPath0,
QList<QInstallerTask *> *copyTasks,
QList<QInstallerTask *> *linkTasks,
QInstaller *installer,
int sourcePathLength = -1)
{
if (sourcePathLength == -1)
sourcePathLength = sourcePath.size();
QFileInfo sourceInfo(sourcePath);
if (sourceInfo.isDir()) {
QDir dir(sourcePath);
dir.setSorting(QDir::Unsorted);
foreach (const QFileInfo &fi, dir.entryInfoList()) {
QString sourceFile = fi.fileName();
if (sourceFile == QLatin1String(".")
|| sourceFile == QLatin1String(".."))
continue;
listDir(sourcePath + '/' + sourceFile, targetPath0,
copyTasks, linkTasks, installer, sourcePathLength);
}
} else if (sourceInfo.isSymLink()) {
QFileInfo absSourceInfo(sourceInfo.absoluteFilePath());
QString absLinkTarget = absSourceInfo.symLinkTarget();
QString absRelPath = absSourceInfo.dir().relativeFilePath(absLinkTarget);
QString targetPath = targetPath0;
targetPath += sourcePath.midRef(sourcePathLength);
//qDebug() << "LINK " << absRelPath << targetPath << targetPath0;
QInstallerLinkFileTask *task = new QInstallerLinkFileTask(installer);
task->setLinkTargetPath(absRelPath);
task->setTargetPath(targetPath);
task->setPermissions(sourceInfo.permissions());
linkTasks->append(task);
} else {
QInstallerCopyFileTask *task = new QInstallerCopyFileTask(installer);
QString targetPath = targetPath0;
targetPath += sourcePath.midRef(sourcePathLength);
//qDebug() << "COPY " << sourcePath << targetPath << targetPath0;
task->setSourcePath(sourcePath);
task->setTargetPath(targetPath);
task->setPermissions(sourceInfo.permissions());
copyTasks->append(task);
}
}
#endif
////////////////////////////////////////////////////////////////////
//
// QInstallerComponent
//
////////////////////////////////////////////////////////////////////
QInstallerComponent::QInstallerComponent(QInstaller *installer)
: d(new QInstallerComponent::Private)
{
d->m_installer = installer;
}
QInstallerComponent::~QInstallerComponent()
{
qDeleteAll(d->m_tasks);
d->m_tasks.clear();
delete d;
}
QString QInstallerComponent::value(const QString &key,
const QString &defaultValue) const
{
return d->m_vars.value(key, defaultValue);
}
void QInstallerComponent::setValue(const QString &key, const QString &value)
{
d->m_vars[key] = value;
}
void QInstallerComponent::appendDirectoryTasks
(const QString &sourcePath0, const QString &targetPath)
{
QList<QInstallerTask *> copyTasks;
QList<QInstallerTask *> linkTasks;
QString sourcePath = d->m_installer->replaceVariables(sourcePath0);
listDir(sourcePath, targetPath, &copyTasks, &linkTasks, d->m_installer);
d->m_tasks += copyTasks;
d->m_tasks += linkTasks;
}
void QInstallerComponent::appendSettingsTask
(const QString &key, const QString &value)
{
QInstallerWriteSettingsTask *task =
new QInstallerWriteSettingsTask(d->m_installer);
task->setKey(key);
task->setValue(value);
appendTask(task);
}
void QInstallerComponent::appendTask(QInstallerTask *task)
{
d->m_tasks.append(task);
}
int QInstallerComponent::taskCount() const
{
return d->m_tasks.size();
}
QInstallerTask *QInstallerComponent::task(int i) const
{
return d->m_tasks.at(i);
}
////////////////////////////////////////////////////////////////////
//
// QInstaller::Private
//
////////////////////////////////////////////////////////////////////
QInstallerTask *createCopyFileTask(QInstaller *installer)
{
return new QInstallerCopyFileTask(installer);
}
QInstallerTask *createLinkFileTask(QInstaller *installer)
{
return new QInstallerLinkFileTask(installer);
}
QInstallerTask *createWriteSettingsTask(QInstaller *installer)
{
return new QInstallerWriteSettingsTask(installer);
}
QInstallerTask *createPatchFileTask(QInstaller *installer)
{
return new QInstallerPatchFileTask(installer);
}
QInstallerTask *createMenuShortcutTask(QInstaller *installer)
{
return new QInstallerMenuShortcutTask(installer);
}
class QInstaller::Private : public QObject
{
Q_OBJECT;
public:
explicit Private(QInstaller *);
~Private();
void initialize();
QInstallerTask *createTaskFromCode(int code);
void undo(const QList<QInstallerTask *> &tasks);
void writeUninstaller(const QList<QInstallerTask *> &tasks);
bool statusCanceledOrFailed() const;
void writeInstaller(QIODevice *out);
void writeInstaller();
void appendCode(QIODevice *out);
void runInstaller();
void runUninstaller();
void deleteUninstaller();
QString uninstallerName() const;
QString replaceVariables(const QString &str) const;
QByteArray replaceVariables(const QByteArray &str) const;
QString registerPath() const;
void registerInstaller();
void unregisterInstaller();
QString installerBinaryPath() const;
bool isCreator() const;
bool isInstaller() const;
bool isUninstaller() const;
bool isTempUninstaller() const;
QInstaller *installer() const { return q; }
bool restartTempUninstaller(const QStringList &args);
void setInstallationProgress(qint64 progress); // relative to m_totalProgress
signals:
void installationStarted();
void installationFinished();
void uninstallationStarted();
void uninstallationFinished();
public:
QInstaller *q;
Dictionary m_vars;
QInstaller::InstallerStatus m_status;
bool m_verbose;
qint64 m_codeSize;
qint64 m_tasksStart;
qint64 m_variablesStart;
qint64 m_componentCount;
qint64 m_tasksOffsetsStart;
qint64 m_variablesOffsetsStart;
qint64 m_variableDataStart;
qint64 m_magicMarker;
int m_installationProgress;
int m_totalProgress;
QString m_installationProgressText;
// Owned. Indexed by component name
QList<QInstallerComponent *> m_components;
QList<QInstaller::TaskCreator> m_taskCreators;
};
QInstaller::Private::Private(QInstaller *q_)
: q(q_), m_status(QInstaller::InstallerUnfinished), m_verbose(false)
{
connect(this, SIGNAL(installationStarted()),
q, SIGNAL(installationStarted()));
connect(this, SIGNAL(installationFinished()),
q, SIGNAL(installationFinished()));
connect(this, SIGNAL(uninstallationStarted()),
q, SIGNAL(uninstallationStarted()));
connect(this, SIGNAL(uninstallationFinished()),
q, SIGNAL(uninstallationFinished()));
}
QInstaller::Private::~Private()
{
qDeleteAll(m_components);
m_components.clear();
}
void QInstaller::Private::initialize()
{
m_installationProgress = 0;
m_totalProgress = 100;
m_vars["ProductName"] = "Unknown Product";
m_vars["LogoPixmap"] = ":/resources/logo.png";
m_vars["WatermarkPixmap"] = ":/resources/watermark.png";
QFile in(installerBinaryPath());
openForRead(in);
m_codeSize = in.size();
// this reads bogus values for 'creators', but it does not harm
in.seek(in.size() - 7 * sizeof(qint64));
m_tasksStart = retrieveInt(&in);
m_variablesStart = retrieveInt(&in);
m_componentCount = retrieveInt(&in);
m_tasksOffsetsStart = retrieveInt(&in);
m_variablesOffsetsStart = retrieveInt(&in);
m_variableDataStart = retrieveInt(&in);
m_magicMarker = retrieveInt(&in);
if (isCreator()) {
// valgrind complains otherwise
m_tasksStart = 0;
m_variablesStart = 0;
m_componentCount = 0;
m_tasksOffsetsStart = 0;
m_variablesOffsetsStart = 0;
m_variableDataStart = 0;
m_magicMarker = 0;
} else {
// fix code size
m_codeSize = m_tasksStart;
// merge stored variables
in.seek(m_variablesStart);
for (int i = 0; i != m_componentCount; ++i) {
QInstallerComponent *component = new QInstallerComponent(q);
component->d->m_vars = retrieveDictionary(&in);
qDebug() << "DICT " << i << component->d->m_vars;
m_components.append(component);
}
// read installer variables
Dictionary dict = retrieveDictionary(&in);
if (m_verbose)
qDebug() << "READ VARIABLES FROM INSTALLER:" << dict;
foreach (const QString &key, dict.keys()) {
if (!m_vars.contains(key))
m_vars.insert(key, dict.value(key));
}
if (m_verbose)
qDebug() << "MERGED VARIABLES:" << m_vars;
}
}
void QInstaller::Private::setInstallationProgress(qint64 progress)
{
// from 0 to m_totalProgress
int percent = progress * 100 / m_totalProgress;
if (percent == m_installationProgress)
return;
//qDebug() << "setting progress to " << progress
// << " of " << m_totalProgress << " " << percent << "%";
m_installationProgress = percent;
qApp->processEvents();
}
QString QInstaller::Private::installerBinaryPath() const
{
return qApp->arguments().at(0);
}
bool QInstaller::Private::isCreator() const
{
return !isInstaller() && !isUninstaller() && !isTempUninstaller();
}
bool QInstaller::Private::isInstaller() const
{
return m_magicMarker == magicInstallerMarker;
}
bool QInstaller::Private::isUninstaller() const
{
return m_magicMarker == magicUninstallerMarker;
}
bool QInstaller::Private::isTempUninstaller() const
{
return m_magicMarker == magicTempUninstallerMarker;
}
void QInstaller::Private::writeInstaller()
{
QString fileName = m_vars["OutputFile"];
#ifdef Q_OS_WIN
if (!fileName.endsWith(QLatin1String(".exe")))
fileName += QLatin1String(".exe");
#endif
QFile out;
out.setFileName(fileName);
openForWrite(out);
writeInstaller(&out);
out.setPermissions(out.permissions() | QFile::WriteUser
| QFile::ExeOther | QFile::ExeGroup | QFile::ExeUser);
}
void QInstaller::Private::writeInstaller(QIODevice *out)
{
appendCode(out);
QList<qint64> tasksOffsets;
QList<qint64> variablesOffsets;
// write component task data
foreach (QInstallerComponent *component, m_components) {
qint64 componentStart = out->size();
tasksOffsets.append(out->size()); // record start of tasks
// pack the component as a whole
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
foreach (QInstallerTask *task, component->d->m_tasks) {
appendInt(&buffer, q->indexOfTaskType(task->creator()));
task->writeToInstaller(&buffer);
}
buffer.close();
QByteArray compressed = qCompress(buffer.buffer());
int uncompressedSize = buffer.buffer().size();
int compressedSize = compressed.size();
appendByteArray(out, compressed);
qDebug() << "COMPRESS: " << uncompressedSize << compressedSize;
component->setValue("TaskCount", QString::number(component->d->m_tasks.size()));
component->setValue("ComponentStart", QString::number(componentStart));
component->setValue("CompressedSize", QString::number(compressedSize));
component->setValue("UncompressedSize", QString::number(uncompressedSize));
}
// write component variables
foreach (QInstallerComponent *component, m_components) {
variablesOffsets.append(out->size()); // record start of variables
appendDictionary(out, component->d->m_vars);
}
// append variables except temporary ones
qint64 variableDataStart = out->size();
Dictionary dict = m_vars;
dict.removeTemporaryKeys();
appendDictionary(out, dict);
// append recorded list of component task offsets
qint64 taskOffsetsStart = out->size();
foreach (qint64 offset, tasksOffsets)
appendInt(out, offset);
// append recorded list of component varaibles offsets
qint64 variablesOffsetsStart = out->size();
foreach (qint64 offset, variablesOffsets)
appendInt(out, offset);
// append trailer
appendInt(out, tasksOffsets[0]);
appendInt(out, variablesOffsets[0]);
appendInt(out, m_components.size());
appendInt(out, taskOffsetsStart);
appendInt(out, variablesOffsetsStart);
appendInt(out, variableDataStart);
appendInt(out, magicInstallerMarker);
}
bool QInstaller::Private::statusCanceledOrFailed() const
{
return m_status == QInstaller::InstallerCanceledByUser
|| m_status == QInstaller::InstallerFailed;
}
QInstallerTask *QInstaller::Private::createTaskFromCode(int code)
{
if (code >= 0 && code < m_taskCreators.size())
return m_taskCreators[code](q);
throw Error("NO TASK WITH CODE %1 REGISTERED");
}
void QInstaller::Private::undo(const QList<QInstallerTask *> &tasks)
{
//qDebug() << "REMOVING" << files.size();
// tasks.size() corresponds to m_installationProgress;
m_totalProgress = tasks.size() * m_installationProgress / 100 + 1;
for (int i = tasks.size(); --i >= 0; ) {
QInstallerTask *task = tasks.at(i);
setInstallationProgress(i);
task->undo();
}
setInstallationProgress(0);
}
void QInstaller::Private::appendCode(QIODevice *out)
{
QFile in(installerBinaryPath());
openForRead(in);
if (m_verbose)
qDebug() << "CODE SIZE: " << m_codeSize;
appendData(out, &in, m_codeSize);
in.close();
}
QString QInstaller::Private::replaceVariables(const QString &str) const
{
QString res;
int pos = 0;
while (true) {
int pos1 = str.indexOf('@', pos);
if (pos1 == -1)
break;
int pos2 = str.indexOf('@', pos1 + 1);
if (pos2 == -1)
break;
res += str.mid(pos, pos1 - pos);
QString name = str.mid(pos1 + 1, pos2 - pos1 - 1);
res += m_vars.value(name);
pos = pos2 + 1;
}
res += str.mid(pos);
return res;
}
QByteArray QInstaller::Private::replaceVariables(const QByteArray &ba) const
{
QByteArray res;
int pos = 0;
while (true) {
int pos1 = ba.indexOf('@', pos);
if (pos1 == -1)
break;
int pos2 = ba.indexOf('@', pos1 + 1);
if (pos2 == -1)
break;
res += ba.mid(pos, pos1 - pos);
QString name = QString::fromLocal8Bit(ba.mid(pos1 + 1, pos2 - pos1 - 1));
res += m_vars.value(name).toLocal8Bit();
pos = pos2 + 1;
}
res += ba.mid(pos);
return res;
}
QString QInstaller::Private::uninstallerName() const
{
QString name = m_vars["TargetDir"];
name += "/uninstall";
#ifdef Q_OS_WIN
name += QLatin1String(".exe");
#endif
return name;
}
void QInstaller::Private::writeUninstaller(const QList<QInstallerTask *> &tasks)
{
QFile out(uninstallerName());
try {
ifVerbose("CREATING UNINSTALLER " << tasks.size());
// Create uninstaller. this is basically a clone of ourselves
// with a few changed variables
openForWrite(out);
appendCode(&out);
qint64 tasksStart = out.size();
appendInt(&out, tasks.size());
for (int i = tasks.size(); --i >= 0; ) {
QInstallerTask *task = tasks.at(i);
appendInt(&out, m_taskCreators.indexOf(task->creator()));
task->writeToUninstaller(&out); // might throw
}
// append variables except temporary ones
qint64 variableDataStart = out.size();
Dictionary dict = m_vars;
dict.removeTemporaryKeys();
dict.setValue(QLatin1String("UninstallerPath"), uninstallerName());
appendDictionary(&out, dict);
// append trailer
appendInt(&out, tasksStart);
appendInt(&out, variableDataStart); // variablesStart
appendInt(&out, 0); // componentCount
appendInt(&out, 0); // tasksOffsetsStart
appendInt(&out, 0); // variablesOffsetsStart
appendInt(&out, variableDataStart);
appendInt(&out, magicUninstallerMarker);
out.setPermissions(out.permissions() | QFile::WriteUser
| QFile::ExeOther | QFile::ExeGroup | QFile::ExeUser);
}
catch (const QInstallerError &err) {
m_status = QInstaller::InstallerFailed;
// local roll back
qDebug() << "WRITING TO UNINSTALLER FAILED: " << err.message();
out.close();
out.remove();
throw;
}
}
QString QInstaller::Private::registerPath() const
{
QString productName = m_vars["ProductName"];
if (productName.isEmpty())
throw QInstallerError("ProductName should be set");
QString path;
if (m_vars["AllUsers"] == "true")
path += "HKEY_LOCAL_MACHINE";
else
path += "HKEY_CURRENT_USER";
path += "\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
path += productName;
return path;
}
void QInstaller::Private::registerInstaller()
{
#ifdef Q_OS_WIN
QSettings settings(registerPath(), QSettings::NativeFormat);
settings.beginGroup("CurrentVersion");
settings.beginGroup("Uninstall");
settings.beginGroup(m_vars["ProductName"]);
settings.setValue("Comments", m_vars["Comments"]);
settings.setValue("Contact", m_vars["Contact"]);
settings.setValue("DisplayName", m_vars["ProductName"]);
settings.setValue("DisplayVersion", m_vars["DisplayVersion"]);
settings.setValue("EstimatedSize", "X4957efb0");
settings.setValue("HelpLink", m_vars["HelpLink"]);
settings.setValue("InstallDate", QDateTime::currentDateTime().toString());
settings.setValue("InstallLocation", m_vars["TargetDir"]);
settings.setValue("NoModify", "1");
settings.setValue("NoRepair", "1");
settings.setValue("Publisher", m_vars["Publisher"]);
settings.setValue("UninstallString", uninstallerName());
settings.setValue("UrlInfoAbout", m_vars["UrlInfoAbout"]);
#endif
}
void QInstaller::Private::unregisterInstaller()
{
#ifdef Q_OS_WIN
QSettings settings(registerPath(), QSettings::NativeFormat);
settings.remove(QString());
#endif
}
void QInstaller::Private::runInstaller()
{
QList<QInstallerTask *> tasks;
try {
emit installationStarted();
if (!m_vars.contains("TargetDir"))
throw QInstallerError(QLatin1String("Variable 'TargetDir' not set."));
QFile in(installerBinaryPath());
openForRead(in);
m_totalProgress = 0;
QList<QInstallerComponent *> componentsToInstall;
for (int i = 0; i != m_componentCount; ++i) {
QInstallerComponent *comp = m_components.at(i);
QString wantedState = comp->value("WantedState");
ifVerbose("HANDLING COMPONENT" << i << "WANTED: " << wantedState);
if (wantedState == "Uninstalled") {
qDebug() << "SKIPPING COMPONENT" << comp->value("DisplayName");
continue;
}
componentsToInstall.append(comp);
m_totalProgress += comp->value("UncompressedSize").toInt();
}
qDebug() << "Install size: " << m_totalProgress
<< "in " << componentsToInstall.size() << "components";
qint64 lastProgressBase = 0;
foreach (QInstallerComponent *comp, componentsToInstall) {
int taskCount = comp->value("TaskCount").toInt();
quint64 componentStart = comp->value("ComponentStart").toInt();
in.seek(componentStart);
if (statusCanceledOrFailed())
throw Error("Installation canceled by user");
m_installationProgressText =
tr("Decompressing component %1").arg(comp->value("DisplayName"));
qApp->processEvents();
QByteArray compressed = retrieveByteArray(&in);
QByteArray uncompressed = qUncompress(compressed);
if (uncompressed.isEmpty()) {
qDebug() << "SIZE: " << compressed.size() << " TASK COUNT: " << taskCount
<< uncompressed.size();
throw Error("DECOMPRESSION FAILED");
}
QBuffer buffer(&uncompressed);
buffer.open(QIODevice::ReadOnly);
for (int j = 0; j != taskCount; ++j) {
int code = retrieveInt(&buffer);
QInstallerTask *task = createTaskFromCode(code); // might throw
task->readAndExecuteFromInstaller(&buffer); // might throw
tasks.append(task);
if (statusCanceledOrFailed())
throw Error("Installation canceled by user");
setInstallationProgress(lastProgressBase + buffer.pos());
}
comp->setValue("CurrentState", "Installed");
lastProgressBase += uncompressed.size();
}
in.close();
registerInstaller();
writeUninstaller(tasks);
m_status = InstallerSucceeded;
m_installationProgressText = tr("Installation finished!");
qApp->processEvents();
emit installationFinished();
}
catch (const QInstallerError &err) {
installer()->showWarning(err.message());
qDebug() << "INSTALLER FAILED: " << err.message() << "\nROLLING BACK";
undo(tasks);
m_installationProgressText = tr("Installation aborted");
qApp->processEvents();
emit installationFinished();
throw;
}
}
bool QInstaller::Private::restartTempUninstaller(const QStringList &args)
{
#ifdef Q_OS_WIN
ifVerbose("Running uninstaller on Windows.");
if (isUninstaller()) {
QString uninstallerFile = installerBinaryPath();
QDir tmpDir = QDir::temp();
QString tmpDirName = QLatin1String("qtcreator_uninst");
QString tmpAppName = QLatin1String("uninst.exe");
if (!tmpDir.exists(tmpDirName)) {
tmpDir.mkdir(tmpDirName);
if (!tmpDir.exists(tmpDirName)) {
ifVerbose("Could not create temporary folder!");
return false;
}
tmpDir.cd(tmpDirName);
}
if (tmpDir.exists(tmpAppName) && !tmpDir.remove(tmpAppName)) {
ifVerbose("Could not remove old temporary uninstaller!");
return false;
}
QString tmpUninstaller = tmpDir.absoluteFilePath(tmpAppName);
QFile in(uninstallerFile);
if (!in.open(QIODevice::ReadOnly)) {
ifVerbose("Cannot open uninstall.exe!");
return false;
}
QFile out(tmpUninstaller);
if (!out.open(QIODevice::WriteOnly)) {
ifVerbose("Cannot open temporary uninstall.exe!");
return false;
}
QByteArray ba = in.readAll();
QBuffer buf(&ba);
buf.open(QIODevice::ReadWrite);
buf.seek(buf.size() - sizeof(qint64));
appendInt(&buf, magicTempUninstallerMarker);
if (in.size() != out.write(buf.data())) {
ifVerbose("Could not copy uninstaller!");
return false;
}
in.close();
out.close();
MoveFileExW((TCHAR*)tmpUninstaller.utf16(),
0, MOVEFILE_DELAY_UNTIL_REBOOT|MOVEFILE_REPLACE_EXISTING);
STARTUPINFOW sInfo;
PROCESS_INFORMATION pInfo;
memset(&sInfo, 0, sizeof(sInfo));
memset(&pInfo, 0, sizeof(pInfo));
sInfo.cb = sizeof(sInfo);
QString cmd = QString("\"%1\"").arg(tmpUninstaller);
foreach (const QString &s, args)
cmd.append(QLatin1String(" \"") + s + QLatin1String("\""));
if (CreateProcessW(0, (TCHAR*)cmd.utf16(), 0, 0, false, 0, 0, 0, &sInfo, &pInfo)) {
CloseHandle(pInfo.hThread);
CloseHandle(pInfo.hProcess);
ifVerbose("Started temp uninstaller.");
} else {
ifVerbose("Cannot launch uninstaller!");
return false;
}
}
#else
Q_UNUSED(args);
#endif
return true;
}
void QInstaller::Private::runUninstaller()
{
QFile uninstaller(installerBinaryPath());
openForRead(uninstaller);
QByteArray ba = uninstaller.readAll();
uninstaller.close();
emit uninstallationStarted();
#ifndef Q_OS_WIN
// remove uninstaller binary itself. Also necessary for successful
// removal of the application directory.
uninstaller.remove();
#else
if (m_vars.contains(QLatin1String("UninstallerPath"))) {
QFile orgUninstaller(m_vars.value(QLatin1String("UninstallerPath")));
orgUninstaller.remove();
}
#endif
// read file
QBuffer in(&ba);
in.open(QIODevice::ReadOnly);
in.seek(m_tasksStart);
qint64 taskCount = retrieveInt(&in);
ifVerbose("FOUND " << taskCount << "UNINSTALLER TASKS");
m_totalProgress = m_variablesStart;
for (int i = 0; i != taskCount; ++i) {
int code = retrieveInt(&in);
QInstallerTask *task = createTaskFromCode(code);
task->readAndExecuteFromUninstaller(&in);
setInstallationProgress(in.pos());
}
in.close();
unregisterInstaller();
m_installationProgressText = tr("Deinstallation finished");
qApp->processEvents();
emit uninstallationFinished();
}
////////////////////////////////////////////////////////////////////
//
// QInstaller
//
////////////////////////////////////////////////////////////////////
QInstaller::QInstaller()
{
d = new Private(this);
d->initialize();
registerTaskType(createCopyFileTask);
registerTaskType(createLinkFileTask);
registerTaskType(createPatchFileTask);
registerTaskType(createWriteSettingsTask);
registerTaskType(createMenuShortcutTask);
}
QInstaller::~QInstaller()
{
delete d;
}
void QInstaller::appendComponent(QInstallerComponent *component)
{
d->m_components.append(component);
}
int QInstaller::componentCount() const
{
return d->m_components.size();
}
QInstallerComponent *QInstaller::component(int i) const
{
return d->m_components.at(i);
}
void QInstaller::registerTaskType(TaskCreator tc)
{
d->m_taskCreators.append(tc);
}
int QInstaller::indexOfTaskType(TaskCreator tc) const
{
return d->m_taskCreators.indexOf(tc);
}
QString QInstaller::value(const QString &key, const QString &defaultValue) const
{
return d->m_vars.value(key, defaultValue);
}
void QInstaller::setValue(const QString &key, const QString &value)
{
d->m_vars[key] = value;
}
bool QInstaller::containsValue(const QString &key) const
{
return d->m_vars.contains(key);
}
bool QInstaller::isVerbose() const
{
return d->m_verbose;
}
void QInstaller::setVerbose(bool on)
{
d->m_verbose = on;
}
QInstaller::InstallerStatus QInstaller::status() const
{
return d->m_status;
}
void QInstaller::interrupt()
{
qDebug() << "INTERRUPT INSTALLER";
d->m_status = InstallerCanceledByUser;
}
QString QInstaller::replaceVariables(const QString &str) const
{
return d->replaceVariables(str);
}
QByteArray QInstaller::replaceVariables(const QByteArray &ba) const
{
return d->replaceVariables(ba);
}
int QInstaller::installationProgress() const
{
return d->m_installationProgress;
}
void QInstaller::setInstallationProgressText(const QString &value)
{
d->m_installationProgressText = value;
}
QString QInstaller::installationProgressText() const
{
return d->m_installationProgressText;
}
QString QInstaller::installerBinaryPath() const
{
return d->installerBinaryPath();
}
bool QInstaller::isCreator() const
{
return d->isCreator();
}
bool QInstaller::isInstaller() const
{
return d->isInstaller();
}
bool QInstaller::isUninstaller() const
{
return d->isUninstaller();
}
bool QInstaller::isTempUninstaller() const
{
return d->isTempUninstaller();
}
bool QInstaller::runInstaller()
{
try { d->runInstaller(); return true; } catch (...) { return false; }
}
bool QInstaller::runUninstaller()
{
try { d->runUninstaller(); return true; } catch (...) { return false; }
}
void QInstaller::showWarning(const QString &str)
{
emit warning(str);
}
void QInstaller::dump() const
{
qDebug() << "COMMAND LINE VARIABLES:" << d->m_vars;
}
void QInstaller::appendInt(QIODevice *out, qint64 n)
{
QT_PREPEND_NAMESPACE(appendInt)(out, n);
}
void QInstaller::appendString(QIODevice *out, const QString &str)
{
QT_PREPEND_NAMESPACE(appendString)(out, str);
}
void QInstaller::appendByteArray(QIODevice *out, const QByteArray &str)
{
QT_PREPEND_NAMESPACE(appendByteArray)(out, str);
}
qint64 QInstaller::retrieveInt(QIODevice *in)
{
return QT_PREPEND_NAMESPACE(retrieveInt)(in);
}
QString QInstaller::retrieveString(QIODevice *in)
{
return QT_PREPEND_NAMESPACE(retrieveString)(in);
}
QByteArray QInstaller::retrieveByteArray(QIODevice *in)
{
return QT_PREPEND_NAMESPACE(retrieveByteArray)(in);
}
bool QInstaller::run()
{
try {
if (isCreator()) {
createTasks(); // implemented in derived classes
d->writeInstaller();
} else if (isInstaller()) {
d->runInstaller();
} else if (isUninstaller() || isTempUninstaller()) {
runUninstaller();
}
return true;
} catch (const QInstallerError &err) {
qDebug() << "Caught Installer Error: " << err.message();
return false;
}
}
QString QInstaller::uninstallerName() const
{
return d->uninstallerName();
}
QString QInstaller::libraryName(const QString &baseName, const QString &version)
{
#ifdef Q_OS_WIN
return baseName + QLatin1String(".dll");
#elif Q_OS_MAC
return QString("lib%1.dylib").arg(baseName);
#else
return QString("lib%1.so.%2").arg(baseName).arg(version);
#endif
}
bool QInstaller::restartTempUninstaller(const QStringList &args)
{
return d->restartTempUninstaller(args);
}
////////////////////////////////////////////////////////////////////
//
// QInstallerTask
//
////////////////////////////////////////////////////////////////////
QInstallerTask::QInstallerTask(QInstaller *parent)
: m_installer(parent)
{}
QInstaller *QInstallerTask::installer() const
{
return m_installer;
}
////////////////////////////////////////////////////////////////////
//
// QInstallerCopyFileTask
//
////////////////////////////////////////////////////////////////////
QInstaller::TaskCreator QInstallerCopyFileTask::creator() const
{
return &createCopyFileTask;
}
void QInstallerCopyFileTask::writeToInstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
appendInt(out, m_permissions);
QFile file(m_sourcePath);
openForRead(file);
appendFileData(out, &file);
}
static int createParentDirs(const QString &absFileName)
{
QFileInfo fi(absFileName);
if (fi.isDir())
return 0;
QString dirName = fi.path();
int n = createParentDirs(dirName);
QDir dir(dirName);
dir.mkdir(fi.fileName());
return n + 1;
}
void QInstallerCopyFileTask::readAndExecuteFromInstaller(QIODevice *in)
{
m_targetPath = installer()->replaceVariables(retrieveString(in));
m_permissions = retrieveInt(in);
ifVerbose("EXECUTE COPY FILE, TARGET " << m_targetPath);
QString path = QDir::cleanPath(QFileInfo(m_targetPath).absolutePath());
m_parentDirCount = createParentDirs(path);
QString msg = QInstaller::tr("Copying file %1").arg(m_targetPath);
installer()->setInstallationProgressText(msg);
QFile file(m_targetPath);
bool res = file.open(QIODevice::WriteOnly);
if (!res) {
// try to make it writeable, and try again
bool res1 = file.setPermissions(file.permissions()|QFile::WriteOwner);
ifVerbose("MAKE WRITABLE: " << res1);
res = file.open(QIODevice::WriteOnly);
}
if (!res) {
// try to remove it, and try again
bool res1 = file.remove();
ifVerbose("REMOVING FILE: " << res1);
res = file.open(QIODevice::WriteOnly);
}
if (!res) {
QString msg = QInstaller::tr("The file %1 is not writeable.")
.arg(m_targetPath);
installer()->showWarning(msg);
throw Error(msg);
}
retrieveFileData(&file, in);
QFile::Permissions perms(m_permissions | QFile::WriteOwner);
file.close();
file.setPermissions(perms);
}
void QInstallerCopyFileTask::writeToUninstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
appendInt(out, m_parentDirCount);
}
void QInstallerCopyFileTask::readAndExecuteFromUninstaller(QIODevice *in)
{
m_targetPath = retrieveString(in);
m_parentDirCount = retrieveInt(in);
undo();
}
void QInstallerCopyFileTask::undo()
{
ifVerbose("UNLINKING FILE" << m_targetPath << m_parentDirCount);
QString msg = QInstaller::tr("Removing %1").arg(m_targetPath);
installer()->setInstallationProgressText(msg);
QFileInfo fi(m_targetPath);
QDir dir(fi.path());
QFile file(m_targetPath);
bool res = file.remove();
if (!res) {
// try to make it writeable, and try again
bool res1 = file.setPermissions(file.permissions()|QFile::WriteOwner);
ifVerbose("MAKE WRITABLE: " << res1);
res = file.remove();
}
while (res && --m_parentDirCount >= 0) {
QString dirName = dir.dirName();
dir.cdUp();
res = dir.rmdir(dirName);
msg = QInstaller::tr("Removing file %1").arg(m_targetPath);
installer()->setInstallationProgressText(msg);
ifVerbose("REMOVING DIR " << dir.path() << dirName << res);
}
}
void QInstallerCopyFileTask::dump(QDebug & os) const
{
os << "c|" + sourcePath() + '|' + targetPath();
}
////////////////////////////////////////////////////////////////////
//
// QInstallerLinkFileTask
//
////////////////////////////////////////////////////////////////////
QInstaller::TaskCreator QInstallerLinkFileTask::creator() const
{
return &createLinkFileTask;
}
void QInstallerLinkFileTask::writeToInstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
appendString(out, m_linkTargetPath);
appendInt(out, m_permissions);
}
void QInstallerLinkFileTask::readAndExecuteFromInstaller(QIODevice *in)
{
m_targetPath = installer()->replaceVariables(retrieveString(in));
m_linkTargetPath = installer()->replaceVariables(retrieveString(in));
m_permissions = retrieveInt(in);
ifVerbose("LINK " << m_targetPath << " TARGET " << m_linkTargetPath);
QFile file(m_linkTargetPath);
if (file.link(m_targetPath))
return;
// ok. linking failed. try to remove targetPath and link again
bool res1 = QFile::remove(m_targetPath);
ifVerbose("TARGET EXITS, REMOVE: " << m_targetPath << res1);
if (file.link(m_targetPath))
return;
// nothing helped.
throw Error(QInstaller::tr("Cannot link file %1 to %2:\n")
.arg(m_linkTargetPath).arg(m_targetPath));
}
void QInstallerLinkFileTask::writeToUninstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
}
void QInstallerLinkFileTask::readAndExecuteFromUninstaller(QIODevice *in)
{
m_targetPath = retrieveString(in);
ifVerbose("UNLINKING LINK" << m_targetPath);
undo();
}
void QInstallerLinkFileTask::undo()
{
QFile file(m_targetPath);
file.remove();
}
void QInstallerLinkFileTask::dump(QDebug & os) const
{
os << "l|" + targetPath() + '|' + linkTargetPath();
}
////////////////////////////////////////////////////////////////////
//
// QInstallerWriteSettingsTask
//
////////////////////////////////////////////////////////////////////
QInstaller::TaskCreator QInstallerWriteSettingsTask::creator() const
{
return &createWriteSettingsTask;
}
void QInstallerWriteSettingsTask::writeToInstaller(QIODevice *out) const
{
appendString(out, m_key);
appendString(out, m_value);
}
void QInstallerWriteSettingsTask::readAndExecuteFromInstaller(QIODevice *in)
{
m_key = installer()->replaceVariables(retrieveString(in));
m_value = installer()->replaceVariables(retrieveString(in));
QSettings settings;
settings.setValue(m_key, m_value);
}
void QInstallerWriteSettingsTask::writeToUninstaller(QIODevice *out) const
{
appendString(out, m_key);
appendString(out, m_value);
}
void QInstallerWriteSettingsTask::readAndExecuteFromUninstaller(QIODevice *in)
{
m_key = installer()->replaceVariables(retrieveString(in));
m_value = installer()->replaceVariables(retrieveString(in));
undo();
}
void QInstallerWriteSettingsTask::undo()
{
QSettings settings;
settings.setValue(m_key, QString());
}
void QInstallerWriteSettingsTask::dump(QDebug & os) const
{
os << "s|" + key() + '|' + value();
}
////////////////////////////////////////////////////////////////////
//
// QInstallerPatchFileTask
//
////////////////////////////////////////////////////////////////////
QInstaller::TaskCreator QInstallerPatchFileTask::creator() const
{
return &createPatchFileTask;
}
void QInstallerPatchFileTask::writeToInstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
appendByteArray(out, m_needle);
appendByteArray(out, m_replacement);
}
void QInstallerPatchFileTask::readAndExecuteFromInstaller(QIODevice *in)
{
m_targetPath = installer()->replaceVariables(retrieveString(in));
m_needle = retrieveByteArray(in);
m_replacement = installer()->replaceVariables(retrieveByteArray(in));
ifVerbose("PATCHING" << m_replacement << m_needle << m_targetPath);
QFile file;
file.setFileName(m_targetPath);
if (!file.open(QIODevice::ReadWrite))
throw Error(QInstaller::tr("Cannot open file %1 for reading").arg(file.fileName()));
uchar *data = file.map(0, file.size());
if (!data)
throw Error(QInstaller::tr("Cannot map file %1").arg(file.fileName()));
QByteArray ba = QByteArray::fromRawData((const char *)data, file.size());
int pos = ba.indexOf(m_needle);
if (pos != -1) {
for (int i = m_replacement.size(); --i >= 0; )
data[pos + i] = m_replacement.at(i);
}
if (!file.unmap(data))
throw Error(QInstaller::tr("Cannot unmap file %1").arg(file.fileName()));
file.close();
}
void QInstallerPatchFileTask::dump(QDebug & os) const
{
os << "p|" + targetPath() + '|' + needle() + '|' + replacement();
}
////////////////////////////////////////////////////////////////////
//
// QInstallerMenuShortcutTask
//
//
// Usage:
//
// static const struct
// {
// const char *target;
// const char *linkTarget;
// } menuShortcuts[] = {
// {"Qt Creator by Nokia\\Run Qt Creator", "bin\\qtcreator.exe"},
// {"Qt Creator by Nokia\\Readme", "readme.txt"},
// {"Qt Creator by Nokia\\Uninstall", "uninstall.exe"}
// };
//
// for (int i = 0; i != sizeof(menuShortcuts) / sizeof(menuShortcuts[0]); ++i) {
// QInstallerMenuShortcutTask *task = new QInstallerMenuShortcutTask(this);
// task->setTargetPath(menuShortcuts[i].target);
// task->setLinkTargetPath(QLatin1String("@TargetDir@\\") + menuShortcuts[i].linkTarget);
// }
//
////////////////////////////////////////////////////////////////////
QInstaller::TaskCreator QInstallerMenuShortcutTask::creator() const
{
return &createMenuShortcutTask;
}
void QInstallerMenuShortcutTask::writeToInstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
appendString(out, m_linkTargetPath);
}
void QInstallerMenuShortcutTask::readAndExecuteFromInstaller(QIODevice *in)
{
m_targetPath = installer()->replaceVariables(retrieveString(in));
m_linkTargetPath = installer()->replaceVariables(retrieveString(in));
#ifdef Q_OS_WIN
QString workingDir = installer()->value(QLatin1String("TargetDir"));
bool res = false;
HRESULT hres;
IShellLink *psl;
bool neededCoInit = false;
ifVerbose("CREATE MENU SHORTCUT: " << m_targetPath << " TARGET " << m_linkTargetPath);
if (installer()->value(QLatin1String("AllUsers")) == "true") {
QSettings registry(QLatin1String("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows"
"\\CurrentVersion\\Explorer\\Shell Folders"), QSettings::NativeFormat);
m_startMenuPath = registry.value(QLatin1String("Common Programs"), QString()).toString();
} else {
QSettings registry(QLatin1String("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows"
"\\CurrentVersion\\Explorer\\Shell Folders"), QSettings::NativeFormat);
m_startMenuPath = registry.value(QLatin1String("Programs"), QString()).toString();
}
if (m_startMenuPath.isEmpty()) {
ifVerbose("CREATE MENU SHORTCUT: Cannot find start menu folder!");
return;
}
if (!m_targetPath.isEmpty()) {
if (!m_targetPath.endsWith(QLatin1String(".lnk")))
m_targetPath.append(QLatin1String(".lnk"));
m_targetPath = m_targetPath.replace('/', '\\');
int i = m_targetPath.lastIndexOf('\\');
if (i > -1) {
QDir dir(m_startMenuPath);
if (!dir.exists(m_targetPath.left(i)))
dir.mkpath(m_targetPath.left(i));
}
if (m_linkTargetPath.isEmpty())
return;
QString trgt = m_linkTargetPath;
if (trgt.startsWith('\"')) {
trgt = trgt.left(trgt.indexOf('\"', 1) + 1);
} else {
trgt = trgt.left(trgt.indexOf(' '));
}
if (trgt.isEmpty())
trgt = m_linkTargetPath;
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl);
if (hres == CO_E_NOTINITIALIZED) {
neededCoInit = true;
CoInitialize(NULL);
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
(void **)&psl);
}
if (SUCCEEDED(hres)) {
hres = psl->SetPath((wchar_t *)trgt.utf16());
if (SUCCEEDED(hres)) {
hres = psl->SetArguments((wchar_t *)m_linkTargetPath.mid(trgt.length()).utf16());
if (SUCCEEDED(hres)) {
hres = psl->SetWorkingDirectory((wchar_t *)workingDir.utf16());
if (SUCCEEDED(hres)) {
IPersistFile *ppf;
hres = psl->QueryInterface(IID_IPersistFile, (void **)&ppf);
if (SUCCEEDED(hres)) {
hres = ppf->Save((TCHAR*)QString(m_startMenuPath
+ QDir::separator() + m_targetPath).utf16(), TRUE);
if (SUCCEEDED(hres))
res = true;
ppf->Release();
}
}
}
}
psl->Release();
}
if (neededCoInit)
CoUninitialize();
}
if (!res) {
QString msg = QInstaller::tr("Cannot create menu shortcut %1 to %2:\n")
.arg(m_linkTargetPath).arg(m_targetPath);
installer()->showWarning(msg);
return;
}
#endif
}
void QInstallerMenuShortcutTask::writeToUninstaller(QIODevice *out) const
{
appendString(out, m_targetPath);
appendString(out, m_startMenuPath);
}
void QInstallerMenuShortcutTask::readAndExecuteFromUninstaller(QIODevice *in)
{
m_targetPath = retrieveString(in);
m_startMenuPath = retrieveString(in);
ifVerbose("REMOVE MENU SHORTCUT: " << m_targetPath);
undo();
}
void QInstallerMenuShortcutTask::undo()
{
#ifdef Q_OS_WIN
QFileInfo fi(m_startMenuPath + QDir::separator() + m_targetPath);
QString path = fi.absoluteFilePath();
if (fi.isFile()) {
path = fi.absolutePath();
QFile file(fi.absoluteFilePath());
file.remove();
}
QDir dir(m_startMenuPath);
dir.rmpath(path);
#endif
}
void QInstallerMenuShortcutTask::dump(QDebug & os) const
{
os << "msc|" + targetPath() + '|' + linkTargetPath();
}
QT_END_NAMESPACE
#include "qinstaller.moc"