From 068e12bbb2cd774af89715105241ba7d3e3d0dcd Mon Sep 17 00:00:00 2001 From: Mikaƫl Capelle Date: Fri, 1 May 2020 18:03:27 +0200 Subject: Move to the new filetree for the installation manager. --- src/archivefiletree.cpp | 252 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/archivefiletree.cpp (limited to 'src/archivefiletree.cpp') 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 . +*/ + +// For QObject::tr: +#include + +#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 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 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; + +public: // Public for make_shared (but not accessible by other since not exposed in .h): + + ArchiveFileTreeImpl(std::shared_ptr parent, QString name, int index, std::vector&& files) + : FileTreeEntry(parent, name), ArchiveFileEntry(parent, name, index), IFileTree(), m_Files(std::move(files)) { } + +public: // Overrides: + + /** + * @override + */ + std::shared_ptr 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(*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(*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 makeDirectory( + std::shared_ptr parent, QString name) const override { + return std::make_shared(parent, name, -1, std::vector{}); + } + + virtual void doPopulate(std::shared_ptr parent, std::vector>& 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 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(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(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(parent, currentName, currentIndex, std::move(currentFiles))); + } + } + +private: + + mutable std::vector m_Files; +}; + +std::shared_ptr ArchiveFileTree::makeTree(Archive* archive) { + + FileData* const* data; + size_t size; + archive->getFileList(data, size); + + std::vector 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(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 +void mapToArchive(FileData* const* data, It begin, It end) { + for (auto it = begin; it != end; ++it) { + auto entry = *it; + auto* aentry = dynamic_cast(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> const& entries) { + FileData* const* data; + size_t size; + archive->getFileList(data, size); + + ::mapToArchive(data, entries.cbegin(), entries.cend()); +} -- cgit v1.3.1