#include "quam/archive.h" #include "quam/pack.h" #include "quam/wad2.h" #include namespace Arc { Option getArchiveType(std::istream &st) noexcept { try { auto pos = st.tellg(); auto magic = readBytes<4>(st); st.seekg(pos); if(magic == std::array{'P', 'A', 'C', 'K'}) {return ArcType::Pack;} if(magic == std::array{'W', 'A', 'D', '2'}) {return ArcType::Wad2;} } catch(...) { } return None; } Node::Node(std::string _name) : name(_name), parent(nullptr) { } Node::~Node() { } Dir *Node::getRoot() { if(!parent) { return getDir(); } else { Dir *rover = parent; while(rover->parent) { rover = rover->parent; } return rover; } } Node *Node::findPath(std::string path) { if(path.empty()) { return nullptr; } Dir *rover; // get the initial dir, which will either be: // - the root if the path starts with / // - the node itself // - the node's current Dir (when it's not a Dir) if(path.front() == '/') { path.erase(path.begin()); rover = getRoot(); } else { rover = getDir(); if(!rover) { rover = parent; } } if(path.empty()) { // no file, so just the directory itself return rover; } else if(auto slash = path.find('/'); slash != std::string::npos) { std::string prev; prev = path.substr(0, slash); path = path.substr(slash + 1); if(prev == "..") { // parent dir on ".." rover = rover->parent ? rover->parent : rover; } else if(prev != ".") { if(auto node = rover->findNode(prev)) { // we want a subdirectory since this path continues rover = node->getDir(); } else { // took a wrong turn rover = nullptr; } } // recurse return rover ? rover->findPath(path) : nullptr; } else { // must be a node inside a directory return rover->findNode(path); } } std::string Node::getPath(bool fromChild) const { if(parent) { return parent->getPath(true) + '/' + name; } else if(fromChild) { return std::string{}; } else { return "/"; } } Dir::Dir(std::string _name) : Node(_name), m_data() { } Dir::~Dir() { } UniquePtr const &Dir::emplaceBack(Node *node) { node->parent = this; return m_data.emplace_back(node); } Dir Dir::readArchive(std::istream &st, ArcType type) { switch(type) { case ArcType::Pack: return readPack(st); case ArcType::Wad2: return readWad2(st); } Q_UNREACHABLE(); } Node *Dir::findNode(std::string const &name) { auto it = std::find_if(m_data.begin(), m_data.end(), [&name](auto const &node) { return node->name == name; }); return it != m_data.end() ? &**it : nullptr; } File::File(std::string _name, QByteArray &&_data) : Node(_name), m_data(std::move(_data)) { } File::~File() { } ArcInfo::ArcInfo(ArcType arcType) noexcept : type{arcType} { switch(type) { case ArcType::Pack: pathMaxChars = 56; validatorType = PackValidator::staticMetaObject; break; case ArcType::Wad2: pathMaxChars = 16; validatorType = Wad2Validator::staticMetaObject; break; } } Arc::Arc(std::istream &st, QObject *parent) : QObject{parent}, info{ArcInfo(orThrow(getArchiveType(st), FileFormatError("not an archive")))}, root{Dir::readArchive(st, info.type)}, validator{qobject_cast(info.validatorType .newInstance(Q_ARG(QObject *, this)))} { } Arc::~Arc() { } Model::Model(QObject *parent) : QAbstractItemModel{parent}, m_dir{nullptr} { } Model::~Model() { } QVariant Model::data(QModelIndex const &index, int role) const { if(!index.isValid()) { return QVariant{}; } auto node = static_cast(index.internalPointer()); auto col = Column(index.column()); switch(role) { case Qt::DecorationRole: if(col == Column::Name) { auto icon = node->getDir() ? "folder" : "text-x-generic"; return QVariant{QIcon::fromTheme(QString::fromUtf8(icon))}; } break; case Qt::DisplayRole: switch(col) { case Column::Size: if(auto file = node->getFile()) { return QVariant{QString::number(file->size())}; } break; case Column::Type: if(auto file = node->getFile()) { return QVariant{tr(enumToString(file->type))}; } else { return QVariant{tr("Directory")}; } case Column::Name: return QVariant{tr(node->name.data())}; } case Qt::UserRole: return QVariant::fromValue(static_cast(node)); default: break; } return QVariant{}; } Qt::ItemFlags Model::flags(QModelIndex const &index) const { if(!index.isValid()) { return Qt::NoItemFlags; } else { return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; } } QVariant Model::headerData(int section, Qt::Orientation ori, int role) const { if(ori == Qt::Horizontal && role == Qt::DisplayRole) { switch(Column(section)) { case Column::Size: return QVariant{tr("Size")}; case Column::Type: return QVariant{tr("Type")}; case Column::Name: return QVariant{tr("Name")}; } } return QVariant{}; } QModelIndex Model::index(int row, int col, QModelIndex const &parent) const { if(!m_dir || !hasIndex(row, col, parent) || row > m_dir->size()) { return QModelIndex{}; } else { return createIndex(row, col, m_dir->at(row)); } } QModelIndex Model::parent(QModelIndex const &) const { return QModelIndex{}; } int Model::rowCount(QModelIndex const &) const { return m_dir ? m_dir->size() : 0; } int Model::columnCount(QModelIndex const &) const { return enumMax(); } Dir *Model::dir() const { return m_dir; } Node *Model::findPath(std::string path) { return m_dir->findPath(path); } void Model::setDir(Dir *to) { auto from = m_dir; if(to != from) { emit layoutAboutToBeChanged(); for(int row = 0, rows = rowCount(); row < rows; row++) { for(int col = 0, cols = columnCount(); col < cols; col++) { changePersistentIndex(index(row, col), QModelIndex{}); } } m_dir = to; emit layoutChanged(); emit dirChanged(from, to); } } void Model::setDirToIndex(QModelIndex const &ind) { auto node = static_cast(ind.data(Qt::UserRole).value()); if(auto dir = node->getDir()) { setDir(dir); } } bool Model::goUp() { if(m_dir && m_dir->parent) { setDir(m_dir->parent); return true; } else { return false; } } bool Model::goPath(std::string path) { Node *node; Dir *dir; if(m_dir && (node = m_dir->findPath(path)) && (dir = node->getDir())) { setDir(dir); return true; } else { return false; } } } // EOF