diff options
| author | Mikaël Capelle <capelle.mikael@gmail.com> | 2020-05-01 18:03:27 +0200 |
|---|---|---|
| committer | Mikaël Capelle <capelle.mikael@gmail.com> | 2020-05-07 12:16:33 +0200 |
| commit | 068e12bbb2cd774af89715105241ba7d3e3d0dcd (patch) | |
| tree | 5c70342098aa6d0cb172a74b2e50518fa236be4e /src | |
| parent | 40c433580f69006a42dc27a6444ebd9a6cccc440 (diff) | |
Move to the new filetree for the installation manager.
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/archivefiletree.cpp | 252 | ||||
| -rw-r--r-- | src/archivefiletree.h | 72 | ||||
| -rw-r--r-- | src/installationmanager.cpp | 326 | ||||
| -rw-r--r-- | src/installationmanager.h | 93 |
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; |
