all repos — mgba @ ac2097f0b69965835397aa8d10be2525d3a34c00

mGBA Game Boy Advance Emulator

Core: Back mLibraries with a shared database
Vicki Pfau vi@endrift.com
Wed, 18 Jan 2017 01:36:59 -0800
commit

ac2097f0b69965835397aa8d10be2525d3a34c00

parent

c11551a1f7809c4f434230fb18b624f6cddea062

M include/mgba/core/library.hinclude/mgba/core/library.h

@@ -14,27 +14,34 @@ #include <mgba/core/core.h>

#include <mgba-util/vector.h> struct mLibraryEntry { - char* filename; - char* title; + const char* base; + const char* filename; + const char* title; char internalTitle[17]; char internalCode[9]; - size_t filesize; enum mPlatform platform; + size_t filesize; + uint32_t crc32; }; +#ifdef USE_SQLITE3 + DECLARE_VECTOR(mLibraryListing, struct mLibraryEntry); -struct mLibrary { - struct mLibraryListing listing; -}; - -void mLibraryInit(struct mLibrary*); -void mLibraryDeinit(struct mLibrary*); +struct mLibrary; +struct mLibrary* mLibraryCreateEmpty(void); +struct mLibrary* mLibraryLoad(const char* filename); +void mLibraryDestroy(struct mLibrary*); struct VDir; struct VFile; -void mLibraryLoadDirectory(struct mLibrary* library, struct VDir* dir); -void mLibraryAddEntry(struct mLibrary* library, const char* filename, struct VFile* vf); +void mLibraryLoadDirectory(struct mLibrary* library, const char* base); + +size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints); +size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints); +struct VFile* mLibraryOpenVFile(struct mLibrary* library, const struct mLibraryEntry* entry); + +#endif CXX_GUARD_END
M src/core/library.csrc/core/library.c

@@ -1,29 +1,205 @@

-/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/core/library.h> +#include <mgba/core/core.h> #include <mgba-util/vfs.h> +#ifdef USE_SQLITE3 + +#include <sqlite3.h> + DEFINE_VECTOR(mLibraryListing, struct mLibraryEntry); -void mLibraryInit(struct mLibrary* library) { - mLibraryListingInit(&library->listing, 0); +struct mLibrary { + sqlite3* db; + sqlite3_stmt* insertPath; + sqlite3_stmt* insertRom; + sqlite3_stmt* insertRoot; + sqlite3_stmt* selectRom; + sqlite3_stmt* selectRoot; + sqlite3_stmt* count; + sqlite3_stmt* select; +}; + +#define CONSTRAINTS_ROMONLY \ + "CASE WHEN :useSize THEN roms.size = :size ELSE 1 END AND " \ + "CASE WHEN :usePlatform THEN roms.platform = :platform ELSE 1 END AND " \ + "CASE WHEN :useCrc32 THEN roms.crc32 = :crc32 ELSE 1 END AND " \ + "CASE WHEN :useInternalCode THEN roms.internalCode = :internalCode ELSE 1 END" + +#define CONSTRAINTS \ + CONSTRAINTS_ROMONLY " AND " \ + "CASE WHEN :useFilename THEN paths.path = :path ELSE 1 END AND " \ + "CASE WHEN :useRoot THEN roots.path = :root ELSE 1 END" + +static void _mLibraryInsertEntry(struct mLibrary* library, struct mLibraryEntry* entry); +static void _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf); + +static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry* constraints) { + if (!constraints) { + return; + } + + int useIndex, index; + if (constraints->crc32) { + useIndex = sqlite3_bind_parameter_index(statement, ":useCrc32"); + index = sqlite3_bind_parameter_index(statement, ":crc32"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_int(statement, index, constraints->crc32); + } + + if (constraints->filesize) { + useIndex = sqlite3_bind_parameter_index(statement, ":useSize"); + index = sqlite3_bind_parameter_index(statement, ":size"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_int64(statement, index, constraints->filesize); + } + + if (constraints->filename) { + useIndex = sqlite3_bind_parameter_index(statement, ":useFilename"); + index = sqlite3_bind_parameter_index(statement, ":path"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_text(statement, index, constraints->filename, -1, SQLITE_TRANSIENT); + } + + if (constraints->base) { + useIndex = sqlite3_bind_parameter_index(statement, ":useRoot"); + index = sqlite3_bind_parameter_index(statement, ":root"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_text(statement, index, constraints->base, -1, SQLITE_TRANSIENT); + } + + if (constraints->internalCode[0]) { + useIndex = sqlite3_bind_parameter_index(statement, ":useInternalCode"); + index = sqlite3_bind_parameter_index(statement, ":internalCode"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_text(statement, index, constraints->internalCode, -1, SQLITE_TRANSIENT); + } + + if (constraints->platform != PLATFORM_NONE) { + useIndex = sqlite3_bind_parameter_index(statement, ":usePlatform"); + index = sqlite3_bind_parameter_index(statement, ":platform"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_int(statement, index, constraints->platform); + } } -void mLibraryDeinit(struct mLibrary* library) { - size_t i; - for (i = 0; i < mLibraryListingSize(&library->listing); ++i) { - struct mLibraryEntry* entry = mLibraryListingGetPointer(&library->listing, i); - free(entry->filename); - free(entry->title); +struct mLibrary* mLibraryCreateEmpty(void) { + return mLibraryLoad(":memory:"); +} + +struct mLibrary* mLibraryLoad(const char* path) { + struct mLibrary* library = malloc(sizeof(*library)); + memset(library, 0, sizeof(*library)); + + if (sqlite3_open_v2(path, &library->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) { + goto error; + } + + static const char createTables[] = + " PRAGMA foreign_keys = ON;" + "\n CREATE TABLE IF NOT EXISTS version (" + "\n tname TEXT NOT NULL PRIMARY KEY," + "\n version INTEGER NOT NULL DEFAULT 1" + "\n );" + "\n CREATE TABLE IF NOT EXISTS roots (" + "\n rootid INTEGER NOT NULL PRIMARY KEY ASC," + "\n path TEXT NOT NULL UNIQUE," + "\n mtime INTEGER NOT NULL DEFAULT 0" + "\n );" + "\n CREATE TABLE IF NOT EXISTS roms (" + "\n romid INTEGER NOT NULL PRIMARY KEY ASC," + "\n internalTitle TEXT," + "\n internalCode TEXT," + "\n platform INTEGER NOT NULL DEFAULT -1," + "\n size INTEGER," + "\n crc32 INTEGER," + "\n md5 BLOB," + "\n sha1 BLOB" + "\n );" + "\n CREATE TABLE IF NOT EXISTS paths (" + "\n pathid INTEGER NOT NULL PRIMARY KEY ASC," + "\n romid INTEGER NOT NULL REFERENCES roms(romid) ON DELETE CASCADE," + "\n path TEXT NOT NULL," + "\n mtime INTEGER NOT NULL DEFAULT 0," + "\n rootid INTEGER REFERENCES roots(rootid) ON DELETE CASCADE," + "\n customTitle TEXT," + "\n CONSTRAINT location UNIQUE (path, rootid)" + "\n );" + "\n CREATE INDEX IF NOT EXISTS crc32 ON roms (crc32);" + "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('version', 1);" + "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roots', 1);" + "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 1);" + "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('paths', 1);"; + if (sqlite3_exec(library->db, createTables, NULL, NULL, NULL)) { + goto error; } - mLibraryListingDeinit(&library->listing); + + static const char insertPath[] = "INSERT INTO paths (romid, path, customTitle, rootid) VALUES (?, ?, ?, ?);"; + if (sqlite3_prepare_v2(library->db, insertPath, -1, &library->insertPath, NULL)) { + goto error; + } + + static const char insertRom[] = "INSERT INTO roms (crc32, size, internalCode, platform) VALUES (:crc32, :size, :internalCode, :platform);"; + if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) { + goto error; + } + + static const char insertRoot[] = "INSERT INTO roots (path) VALUES (?);"; + if (sqlite3_prepare_v2(library->db, insertRoot, -1, &library->insertRoot, NULL)) { + goto error; + } + + static const char selectRom[] = "SELECT romid FROM roms WHERE " CONSTRAINTS_ROMONLY ";"; + if (sqlite3_prepare_v2(library->db, selectRom, -1, &library->selectRom, NULL)) { + goto error; + } + + static const char selectRoot[] = "SELECT rootid FROM roots WHERE path = ?;"; + if (sqlite3_prepare_v2(library->db, selectRoot, -1, &library->selectRoot, NULL)) { + goto error; + } + + static const char count[] = "SELECT count(pathid) FROM paths JOIN roots USING (rootid) JOIN roms USING (romid) WHERE " CONSTRAINTS ";"; + if (sqlite3_prepare_v2(library->db, count, -1, &library->count, NULL)) { + goto error; + } + + static const char select[] = "SELECT *, paths.path AS filename, roots.path AS base FROM paths JOIN roots USING (rootid) JOIN roms USING (romid) WHERE " CONSTRAINTS " LIMIT :count OFFSET :offset;"; + if (sqlite3_prepare_v2(library->db, select, -1, &library->select, NULL)) { + goto error; + } + + return library; + +error: + mLibraryDestroy(library); + return NULL; +} + +void mLibraryDestroy(struct mLibrary* library) { + sqlite3_finalize(library->insertPath); + sqlite3_finalize(library->insertRom); + sqlite3_finalize(library->insertRoot); + sqlite3_finalize(library->selectRom); + sqlite3_finalize(library->selectRoot); + sqlite3_finalize(library->select); + sqlite3_finalize(library->count); + sqlite3_close(library->db); } -void mLibraryLoadDirectory(struct mLibrary* library, struct VDir* dir) { +void mLibraryLoadDirectory(struct mLibrary* library, const char* base) { + struct VDir* dir = VDirOpenArchive(base); + if (!dir) { + dir = VDirOpen(base); + } + if (!dir) { + return; + } struct VDirEntry* dirent = dir->listNext(dir); while (dirent) { struct VFile* vf = dir->openFile(dir, dirent->name(dirent), O_RDONLY);

@@ -31,33 +207,174 @@ if (!vf) {

dirent = dir->listNext(dir); continue; } - mLibraryAddEntry(library, dirent->name(dirent), vf); + _mLibraryAddEntry(library, dirent->name(dirent), base, vf); dirent = dir->listNext(dir); } + dir->close(dir); } -void mLibraryAddEntry(struct mLibrary* library, const char* filename, struct VFile* vf) { +void _mLibraryAddEntry(struct mLibrary* library, const char* filename, const char* base, struct VFile* vf) { struct mCore* core; if (!vf) { - vf = VFileOpen(filename, O_RDONLY); - } - if (!vf) { return; } core = mCoreFindVF(vf); if (core) { + struct mLibraryEntry entry; + memset(&entry, 0, sizeof(entry)); core->init(core); core->loadROM(core, vf); - struct mLibraryEntry* entry = mLibraryListingAppend(&library->listing); - core->getGameTitle(core, entry->internalTitle); - core->getGameCode(core, entry->internalCode); - entry->title = NULL; - entry->filename = strdup(filename); - entry->filesize = vf->size(vf); + core->getGameTitle(core, entry.internalTitle); + core->getGameCode(core, entry.internalCode); + core->checksum(core, &entry.crc32, CHECKSUM_CRC32); + entry.platform = core->platform(core); + entry.title = NULL; + entry.base = base; + entry.filename = filename; + entry.filesize = vf->size(vf); + _mLibraryInsertEntry(library, &entry); // Note: this destroys the VFile core->deinit(core); } else { vf->close(vf); } } + +static void _mLibraryInsertEntry(struct mLibrary* library, struct mLibraryEntry* entry) { + sqlite3_exec(library->db, "BEGIN TRANSACTION;", NULL, NULL, NULL); + + sqlite3_clear_bindings(library->selectRom); + sqlite3_reset(library->selectRom); + struct mLibraryEntry constraints = *entry; + constraints.filename = NULL; + constraints.base = NULL; + _bindConstraints(library->selectRom, &constraints); + sqlite3_int64 romId; + if (sqlite3_step(library->selectRom) == SQLITE_DONE) { + sqlite3_clear_bindings(library->insertRom); + sqlite3_reset(library->insertRom); + _bindConstraints(library->insertRom, entry); + sqlite3_step(library->insertRom); + romId = sqlite3_last_insert_rowid(library->db); + } else { + romId = sqlite3_column_int64(library->selectRom, 0); + } + + sqlite3_int64 rootId = 0; + if (entry->base) { + sqlite3_clear_bindings(library->selectRoot); + sqlite3_reset(library->selectRoot); + sqlite3_bind_text(library->selectRoot, 1, entry->base, -1, SQLITE_TRANSIENT); + if (sqlite3_step(library->selectRoot) == SQLITE_DONE) { + sqlite3_clear_bindings(library->insertRoot); + sqlite3_reset(library->insertRoot); + sqlite3_bind_text(library->insertRoot, 1, entry->base, -1, SQLITE_TRANSIENT); + sqlite3_step(library->insertRoot); + rootId = sqlite3_last_insert_rowid(library->db); + } else { + rootId = sqlite3_column_int64(library->selectRoot, 0); + } + } + + sqlite3_clear_bindings(library->insertPath); + sqlite3_reset(library->insertPath); + sqlite3_bind_int64(library->insertPath, 1, romId); + sqlite3_bind_text(library->insertPath, 2, entry->filename, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(library->insertPath, 3, entry->title, -1, SQLITE_TRANSIENT); + if (rootId > 0) { + sqlite3_bind_int64(library->insertPath, 4, rootId); + } + sqlite3_step(library->insertPath); + sqlite3_exec(library->db, "COMMIT;", NULL, NULL, NULL); +} + +size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) { + sqlite3_clear_bindings(library->count); + sqlite3_reset(library->count); + _bindConstraints(library->count, constraints); + if (sqlite3_step(library->count) != SQLITE_ROW) { + return 0; + } + return sqlite3_column_int64(library->count, 0); +} + +size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints) { + mLibraryListingClear(out); // TODO: Free memory + sqlite3_clear_bindings(library->select); + sqlite3_reset(library->select); + _bindConstraints(library->select, constraints); + + int countIndex = sqlite3_bind_parameter_index(library->select, ":count"); + int offsetIndex = sqlite3_bind_parameter_index(library->select, ":offset"); + sqlite3_bind_int64(library->select, countIndex, numEntries ? numEntries : -1); + sqlite3_bind_int64(library->select, offsetIndex, offset); + + size_t entryIndex; + for (entryIndex = 0; (!numEntries || entryIndex < numEntries) && sqlite3_step(library->select) == SQLITE_ROW; ++entryIndex) { + struct mLibraryEntry* entry = mLibraryListingAppend(out); + memset(entry, 0, sizeof(*entry)); + int nCols = sqlite3_column_count(library->select); + int i; + for (i = 0; i < nCols; ++i) { + const char* colName = sqlite3_column_name(library->select, i); + if (strcmp(colName, "crc32") == 0) { + entry->crc32 = sqlite3_column_int(library->select, i); + } else if (strcmp(colName, "platform") == 0) { + entry->platform = sqlite3_column_int(library->select, i); + } else if (strcmp(colName, "size") == 0) { + entry->filesize = sqlite3_column_int64(library->select, i); + } else if (strcmp(colName, "internalCode") == 0 && sqlite3_column_type(library->select, i) == SQLITE_TEXT) { + strncpy(entry->internalCode, (const char*) sqlite3_column_text(library->select, i), sizeof(entry->internalCode) - 1); + } else if (strcmp(colName, "internalTitle") == 0 && sqlite3_column_type(library->select, i) == SQLITE_TEXT) { + strncpy(entry->internalTitle, (const char*) sqlite3_column_text(library->select, i), sizeof(entry->internalTitle) - 1); + } else if (strcmp(colName, "filename") == 0) { + entry->filename = strdup((const char*) sqlite3_column_text(library->select, i)); + } else if (strcmp(colName, "base") == 0) { + entry->base = strdup((const char*) sqlite3_column_text(library->select, i)); + } + } + } + return mLibraryListingSize(out); +} + +struct VFile* mLibraryOpenVFile(struct mLibrary* library, const struct mLibraryEntry* entry) { + struct mLibraryListing entries; + mLibraryListingInit(&entries, 0); + if (!mLibraryGetEntries(library, &entries, 0, 0, entry)) { + mLibraryListingDeinit(&entries); + return NULL; + } + struct VFile* vf = NULL; + size_t i; + for (i = 0; i < mLibraryListingSize(&entries); ++i) { + struct mLibraryEntry* e = mLibraryListingGetPointer(&entries, i); + struct VDir* dir = VDirOpenArchive(e->base); + bool isArchive = true; + if (!dir) { + dir = VDirOpen(e->base); + isArchive = false; + } + if (!dir) { + continue; + } + vf = dir->openFile(dir, e->filename, O_RDONLY); + if (vf && isArchive) { + struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); + uint8_t buffer[2048]; + ssize_t read; + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + vfclone->write(vfclone, buffer, read); + } + vf->close(vf); + vf = vfclone; + } + dir->close(dir); + if (vf) { + break; + } + } + return vf; +} + +#endif
M src/platform/qt/ArchiveInspector.cppsrc/platform/qt/ArchiveInspector.cpp

@@ -7,29 +7,24 @@ #include "ArchiveInspector.h"

#include <mgba-util/vfs.h> +#include "ConfigController.h" + using namespace QGBA; ArchiveInspector::ArchiveInspector(const QString& filename, QWidget* parent) : QDialog(parent) + , m_model(ConfigController::configDir() + "/library.sqlite3") { m_ui.setupUi(this); - m_dir = VDirOpenArchive(filename.toUtf8().constData()); - if (m_dir) { - m_model.loadDirectory(m_dir); - } + m_model.loadDirectory(filename); + m_model.constrainBase(filename); m_ui.archiveListing->setModel(&m_model); } -ArchiveInspector::~ArchiveInspector() { - if (m_dir) { - m_dir->close(m_dir); - } -} - VFile* ArchiveInspector::selectedVFile() const { QModelIndex index = m_ui.archiveListing->selectionModel()->currentIndex(); if (!index.isValid()) { return nullptr; } - return m_dir->openFile(m_dir, m_model.entryAt(index.row())->filename, O_RDONLY); + return m_model.openVFile(index); }
M src/platform/qt/ArchiveInspector.hsrc/platform/qt/ArchiveInspector.h

@@ -10,7 +10,6 @@ #include "LibraryModel.h"

#include "ui_ArchiveInspector.h" -struct VDir; struct VFile; namespace QGBA {

@@ -20,7 +19,6 @@ Q_OBJECT

public: ArchiveInspector(const QString& filename, QWidget* parent = nullptr); - virtual ~ArchiveInspector(); VFile* selectedVFile() const;

@@ -28,7 +26,6 @@ private:

Ui::ArchiveInspector m_ui; LibraryModel m_model; - VDir* m_dir; }; }
M src/platform/qt/LibraryModel.cppsrc/platform/qt/LibraryModel.cpp

@@ -9,40 +9,70 @@ #include <mgba-util/vfs.h>

using namespace QGBA; -LibraryModel::LibraryModel(QObject* parent) +Q_DECLARE_METATYPE(mLibraryEntry); + +LibraryModel::LibraryModel(const QString& path, QObject* parent) : QAbstractItemModel(parent) { - mLibraryInit(&m_library); + if (!path.isNull()) { + m_library = mLibraryLoad(path.toUtf8().constData()); + } else { + m_library = mLibraryCreateEmpty(); + } + memset(&m_constraints, 0, sizeof(m_constraints)); + m_constraints.platform = PLATFORM_NONE; } LibraryModel::~LibraryModel() { - mLibraryDeinit(&m_library); + clearConstraints(); + mLibraryDestroy(m_library); } -void LibraryModel::loadDirectory(VDir* dir) { - mLibraryLoadDirectory(&m_library, dir); +void LibraryModel::loadDirectory(const QString& path) { + beginResetModel(); + mLibraryLoadDirectory(m_library, path.toUtf8().constData()); + endResetModel(); } -const mLibraryEntry* LibraryModel::entryAt(int row) const { - if ((unsigned) row < mLibraryListingSize(&m_library.listing)) { - return mLibraryListingGetConstPointer(&m_library.listing, row); +bool LibraryModel::entryAt(int row, mLibraryEntry* out) const { + mLibraryListing entries; + mLibraryListingInit(&entries, 0); + if (!mLibraryGetEntries(m_library, &entries, 1, row, &m_constraints)) { + mLibraryListingDeinit(&entries); + return false; } - return nullptr; + *out = *mLibraryListingGetPointer(&entries, 0); + mLibraryListingDeinit(&entries); + return true; +} + +VFile* LibraryModel::openVFile(const QModelIndex& index) const { + mLibraryEntry entry; + if (!entryAt(index.row(), &entry)) { + return nullptr; + } + return mLibraryOpenVFile(m_library, &entry); } QVariant LibraryModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } + mLibraryEntry entry; + if (!entryAt(index.row(), &entry)) { + return QVariant(); + } + if (role == Qt::UserRole) { + return QVariant::fromValue(entry); + } if (role != Qt::DisplayRole) { return QVariant(); } - const mLibraryEntry* entry = mLibraryListingGetConstPointer(&m_library.listing, index.row()); switch (index.column()) { case 0: - return entry->filename; + return entry.filename; case 1: - return (unsigned long long) entry->filesize; + return (unsigned long long) entry.filesize; } return QVariant(); }

@@ -84,5 +114,25 @@ int LibraryModel::rowCount(const QModelIndex& parent) const {

if (parent.isValid()) { return 0; } - return mLibraryListingSize(&m_library.listing); + return mLibraryCount(m_library, &m_constraints); +} + +void LibraryModel::constrainBase(const QString& path) { + if (m_constraints.base) { + free(const_cast<char*>(m_constraints.base)); + } + m_constraints.base = strdup(path.toUtf8().constData()); +} + +void LibraryModel::clearConstraints() { + if (m_constraints.base) { + free(const_cast<char*>(m_constraints.base)); + } + if (m_constraints.filename) { + free(const_cast<char*>(m_constraints.filename)); + } + if (m_constraints.title) { + free(const_cast<char*>(m_constraints.title)); + } + memset(&m_constraints, 0, sizeof(m_constraints)); }
M src/platform/qt/LibraryModel.hsrc/platform/qt/LibraryModel.h

@@ -11,6 +11,7 @@

#include <mgba/core/library.h> struct VDir; +struct VFile; namespace QGBA {

@@ -18,12 +19,13 @@ class LibraryModel : public QAbstractItemModel {

Q_OBJECT public: - LibraryModel(QObject* parent = nullptr); + LibraryModel(const QString& path, QObject* parent = nullptr); virtual ~LibraryModel(); - void loadDirectory(VDir* dir); + void loadDirectory(const QString& path); - const mLibraryEntry* entryAt(int row) const; + bool entryAt(int row, mLibraryEntry* out) const; + VFile* openVFile(const QModelIndex& index) const; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

@@ -34,9 +36,13 @@

virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; -private: - mLibrary m_library; +public slots: + void constrainBase(const QString& path); + void clearConstraints(); +private: + mLibrary* m_library; + mLibraryEntry m_constraints; }; }