/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.
This file is part of Mod Organizer.
Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see .
*/
#include "installationmanager.h"
#include "utility.h"
#include "report.h"
#include "categories.h"
#include "questionboxmemory.h"
#include "settings.h"
#include "queryoverwritedialog.h"
#include "messagedialog.h"
#include "iplugininstallersimple.h"
#include "iplugininstallercustom.h"
#include "nexusinterface.h"
#include "selectiondialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace MOBase;
using namespace MOShared;
typedef Archive* (*CreateArchiveType)();
template T resolveFunction(QLibrary &lib, const char *name)
{
T temp = reinterpret_cast(lib.resolve(name));
if (temp == NULL) {
throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData());
}
return temp;
}
InstallationManager::InstallationManager(QWidget *parent)
: QObject(parent), m_ParentWidget(parent),
m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001"))
{
QLibrary archiveLib("dlls\\archive.dll");
if (!archiveLib.load()) {
throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
}
CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive");
m_CurrentArchive = CreateArchiveFunc();
if (!m_CurrentArchive->isValid()) {
throw MyException(getErrorString(m_CurrentArchive->getLastError()));
}
m_InstallationProgress.setWindowFlags(m_InstallationProgress.windowFlags() & (~Qt::WindowContextHelpButtonHint));
}
InstallationManager::~InstallationManager()
{
delete m_CurrentArchive;
}
void InstallationManager::queryPassword(LPSTR password)
{
QString result = QInputDialog::getText(NULL, tr("Password required"), tr("Password"), QLineEdit::Password);
strncpy(password, result.toLocal8Bit().constData(), MAX_PASSWORD_LENGTH);
}
void InstallationManager::mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data)
{
if (path.length() > 0) {
path.append(L"\\");
}
for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) {
data[iter->getIndex()]->setSkip(false);
std::wstring temp = path.substr().append(ToWString(iter->getName()));
data[iter->getIndex()]->setOutputFileName(temp.c_str());
}
for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
if ((*iter)->getData().index != -1) {
data[(*iter)->getData().index]->setSkip(false);
std::wstring temp = path.substr().append(ToWString((*iter)->getData().name));
data[(*iter)->getData().index]->setOutputFileName(temp.c_str());
}
mapToArchive(*iter, path.substr().append(ToWString((*iter)->getData().name)), data);
}
}
void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode)
{
FileData* const *data;
size_t size;
m_CurrentArchive->getFileList(data, size);
// first disable all files + folders, we will re-enable those present in baseNode
for (size_t i = 0; i < size; ++i) {
data[i]->setSkip(true);
}
std::wstring currentPath;
mapToArchive(baseNode, currentPath, data);
}
bool InstallationManager::unpackSingleFile(const QString &fileName)
{
FileData* const *data;
size_t size;
m_CurrentArchive->getFileList(data, size);
QString baseName = QFileInfo(fileName).fileName();
bool available = false;
for (size_t i = 0; i < size; ++i) {
if (_wcsicmp(data[i]->getFileName(), ToWString(fileName).c_str()) == 0) {
available = true;
data[i]->setSkip(false);
data[i]->setOutputFileName(ToWString(baseName).c_str());
m_TempFilesToDelete.insert(baseName);
} else {
data[i]->setSkip(true);
}
}
if (available) {
m_InstallationProgress.setWindowTitle(tr("Extracting files"));
m_InstallationProgress.setLabelText(QString());
m_InstallationProgress.setValue(0);
m_InstallationProgress.setWindowModality(Qt::WindowModal);
m_InstallationProgress.show();
bool res = m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(),
new MethodCallback(this, &InstallationManager::updateProgress),
new MethodCallback(this, &InstallationManager::dummyProgressFile),
new MethodCallback(this, &InstallationManager::report7ZipError));
m_InstallationProgress.hide();
return res;
} else {
return false;
}
}
QString InstallationManager::extractFile(const QString &fileName)
{
if (unpackSingleFile(fileName)) {
return QDir::tempPath().append("/").append(QFileInfo(fileName).fileName());
} else {
return QString();
}
}
QString canonicalize(const QString &name)
{
QString result(name);
if ((result.startsWith('/')) ||
(result.startsWith('\\'))) {
result.remove(0, 1);
}
result.replace('/', '\\');
return result;
}
QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool flatten)
{
QStringList files;
foreach (const QString &file, filesOrig) {
files.append(canonicalize(file));
}
QStringList result;
FileData* const *data;
size_t size;
m_CurrentArchive->getFileList(data, size);
for (size_t i = 0; i < size; ++i) {
if (files.contains(ToQString(data[i]->getFileName()), Qt::CaseInsensitive)) {
const wchar_t *targetFile = data[i]->getFileName();
if (flatten) {
targetFile = wcsrchr(data[i]->getFileName(), '\\');
if (targetFile == NULL) {
targetFile = wcsrchr(data[i]->getFileName(), '/');
}
if (targetFile == NULL) {
qCritical("failed to find backslash in %ls", data[i]->getFileName());
continue;
} else {
// skip the slash
++targetFile;
}
}
data[i]->setOutputFileName(targetFile);
result.append(QDir::tempPath().append("/").append(ToQString(targetFile)));
data[i]->setSkip(false);
m_TempFilesToDelete.insert(ToQString(targetFile));
} else {
data[i]->setSkip(true);
}
}
m_InstallationProgress.setWindowTitle(tr("Extracting files"));
m_InstallationProgress.setLabelText(QString());
m_InstallationProgress.setValue(0);
m_InstallationProgress.setWindowModality(Qt::WindowModal);
m_InstallationProgress.show();
// unpack only the files we need for the installer
if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(),
new MethodCallback(this, &InstallationManager::updateProgress),
new MethodCallback(this, &InstallationManager::dummyProgressFile),
new MethodCallback(this, &InstallationManager::report7ZipError))) {
m_InstallationProgress.hide();
throw std::runtime_error("extracting failed");
}
m_InstallationProgress.hide();
return result;
}
IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue &modName, const QString &archiveName)
{
// in earlier versions the modName was copied here and the copy passed to install. I don't know why I did this and it causes
// a problem if this is called by the bundle installer and the bundled installer adds additional names that then end up being used,
// because the caller will then not have the right name.
bool iniTweaks;
if (install(archiveName, modName, iniTweaks)) {
return IPluginInstaller::RESULT_SUCCESS;
} else {
return IPluginInstaller::RESULT_FAILED;
}
}
DirectoryTree *InstallationManager::createFilesTree()
{
FileData* const *data;
size_t size;
m_CurrentArchive->getFileList(data, size);
QScopedPointer result(new DirectoryTree);
for (size_t i = 0; i < size; ++i) {
// the files are in a flat list where each file has a a full path relative to the archive root
// to create a tree, we have to iterate over each path component of each. This could be sped up by
// grouping the filenames first, but so far there doesn't seem to be an actual performance problem
DirectoryTree::Node *currentNode = result.data();
QString fileName = ToQString(data[i]->getFileName());
QStringList components = fileName.split("\\");
// iterate over all path-components of this filename (including the filename itself)
for (QStringList::iterator componentIter = components.begin(); componentIter != components.end(); ++componentIter) {
if (componentIter->size() == 0) {
// empty string indicates fileName is actually only a directory name and we have
// completely processed it already.
break;
}
bool exists = false;
// test if this path is already in the tree
for (DirectoryTree::node_iterator nodeIter = currentNode->nodesBegin(); nodeIter != currentNode->nodesEnd(); ++nodeIter) {
if ((*nodeIter)->getData().name == *componentIter) {
currentNode = *nodeIter;
exists = true;
break;
}
}
if (!exists) {
if (componentIter + 1 == components.end()) {
// last path component. directory or file?
if (data[i]->isDirectory()) {
// this is a bit problematic. archives will often only list directories if they are empty,
// otherwise the dir only appears in the path of a file. In the UI however we allow the user
// to uncheck all files in a directory while keeping the dir checked. Those directories are
// currently not installed.
DirectoryTree::Node *newNode = new DirectoryTree::Node;
newNode->setData(DirectoryTreeInformation(*componentIter, i));
currentNode->addNode(newNode, false);
currentNode = newNode;
} else {
currentNode->addLeaf(FileTreeInformation(*componentIter, i));
}
} else {
DirectoryTree::Node *newNode = new DirectoryTree::Node;
newNode->setData(DirectoryTreeInformation(*componentIter, -1));
currentNode->addNode(newNode, false);
currentNode = newNode;
}
}
}
}
return result.take();
}
bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle)
{
// see if there is at least one directory that makes sense on the top level
for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) ||
(!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) {
qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData());
return true;
}
}
// see if there is a file that makes sense on the top level
for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) {
if (InstallationTester::isTopLevelSuffix(iter->getName())) {
return true;
}
}
return false;
}
DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *dataTree)
{
DirectoryTree::Node *currentNode = dataTree;
while (true) {
if (isSimpleArchiveTopLayer(currentNode, false)) {
return currentNode;
} else if ((currentNode->numLeafs() == 0) &&
(currentNode->numNodes() == 1)) {
currentNode = *currentNode->nodesBegin();
} else {
qDebug("not a simple archive");
return NULL;
}
}
}
void InstallationManager::updateProgress(float percentage)
{
m_InstallationProgress.setValue(static_cast(percentage * 100.0));
if (m_InstallationProgress.wasCanceled()) {
m_CurrentArchive->cancel();
m_InstallationProgress.reset();
}
}
void InstallationManager::updateProgressFile(LPCWSTR fileName)
{
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
m_InstallationProgress.setLabelText(QString::fromWCharArray(fileName));
#else
m_InstallationProgress.setLabelText(QString::fromUtf16(fileName));
#endif
}
void InstallationManager::report7ZipError(LPCWSTR errorMessage)
{
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
m_InstallationProgress.setLabelText(QString::fromWCharArray(errorMessage));
#else
reportError(QString::fromUtf16(errorMessage));
#endif
m_CurrentArchive->cancel();
}
QString InstallationManager::generateBackupName(const QString &directoryName) const
{
QString backupName = directoryName + "_backup";
if (QDir(backupName).exists()) {
int idx = 2;
QString temp = backupName + QString::number(idx);
while (QDir(temp).exists()) {
++idx;
temp = backupName + QString::number(idx);
}
backupName = temp;
}
return backupName;
}
bool InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) const
{
QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName);
while (QDir(targetDirectory).exists()) {
QueryOverwriteDialog overwriteDialog(m_ParentWidget);
if (overwriteDialog.exec()) {
if (overwriteDialog.backup()) {
QString backupDirectory = generateBackupName(targetDirectory);
if (!copyDir(targetDirectory, backupDirectory, false)) {
reportError(tr("failed to create backup"));
return false;
}
}
if (merge != nullptr) {
*merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE);
}
if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) {
bool ok = false;
QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"),
QLineEdit::Normal, modName, &ok);
if (ok && !name.isEmpty()) {
modName.update(name, GUESS_USER);
if (!ensureValidModName(modName)) {
return false;
}
targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory.mid(0).append("\\").append(modName));
}
} else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) {
// save original settings like categories. Because it makes sense
QString metaFilename = targetDirectory.mid(0).append("/meta.ini");
QFile settingsFile(metaFilename);
QByteArray originalSettings;
if (settingsFile.open(QIODevice::ReadOnly)) {
originalSettings = settingsFile.readAll();
settingsFile.close();
}
// remove the directory with all content, then recreate it empty
shellDelete(QStringList(targetDirectory));
if (!QDir().mkdir(targetDirectory)) {
// windows may keep the directory around for a moment, preventing its re-creation. Not sure
// if this still happens with shellDelete
Sleep(100);
QDir().mkdir(targetDirectory);
}
// restore the saved settings
if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(originalSettings);
settingsFile.close();
} else {
qCritical("failed to restore original settings: %s", metaFilename.toUtf8().constData());
}
return true;
} else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) {
return true;
}
} else {
return false;
}
}
QDir().mkdir(targetDirectory);
return true;
}
bool InstallationManager::ensureValidModName(GuessedValue &name) const
{
while (name->isEmpty()) {
bool ok;
name.update(QInputDialog::getText(m_ParentWidget, tr("Invalid name"),
tr("The name you entered is invalid, please enter a different one."),
QLineEdit::Normal, "", &ok),
GUESS_USER);
if (!ok) {
return false;
}
}
return true;
}
bool InstallationManager::doInstall(GuessedValue &modName, int modID,
const QString &version, const QString &newestVersion, int categoryID)
{
if (!ensureValidModName(modName)) {
return false;
}
bool merge = false;
// determine target directory
if (!testOverwrite(modName, &merge)) {
return false;
}
QString targetDirectoryNative = m_ModsDirectory.mid(0).append("\\").append(modName);
QString targetDirectory = QDir::fromNativeSeparators(targetDirectoryNative);
qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData());
m_InstallationProgress.setWindowTitle(tr("Extracting files"));
m_InstallationProgress.setLabelText(QString());
m_InstallationProgress.setValue(0);
m_InstallationProgress.setWindowModality(Qt::WindowModal);
m_InstallationProgress.show();
if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(targetDirectory)).c_str(),
new MethodCallback(this, &InstallationManager::updateProgress),
new MethodCallback(this, &InstallationManager::updateProgressFile),
new MethodCallback(this, &InstallationManager::report7ZipError))) {
m_InstallationProgress.hide();
if (m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) {
return false;
} else {
throw std::runtime_error("extracting failed");
}
}
m_InstallationProgress.hide();
QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat);
// overwrite settings only if they are actually are available or haven't been set before
if ((modID != 0) || !settingsFile.contains("modid")) {
settingsFile.setValue("modid", modID);
}
if (!settingsFile.contains("version") ||
(!version.isEmpty() &&
(!merge || (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString()))))) {
settingsFile.setValue("version", version);
}
if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) {
settingsFile.setValue("newestVersion", newestVersion);
}
// issue #51 used to overwrite the manually set categories
if (!settingsFile.contains("category")) {
settingsFile.setValue("category", QString::number(categoryID));
}
settingsFile.setValue("installationFile", m_CurrentFile);
return true;
}
bool InstallationManager::wasCancelled()
{
return m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED;
}
void InstallationManager::postInstallCleanup() const
{
m_CurrentArchive->close();
// directories we may want to remove. sorted from longest to shortest to ensure we remove subdirectories first.
auto longestFirst = [](const QString &LHS, const QString &RHS) -> bool {
if (LHS.size() != RHS.size()) return LHS.size() > RHS.size();
else return LHS < RHS;
};
std::set> directoriesToRemove(longestFirst);
// clean up temp files
// TODO: this doesn't yet remove directories. Also, the files may be left there if this point isn't reached
foreach (const QString &tempFile, m_TempFilesToDelete) {
QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile);
QFile::remove(fileInfo.absoluteFilePath());
directoriesToRemove.insert(fileInfo.absolutePath());
}
// try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok
foreach (const QString &dir, directoriesToRemove) {
QDir().rmdir(dir);
}
}
bool InstallationManager::install(const QString &fileName, GuessedValue &modName, bool &hasIniTweaks)
{
QFileInfo fileInfo(fileName);
if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) {
reportError(tr("File format \"%1\" not supported").arg(fileInfo.completeSuffix()));
return false;
}
modName.setFilter(&fixDirectoryName);
modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK);
// read out meta information from the download if available
int modID = 0;
QString version = "";
QString newestVersion = "";
int categoryID = 0;
QString metaName = fileName.mid(0).append(".meta");
if (QFile(metaName).exists()) {
QSettings metaFile(metaName, QSettings::IniFormat);
modID = metaFile.value("modID", 0).toInt();
modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK);
modName.update(metaFile.value("modName", "").toString(), GUESS_META);
version = metaFile.value("version", "").toString();
newestVersion = metaFile.value("newestVersion", "").toString();
unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt());
categoryID = CategoryFactory::instance().getCategoryID(categoryIndex);
}
if (version.isEmpty()) {
QDateTime lastMod = fileInfo.lastModified();
version = "d" + lastMod.toString("yyyy.M.d");
}
{ // guess the mod name and mod if from the file name if there was no meta information
QString guessedModName;
int guessedModID = modID;
NexusInterface::interpretNexusFileName(QFileInfo(fileName).baseName(), guessedModName, guessedModID, false);
if ((modID == 0) && (guessedModID != -1)) {
modID = guessedModID;
} else if (modID != guessedModID) {
qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID);
}
modName.update(guessedModName, GUESS_GOOD);
}
m_CurrentFile = fileInfo.absoluteFilePath();
if (fileInfo.dir() == QDir(m_DownloadsDirectory)) {
m_CurrentFile = fileInfo.fileName();
}
qDebug("using mod name \"%s\" (id %d) -> %s", modName->toUtf8().constData(), modID, qPrintable(m_CurrentFile));
// open the archive and construct the directory tree the installers work on
bool archiveOpen = m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(fileName)).c_str(),
new MethodCallback(this, &InstallationManager::queryPassword));
if (!archiveOpen) {
qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_CurrentArchive->getLastError());
}
ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this));
QScopedPointer filesTree(archiveOpen ? createFilesTree() : NULL);
IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED;
std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) {
return LHS->priority() > RHS->priority();
});
foreach (IPluginInstaller *installer, m_Installers) {
// don't use inactive installers
if (!installer->isActive()) {
continue;
}
// try only manual installers if that was requested
if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) {
if (!installer->isManualInstaller()) {
continue;
}
} else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) {
break;
}
try {
{ // simple case
IPluginInstallerSimple *installerSimple = dynamic_cast(installer);
if ((installerSimple != NULL) &&
(filesTree != NULL) && (installer->isArchiveSupported(*filesTree))) {
installResult = installerSimple->install(modName, *filesTree, version, modID);
if (installResult == IPluginInstaller::RESULT_SUCCESS) {
mapToArchive(filesTree.data());
// the simple installer only prepares the installation, the rest works the same for all installers
if (!doInstall(modName, modID, version, newestVersion, categoryID)) {
installResult = IPluginInstaller::RESULT_FAILED;
}
}
}
}
{ // custom case
IPluginInstallerCustom *installerCustom = dynamic_cast(installer);
if ((installerCustom != NULL) &&
(((filesTree != NULL) && installer->isArchiveSupported(*filesTree)) ||
((filesTree == NULL) && installerCustom->isArchiveSupported(fileName)))) {
std::set installerExtensions = installerCustom->supportedExtensions();
if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) {
installResult = installerCustom->install(modName, fileName, version, modID);
}
}
}
} catch (const IncompatibilityException &e) {
qCritical("plugin \"%s\" incompatible: %s",
qPrintable(installer->name()), e.what());
}
// act upon the installation result. at this point the files have already been
// extracted to the correct location
switch (installResult) {
case IPluginInstaller::RESULT_CANCELED:
case IPluginInstaller::RESULT_FAILED: {
return false;
} break;
case IPluginInstaller::RESULT_SUCCESS:
case IPluginInstaller::RESULT_SUCCESSCANCEL: {
if (filesTree != NULL) {
DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks"));
hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) &&
((*iniTweakNode)->numLeafs() != 0);
return true;
} else {
return false;
}
} break;
}
}
reportError(tr("None of the available installer plugins were able to handle that archive"));
return false;
}
QString InstallationManager::getErrorString(Archive::Error errorCode)
{
switch (errorCode) {
case Archive::ERROR_NONE: {
return tr("no error");
} break;
case Archive::ERROR_LIBRARY_NOT_FOUND: {
return tr("7z.dll not found");
} break;
case Archive::ERROR_LIBRARY_INVALID: {
return tr("7z.dll isn't valid");
} break;
case Archive::ERROR_ARCHIVE_NOT_FOUND: {
return tr("archive not found");
} break;
case Archive::ERROR_FAILED_TO_OPEN_ARCHIVE: {
return tr("failed to open archive");
} break;
case Archive::ERROR_INVALID_ARCHIVE_FORMAT: {
return tr("unsupported archive type");
} break;
case Archive::ERROR_LIBRARY_ERROR: {
return tr("internal library error");
} break;
case Archive::ERROR_ARCHIVE_INVALID: {
return tr("archive invalid");
} break;
default: {
// this probably means the archiver.dll is newer than this
return tr("unknown archive error");
} break;
}
}
void InstallationManager::registerInstaller(IPluginInstaller *installer)
{
m_Installers.push_back(installer);
installer->setInstallationManager(this);
IPluginInstallerCustom *installerCustom = dynamic_cast(installer);
if (installerCustom != NULL) {
std::set extensions = installerCustom->supportedExtensions();
m_SupportedExtensions.insert(extensions.begin(), extensions.end());
}
}
QStringList InstallationManager::getSupportedExtensions() const
{
QStringList result;
foreach (const QString &extension, m_SupportedExtensions) {
result.append(extension);
}
return result;
}