/* 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. * * @param parent The tree containing this entry. * @param name The name of this entry. * @param index The index of the entry in the archive. */ ArchiveFileEntry(std::shared_ptr parent, QString name, int index) : FileTreeEntry(parent, name), m_Index(index) {} virtual std::shared_ptr clone() const override { return std::make_shared(nullptr, name(), m_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: // Override to avoid VS warnings: virtual std::shared_ptr astree() override { return IFileTree::astree(); } virtual std::shared_ptr astree() const override { return IFileTree::astree(); } protected: virtual std::shared_ptr clone() const override { return IFileTree::clone(); } public: // Overrides: /** * */ static void mapToArchive(IFileTree const& tree, QString path, std::vector 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]->addOutputFilePath(tmp.toStdWString()); } mapToArchive(*archiveEntry.astree(), tmp, data); } else { const ArchiveFileEntry& archiveFileEntry = dynamic_cast(*entry); if (archiveFileEntry.m_Index != -1) { data[archiveFileEntry.m_Index]->addOutputFilePath( (path + archiveFileEntry.name()).toStdWString()); } } } } /** * */ void mapToArchive(Archive& archive) const override { mapToArchive(*this, "", archive.getFileList()); } protected: /** * Overriding makeDirectory and makeFile to create file tree or file entry with index * -1. * */ virtual std::shared_ptr makeDirectory(std::shared_ptr parent, QString name) const override { return std::make_shared(parent, name, -1, std::vector{}); } virtual std::shared_ptr makeFile(std::shared_ptr parent, QString name) const override { return std::make_shared(parent, name, -1); } virtual bool 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 = -1; std::vector currentFiles; for (auto& p : m_Files) { // At the start or if we have reset, just retrieve the current name: if (currentName == "") { currentName = std::get<0>(p)[0]; } // If the name is different, we need to create a directory from what we have // accumulated: if (currentName != std::get<0>(p)[0]) { // We may or may not have an index here, it depends on the type of archive (some // archives list intermediate non-empty folders, some don't): entries.push_back(std::make_shared( parent, currentName, currentIndex, std::move(currentFiles))); currentFiles.clear(); // Back to a valid state. // Reset the index: currentIndex = -1; } // 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))); currentName = ""; } else { // Otherwize, it is the actual "file" corresponding to the directory we are // listing, 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))); } // Let the parent class sort the entries: return false; } virtual std::shared_ptr doClone() const override { return std::make_shared(nullptr, name(), m_Index, m_Files); } private: mutable std::vector m_Files; }; std::shared_ptr ArchiveFileTree::makeTree(Archive const& archive) { auto const& data = archive.getFileList(); std::vector files; files.reserve(data.size()); for (size_t i = 0; i < data.size(); ++i) { // Ignore "." and ".." as they're useless and muck things up if (data[i]->getArchiveFilePath().compare(L".") == 0 || data[i]->getArchiveFilePath().compare(L"..") == 0) { continue; } files.push_back( std::make_tuple(QString::fromStdWString(data[i]->getArchiveFilePath()) .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(std::vector 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]->addOutputFilePath(aentry->path().toStdWString()); } if (entry->isDir()) { auto tree = entry->astree(); mapToArchive(data, tree->begin(), tree->end()); } } } void ArchiveFileTree::mapToArchive( Archive& archive, std::vector> const& entries) { ::mapToArchive(archive.getFileList(), entries.cbegin(), entries.cend()); }