/*
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:
/**
* @override
*/
std::shared_ptr addFile(QString path) 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 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* 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());
}