diff options
| author | Al <26797547+Al12rs@users.noreply.github.com> | 2020-05-20 13:05:02 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-20 13:05:02 +0200 |
| commit | b7110cba2dceb172bf6a903f1c5de5ff1c460f11 (patch) | |
| tree | ee3be3be522842c6ef42c14d48c822fa657333c9 /src/installationmanager.cpp | |
| parent | c214b67ae784537d696a53c8cae00676eefd3841 (diff) | |
| parent | c391c2b584a309f64f87dd5e00b0083279e6211c (diff) | |
Merge pull request #1066 from Holt59/new-filetree
Update for the new file tree implementation and minor fixes
Diffstat (limited to 'src/installationmanager.cpp')
| -rw-r--r-- | src/installationmanager.cpp | 326 |
1 files changed, 59 insertions, 267 deletions
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; |
