summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/archivefiletree.cpp252
-rw-r--r--src/archivefiletree.h72
-rw-r--r--src/installationmanager.cpp326
-rw-r--r--src/installationmanager.h93
5 files changed, 441 insertions, 303 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6e454fc1..99d31541 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,7 @@ add_filter(NAME src/browser GROUPS
add_filter(NAME src/core GROUPS
categories
+ archivefiletree
installationmanager
instancemanager
loadmechanism
diff --git a/src/archivefiletree.cpp b/src/archivefiletree.cpp
new file mode 100644
index 00000000..b629cc8e
--- /dev/null
+++ b/src/archivefiletree.cpp
@@ -0,0 +1,252 @@
+/*
+Copyright (C) MO2 Team. 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 <http://www.gnu.org/licenses/>.
+*/
+
+// For QObject::tr:
+#include <QObject>
+
+#include "archivefiletree.h"
+
+#include "log.h"
+
+using namespace MOBase;
+
+/**
+ * We use custom file entries to store the index.
+ */
+class ArchiveFileEntry : public virtual FileTreeEntry {
+public:
+
+ /**
+ * @brief Create a new entry corresponding to a file.
+ *
+ * @param parent The tree containing this file.
+ * @param name The name of this file.
+ * @param index The index of the file in the archive.
+ * @param time The modification time of this file.
+ */
+ ArchiveFileEntry(std::shared_ptr<IFileTree> parent, QString name, int index, QDateTime time) :
+ FileTreeEntry(parent, name, time), m_Index(index) {
+ }
+
+ /**
+ * @brief Create a new entry corresponding to a directory.
+ *
+ * @param parent The tree containing this directory.
+ * @param name The name of this directory.
+ * @param index The index of the directory in the archive, or -1.
+ */
+ ArchiveFileEntry(std::shared_ptr<IFileTree> parent, QString name, int index) :
+ FileTreeEntry(parent, name), m_Index(index) {
+ }
+
+ // No private since we are in an implementation file:
+ const int m_Index;
+};
+
+
+/**
+ *
+ */
+class ArchiveFileTreeImpl: public virtual ArchiveFileTree, public virtual ArchiveFileEntry {
+public:
+
+ using File = std::tuple<QStringList, bool, int>;
+
+public: // Public for make_shared (but not accessible by other since not exposed in .h):
+
+ ArchiveFileTreeImpl(std::shared_ptr<IFileTree> parent, QString name, int index, std::vector<File>&& files)
+ : FileTreeEntry(parent, name), ArchiveFileEntry(parent, name, index), IFileTree(), m_Files(std::move(files)) { }
+
+public: // Overrides:
+
+ /**
+ * @override
+ */
+ std::shared_ptr<FileTreeEntry> addFile(QString path, QDateTime time = QDateTime()) override {
+ // Cannot add file to an archive.
+ throw UnsupportedOperationException(QObject::tr("Cannot create file within an archive."));
+ }
+
+ /**
+ *
+ */
+ static void mapToArchive(IFileTree const& tree, QString path, FileData* const* data)
+ {
+ if (path.length() > 0) {
+ // when using a long windows path (starting with \\?\) we apparently can have redundant
+ // . components in the path. This wasn't a problem with "regular" path names.
+ if (path == ".") {
+ path.clear();
+ }
+ else {
+ path.append("\\");
+ }
+ }
+
+ for (auto const& entry : tree) {
+ if (entry->isDir()) {
+ const ArchiveFileTreeImpl& archiveEntry = dynamic_cast<const ArchiveFileTreeImpl&>(*entry);
+ QString tmp = path + archiveEntry.name();
+ if (archiveEntry.m_Index != -1) {
+ data[archiveEntry.m_Index]->addOutputFileName(tmp);
+ }
+ mapToArchive(*archiveEntry.astree(), tmp, data);
+ }
+ else {
+ const ArchiveFileEntry& archiveFileEntry = dynamic_cast<const ArchiveFileEntry&>(*entry);
+ data[archiveFileEntry.m_Index]->addOutputFileName(path + archiveFileEntry.name());
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ void mapToArchive(Archive* archive) const override {
+ FileData* const* data;
+ size_t size;
+ archive->getFileList(data, size);
+ mapToArchive(*this, "", data);
+ }
+
+protected:
+
+ /*
+ * Overriding this to create custom FileTreeEntry with index set to -1. No need to
+ * override makeFile() since we addFile is overriden. Note that this will not be
+ * used to create existing tree since we do this manually in doPopulate.
+ *
+ * @override
+ */
+ virtual std::shared_ptr<IFileTree> makeDirectory(
+ std::shared_ptr<IFileTree> parent, QString name) const override {
+ return std::make_shared<ArchiveFileTreeImpl>(parent, name, -1, std::vector<File>{});
+ }
+
+ virtual void doPopulate(std::shared_ptr<IFileTree> parent, std::vector<std::shared_ptr<FileTreeEntry>>& entries) const override {
+
+ // Sort by name:
+ std::sort(std::begin(m_Files), std::end(m_Files),
+ [](const auto& a, const auto& b) {
+ return std::get<0>(a)[0].compare(std::get<0>(b)[0], Qt::CaseInsensitive) < 0; });
+
+ // We know that the files are sorted:
+ QString currentName = "";
+ int currentIndex;
+ std::vector<File> currentFiles;
+ for (auto& p : m_Files) {
+
+ // At the start or if we have reset, just retrieve the current name and index - The
+ // index might not be valid in this case (e.g., if the path is a/b, the index is the
+ // one for a/b while we would want the one for a, but we correct that later):
+ if (currentName == "") {
+ currentName = std::get<0>(p)[0];
+ currentIndex = std::get<2>(p);
+ }
+
+ // If the name is different, we need to create a directory from what we have
+ // accumulated:
+ if (currentName != std::get<0>(p)[0]) {
+ // No index here since this is not an empty tree:
+ entries.push_back(std::make_shared<ArchiveFileTreeImpl>(parent, currentName, currentIndex, std::move(currentFiles)));
+ currentFiles.clear(); // Back to a valid state.
+
+ // Retrieve the next index:
+ currentIndex = std::get<2>(p);
+ }
+
+ // We can always override the current name:
+ currentName = std::get<0>(p)[0];
+
+ // If the current path contains only one components:
+ if (std::get<0>(p).size() == 1) {
+ // If it is not a directory, then it is a file in directly under this tree:
+ if (!std::get<1>(p)) {
+ entries.push_back(
+ std::make_shared<ArchiveFileEntry>(parent, currentName, std::get<2>(p), QDateTime()));
+ currentName = "";
+ }
+ // Otherwize, it is the actual "file" corresponding to the directory, so we can retrieve
+ // the index here:
+ currentIndex = std::get<2>(p);
+ }
+ else {
+ currentFiles.push_back({
+ QStringList(std::get<0>(p).begin() + 1, std::get<0>(p).end()), std::get<1>(p), std::get<2>(p)
+ });
+ }
+ }
+
+ if (currentName != "") {
+ entries.push_back(std::make_shared<ArchiveFileTreeImpl>(parent, currentName, currentIndex, std::move(currentFiles)));
+ }
+ }
+
+private:
+
+ mutable std::vector<File> m_Files;
+};
+
+std::shared_ptr<ArchiveFileTree> ArchiveFileTree::makeTree(Archive* archive) {
+
+ FileData* const* data;
+ size_t size;
+ archive->getFileList(data, size);
+
+ std::vector<ArchiveFileTreeImpl::File> files;
+ files.reserve(size);
+
+ for (size_t i = 0; i < size; ++i) {
+ files.push_back(std::make_tuple(data[i]->getFileName().replace("\\", "/").split("/", Qt::SkipEmptyParts), data[i]->isDirectory(), (int) i));
+ }
+
+ auto tree = std::make_shared<ArchiveFileTreeImpl>(nullptr, "", -1, std::move(files));
+ return tree;
+}
+
+/**
+ * @brief Recursive function for the ArchiveFileTree::mapToArchive method. Need a template
+ * here because iterators from a vector of entries are not exactly the same as the iterators
+ * returned by a IFileTree.
+ *
+ */
+template <class It>
+void mapToArchive(FileData* const* data, It begin, It end) {
+ for (auto it = begin; it != end; ++it) {
+ auto entry = *it;
+ auto* aentry = dynamic_cast<const ArchiveFileEntry*>(entry.get());
+
+ if (aentry->m_Index != -1) {
+ data[aentry->m_Index]->addOutputFileName(aentry->path());
+ }
+
+ if (entry->isDir()) {
+ auto tree = entry->astree();
+ mapToArchive(data, tree->begin(), tree->end());
+ }
+ }
+}
+
+void ArchiveFileTree::mapToArchive(Archive* archive, std::vector<std::shared_ptr<const FileTreeEntry>> const& entries) {
+ FileData* const* data;
+ size_t size;
+ archive->getFileList(data, size);
+
+ ::mapToArchive(data, entries.cbegin(), entries.cend());
+}
diff --git a/src/archivefiletree.h b/src/archivefiletree.h
new file mode 100644
index 00000000..0041acd6
--- /dev/null
+++ b/src/archivefiletree.h
@@ -0,0 +1,72 @@
+/*
+Copyright (C) MO2 Team. 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef ARCHIVEFILENETRY_H
+#define ARCHIVEFILENTRY_H
+
+#include "archive.h"
+#include "ifiletree.h"
+
+
+/**
+ *
+ */
+class ArchiveFileTree: public virtual MOBase::IFileTree {
+public:
+
+ /**
+ * @brief Create a new file tree representing the given archive.
+ *
+ * @param archive Archive to represent by the file tree.
+ *
+ * @return a file tree representing the given archive.
+ */
+ static std::shared_ptr<ArchiveFileTree> makeTree(Archive* archive);
+
+ /**
+ * @brief Update the given archive to reflect change in this tree.
+ *
+ * This method disables files that have been removed from the file
+ * tree and move the ones that have been moved.
+ *
+ * @param archive The archive to update. Must be the one used to
+ * create the tree.
+ */
+ virtual void mapToArchive(Archive *archive) const = 0;
+
+ /**
+ * @brief Update the given archive to prepare for the extraction
+ * of the given entries.
+ *
+ * This method "enables" files that correspond to the given entry.
+ *
+ * @param archive The archive to update. Must be the one used to
+ * create the tree.
+ * @param entries List of entries to mark for extraction. All the entries must
+ * come from a tree created with the given archive.
+ */
+ static void mapToArchive(Archive* archive, std::vector<std::shared_ptr<const FileTreeEntry>> const& entries);
+
+protected:
+
+ using IFileTree::IFileTree;
+
+};
+
+#endif
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp
index d62067c2..d4e6a444 100644
--- a/src/installationmanager.cpp
+++ b/src/installationmanager.cpp
@@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <tuple>
+
#include "installationmanager.h"
#include "utility.h"
@@ -56,6 +58,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <boost/assign.hpp>
#include <boost/scoped_ptr.hpp>
+#include "archivefiletree.h"
+
using namespace MOBase;
using namespace MOShared;
@@ -121,172 +125,18 @@ void InstallationManager::queryPassword(QString *password)
tr("Password"), QLineEdit::Password);
}
-void InstallationManager::mapToArchive(const DirectoryTree::Node *node, QString path, FileData * const *data)
-{
- if (path.length() > 0) {
- // when using a long windows path (starting with \\?\) we apparently can have redundant
- // . components in the path. This wasn't a problem with "regular" path names.
- if (path == ".") {
- path.clear();
- } else {
- path.append("\\");
- }
- }
-
- for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) {
- data[iter->getIndex()]->addOutputFileName(path + iter->getName().toQString());
- }
-
- for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
- QString temp = path + (*iter)->getData().name.toQString();
- if ((*iter)->getData().index != -1) {
- data[(*iter)->getData().index]->addOutputFileName(temp);
- }
- mapToArchive(*iter, temp, data);
- }
-}
-
-
-void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode)
-{
- FileData* const *data;
- size_t size;
- m_ArchiveHandler->getFileList(data, size);
-
- mapToArchive(baseNode, "", data);
-}
-
-bool InstallationManager::unpackSingleFile(const QString &fileName)
+bool InstallationManager::extractFiles(QDir extractPath)
{
- FileData* const *data;
- size_t size;
- m_ArchiveHandler->getFileList(data, size);
-
- QString baseName = QFileInfo(fileName).fileName();
-
- bool available = false;
- for (size_t i = 0; i < size; ++i) {
- if (data[i]->getFileName().compare(fileName, Qt::CaseInsensitive) == 0) {
- available = true;
- data[i]->addOutputFileName(baseName);
- m_TempFilesToDelete.insert(baseName);
- }
- }
-
- if (!available) {
- return false;
- }
-
m_InstallationProgress = new QProgressDialog(m_ParentWidget);
- ON_BLOCK_EXIT([this] () {
+ ON_BLOCK_EXIT([this]() {
m_InstallationProgress->hide();
m_InstallationProgress->deleteLater();
m_InstallationProgress = nullptr;
m_Progress = 0;
- });
+ });
m_InstallationProgress->setWindowFlags(
- m_InstallationProgress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
-
- m_InstallationProgress->setWindowTitle(tr("Extracting files"));
- m_InstallationProgress->setWindowModality(Qt::WindowModal);
- m_InstallationProgress->setFixedSize(600, 100);
- m_InstallationProgress->show();
-
- QFuture<bool> future = QtConcurrent::run([&]() -> bool {
- return m_ArchiveHandler->extract(
- QDir::tempPath(),
- new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
- nullptr,
- new MethodCallback<InstallationManager, void, QString const &>(this, &InstallationManager::report7ZipError)
- );
- });
- do {
- if (m_Progress != m_InstallationProgress->value())
- m_InstallationProgress->setValue(m_Progress);
- QCoreApplication::processEvents();
- } while (!future.isFinished() || m_InstallationProgress->isVisible());
- bool res = future.result();
-
- return res;
-}
-
-
-QString InstallationManager::extractFile(const QString &fileName)
-{
- if (unpackSingleFile(fileName)) {
- return QDir::tempPath() + "/" + QFileInfo(fileName).fileName();
- } else {
- return QString();
- }
-}
-
-
-static 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;
-
- for (const QString &file : filesOrig) {
- files.append(canonicalize(file));
- }
-
- QStringList result;
-
- FileData* const *data;
- size_t size;
- m_ArchiveHandler->getFileList(data, size);
-
- for (size_t i = 0; i < size; ++i) {
- //FIXME Use qstring all the way through
- if (files.contains(data[i]->getFileName(), Qt::CaseInsensitive)) {
- std::wstring temp = data[i]->getFileName().toStdWString();
- wchar_t const * const origFile = temp.c_str();
- const wchar_t *targetFile = origFile;
- //Note: I don't think 'flatten' is ever set to true. so this code
- //might never be executed
- if (flatten) {
- targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '\\');
- if (targetFile == nullptr) {
- targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '/');
- }
- if (targetFile == nullptr) {
- log::error("Failed to find backslash in {}", data[i]->getFileName());
- continue;
- } else {
- // skip the slash
- ++targetFile;
- }
- }
- data[i]->addOutputFileName(ToQString(targetFile));
-
- result.append(QDir::tempPath().append("/").append(ToQString(targetFile)));
-
- m_TempFilesToDelete.insert(ToQString(targetFile));
- }
- }
-
- m_InstallationProgress = new QProgressDialog(m_ParentWidget);
- ON_BLOCK_EXIT([this] () {
- m_InstallationProgress->hide();
- m_InstallationProgress->deleteLater();
- m_InstallationProgress = nullptr;
- m_Progress = 0;
- });
- m_InstallationProgress->setWindowFlags(
- m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint));
+ m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint));
m_InstallationProgress->setWindowTitle(tr("Extracting files"));
m_InstallationProgress->setWindowModality(Qt::WindowModal);
m_InstallationProgress->setFixedSize(600, 100);
@@ -298,9 +148,9 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool
QDir::tempPath(),
new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
nullptr,
- new MethodCallback<InstallationManager, void, QString const &>(this, &InstallationManager::report7ZipError)
+ new MethodCallback<InstallationManager, void, QString const&>(this, &InstallationManager::report7ZipError)
);
- });
+ });
do {
if (m_Progress != m_InstallationProgress->value())
m_InstallationProgress->setValue(m_Progress);
@@ -310,8 +160,9 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool
if (m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) {
if (!m_ErrorMessage.isEmpty()) {
throw MyException(tr("Extraction failed: %1").arg(m_ErrorMessage));
- } else {
- return QStringList();
+ }
+ else {
+ return false;
}
}
else {
@@ -319,122 +170,53 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool
}
}
- return result;
+ return true;
}
-IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue<QString> &modName, const QString &archiveName, const int &modId)
-{
- // 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;
- return install(archiveName, modName, iniTweaks, modId);
-}
-DirectoryTree *InstallationManager::createFilesTree()
+QString InstallationManager::extractFile(std::shared_ptr<const FileTreeEntry> entry)
{
- FileData* const *data;
- size_t size;
- m_ArchiveHandler->getFileList(data, size);
+ QStringList result = this->extractFiles({ entry });
+ return result.isEmpty() ? QString() : result[0];
+}
- QScopedPointer<DirectoryTree> 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();
+QStringList InstallationManager::extractFiles(std::vector<std::shared_ptr<const FileTreeEntry>> const& entries)
+{
+ // Remove the directory since mapToArchive would add them:
+ std::vector<std::shared_ptr<const FileTreeEntry>> files;
+ std::copy_if(entries.begin(), entries.end(), std::back_inserter(files),
+ [](auto const& entry) { return entry->isFile(); });
- QString fileName = data[i]->getFileName();
- QStringList components = fileName.split("\\");
+ // Update the archive:
+ ArchiveFileTree::mapToArchive(m_ArchiveHandler, files);
- // 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;
- }
- }
+ // Retrieve the file path:
+ QStringList result;
- 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, static_cast<int>(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;
- }
- }
- }
+ for (auto &entry : files) {
+ auto path = entry->path();
+ result.append(QDir::tempPath().append("/").append(path));
+ m_TempFilesToDelete.insert(path);
}
- 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))) {
- log::debug("{} on the top level", (*iter)->getData().name.toQString());
- return true;
- }
+ if (!extractFiles(QDir::tempPath())) {
+ return QStringList();
}
- // 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;
+ return result;
}
-DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *dataTree)
+IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue<QString> &modName, const QString &archiveName, int modId)
{
- DirectoryTree::Node *currentNode = dataTree;
-
- while (true) {
- if (isSimpleArchiveTopLayer(currentNode, false)) {
- return currentNode;
- } else if ((currentNode->numLeafs() == 0) &&
- (currentNode->numNodes() == 1)) {
- currentNode = *currentNode->nodesBegin();
- } else {
- log::debug("not a simple archive");
- return nullptr;
- }
- }
+ // 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;
+ return install(archiveName, modName, iniTweaks, modId);
}
-
void InstallationManager::updateProgress(float percentage)
{
if (m_InstallationProgress != nullptr) {
@@ -804,7 +586,8 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
}
ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this));
- QScopedPointer<DirectoryTree> filesTree(archiveOpen ? createFilesTree() : nullptr);
+ std::shared_ptr<IFileTree> filesTree =
+ archiveOpen ? ArchiveFileTree::makeTree(m_ArchiveHandler) : nullptr;
IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED;
std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) {
@@ -831,11 +614,20 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
IPluginInstallerSimple *installerSimple
= dynamic_cast<IPluginInstallerSimple *>(installer);
if ((installerSimple != nullptr) && (filesTree != nullptr)
- && (installer->isArchiveSupported(*filesTree))) {
+ && (installer->isArchiveSupported(filesTree))) {
installResult
- = installerSimple->install(modName, *filesTree, version, modID);
+ = installerSimple->install(modName, filesTree, version, modID);
if (installResult == IPluginInstaller::RESULT_SUCCESS) {
- mapToArchive(filesTree.data());
+
+ // Note: Have to maintain a pointer to IFileTree because install() takes a
+ // reference. This could be an issue if ever a plugin creates a new tree that
+ // is not a ArchiveFileTree.
+ ArchiveFileTree* p = dynamic_cast<ArchiveFileTree*>(filesTree.get());
+ if (p == nullptr) {
+ throw IncompatibilityException(tr("Invalid file tree returned by plugin."));
+ }
+ p->mapToArchive(m_ArchiveHandler);
+
// the simple installer only prepares the installation, the rest
// works the same for all installers
installResult = doInstall(modName, gameName, modID, version, newestVersion, categoryID, fileCategoryID, repository);
@@ -848,7 +640,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
= dynamic_cast<IPluginInstallerCustom *>(installer);
if ((installerCustom != nullptr)
&& (((filesTree != nullptr)
- && installer->isArchiveSupported(*filesTree))
+ && installer->isArchiveSupported(filesTree))
|| ((filesTree == nullptr)
&& installerCustom->isArchiveSupported(fileName)))) {
std::set<QString> installerExt
@@ -880,9 +672,9 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
case IPluginInstaller::RESULT_SUCCESS:
case IPluginInstaller::RESULT_SUCCESSCANCEL: {
if (filesTree != nullptr) {
- DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks"));
- hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) &&
- ((*iniTweakNode)->numLeafs() != 0);
+ auto iniTweakEntry = filesTree->find("INI Tweaks", FileTreeEntry::DIRECTORY);
+ hasIniTweaks = iniTweakEntry != nullptr
+ && !iniTweakEntry->astree()->empty();
}
return IPluginInstaller::RESULT_SUCCESS;
} break;
diff --git a/src/installationmanager.h b/src/installationmanager.h
index 199a0f82..008a0916 100644
--- a/src/installationmanager.h
+++ b/src/installationmanager.h
@@ -20,7 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef INSTALLATIONMANAGER_H
#define INSTALLATIONMANAGER_H
-
+#include <ifiletree.h>
#include <iinstallationmanager.h>
#include <iplugininstaller.h>
#include <guessedvalue.h>
@@ -107,42 +107,61 @@ public:
* @brief register an installer-plugin
* @param the installer to register
*/
- void registerInstaller(MOBase::IPluginInstaller *installer);
-
+ void registerInstaller(MOBase::IPluginInstaller *installer);
+
/**
* @return list of file extensions we can install
*/
QStringList getSupportedExtensions() const;
/**
- * @brief extract the specified file from the currently open archive to a temporary location
- * @param (relative) name of the file within the archive
- * @return the absolute name of the temporary file
- * @note the call will fail with an exception if no archive is open (plugins deriving
- * from IPluginInstallerSimple can rely on that, custom installers shouldn't)
- * @note the temporary file is automatically cleaned up after the installation
- * @note This call can be very slow if the archive is large and "solid"
+ * @brief Extract the specified file from the currently opened archive to a temporary location.
+ *
+ * This method cannot be used to extract directory.
+ *
+ * @param entry Entry corresponding to the file to extract.
+ *
+ * @return the absolute path to the temporary file.
+ *
+ * @note The call will fail with an exception if no archive is open (plugins deriving
+ * from IPluginInstallerSimple can rely on that, custom installers should not).
+ * @note The temporary file is automatically cleaned up after the installation.
+ * @note This call can be very slow if the archive is large and "solid".
*/
- virtual QString extractFile(const QString &fileName);
+ virtual QString extractFile(std::shared_ptr<const MOBase::FileTreeEntry> entry) override;
/**
- * @brief extract the specified files from the currently open archive to a temporary location
- * @param (relative) names of files within the archive
- * @return the absolute names of the temporary files
- * @note the call will fail with an exception if no archive is open (plugins deriving
- * from IPluginInstallerSimple can rely on that, custom installers shouldn't)
- * @note the temporary file is automatically cleaned up after the installation
- * @note This call can be very slow if the archive is large and "solid"
+ * @brief Extract the specified files from the currently opened archive to a temporary location.
+ *
+ * This method cannot be used to extract directory.
+ *
+ * @param entres Entries corresponding to the files to extract.
+ *
+ * @return the list of absolute paths to the temporary files.
+ *
+ * @note The call will fail with an exception if no archive is open (plugins deriving
+ * from IPluginInstallerSimple can rely on that, custom installers should not).
+ * @note The temporary file is automatically cleaned up after the installation.
+ * @note This call can be very slow if the archive is large and "solid".
+ *
+ * The flatten argument is not present here while it is present in the deprecated QStringList
+ * version for multiple reasons: 1) it was never used, 2) it is kind of fishy because there
+ * is no way to know if a file is going to be overriden, 3) it is quite easy to flatten a
+ * IFileTree and thus to given a list of entries flattened (this was not possible with the
+ * QStringList version since these were based on the name of the file inside the archive).
*/
- virtual QStringList extractFiles(const QStringList &files, bool flatten);
+ virtual QStringList extractFiles(std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> const& entries) override;
/**
- * @brief installs an archive
- * @param modName suggested name of the mod
- * @param archiveFile path to the archive to install
- * @return the installation result
+ * @brief Installs the given archive.
+ *
+ * @param modName Suggested name of the mod.
+ * @param archiveFile Path to the archive to install.
+ * @param modId ID of the mod, if available.
+ *
+ * @return the installation result.
*/
- virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue<QString> &modName, const QString &archiveName, const int &modId = 0);
+ virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue<QString> &modName, const QString &archiveName, int modId = 0) override;
/**
* @brief test if the specified mod name is free. If not, query the user how to proceed
@@ -161,20 +180,9 @@ private:
void updateProgressFile(const QString &fileName);
void report7ZipError(const QString &errorMessage);
- MOBase::DirectoryTree *createFilesTree();
-
- // remap all files in the archive to the directory structure represented by baseNode
- // files not present in baseNode are disabled
- void mapToArchive(const MOBase::DirectoryTree::Node *baseNode);
-
- // recursive worker function for mapToArchive
- void mapToArchive(const MOBase::DirectoryTree::Node *node, QString path, FileData * const *data);
+ // Recursive worker function for mapToArchive (takes raw reference for "speed").
bool unpackSingleFile(const QString &fileName);
-
- bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle);
- MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree);
-
MOBase::IPluginInstaller::EInstallResult doInstall(MOBase::GuessedValue<QString> &modName, QString gameName,
int modID, const QString &version, const QString &newestVersion, int categoryID, int fileCategoryID, const QString &repository);
@@ -205,6 +213,19 @@ private:
}
};
+ /**
+ * @brief Extract the files from the archived that are not disabled (that have
+ * output filenames associated with them) to the given path.
+ *
+ * @param extractPath Path (on the disk) were the extracted files should be put.
+ *
+ * This method is mainly a convenience method for the extractFiles() methods.
+ *
+ * @return true if the extraction was successful, false if the extraciton was
+ * cancelled. If an error occured, an exception is thrown.
+ */
+ bool extractFiles(QDir extractPath);
+
private:
bool m_IsRunning;