Core: Back mLibraries with a shared database

This commit is contained in:
Vicki Pfau 2017-01-18 01:36:59 -08:00
parent c11551a1f7
commit ac2097f0b6
6 changed files with 438 additions and 66 deletions

View File

@ -14,27 +14,34 @@ CXX_GUARD_START
#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

View File

@ -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;
};
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);
#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);
}
mLibraryListingDeinit(&library->listing);
}
void mLibraryLoadDirectory(struct mLibrary* library, struct VDir* dir) {
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;
}
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, 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 @@ void mLibraryLoadDirectory(struct mLibrary* library, struct VDir* dir) {
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

View File

@ -7,29 +7,24 @@
#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);
}

View File

@ -10,7 +10,6 @@
#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;
};
}

View File

@ -9,40 +9,70 @@
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));
}

View File

@ -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 @@ public:
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;
};
}