reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 5d5cd63da19a81718606fad2dd02fb2b3ad55f46
parent fda52195587cb36cc761f058dbcf54a814975037
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Sat, 16 Jan 2016 18:41:21 -0500

store the package registry in a dedicated database

Diffstat:
Mmacosx.tup | 2+-
Msrc/config.cpp | 37+------------------------------------
Msrc/config.hpp | 6------
Msrc/main.cpp | 9++++++++-
Msrc/reapack.cpp | 7++++++-
Msrc/reapack.hpp | 2++
Msrc/registry.cpp | 67++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/registry.hpp | 16++++++++--------
Asrc/sqlite.cpp | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sqlite.hpp | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/registry.cpp | 11-----------
Atest/sqlite.cpp | 27+++++++++++++++++++++++++++
12 files changed, 282 insertions(+), 87 deletions(-)

diff --git a/macosx.tup b/macosx.tup @@ -16,7 +16,7 @@ WDLSOURCE += $(SWELL)/swell.cpp $(SWELL)/swell-ini.cpp $(SWELL)/swell-miscdlg.mm WDLSOURCE += $(SWELL)/swell-gdi.mm $(SWELL)/swell-kb.mm $(SWELL)/swell-menu.mm WDLSOURCE += $(SWELL)/swell-misc.mm $(SWELL)/swell-dlg.mm $(SWELL)/swell-wnd.mm -LDFLAGS := -framework Cocoa -framework Carbon -lcurl +LDFLAGS := -framework Cocoa -framework Carbon -lcurl -lsqlite3 SOFLAGS := -dynamiclib SOTARGET := bin/reaper_reapack_@(OSXARCH).dylib diff --git a/src/config.cpp b/src/config.cpp @@ -35,10 +35,6 @@ static const char *SIZE_KEY = "size"; static const char *REMOTES_GRP = "remotes"; static const char *REMOTE_KEY = "remote"; -static const char *REGISTRY_GRP = "registry"; -static const char *PACK_KEY = "reapack"; -static const char *VER_KEY = "version"; - static string ArrayKey(const string &key, const size_t i) { return key + to_string(i); @@ -47,7 +43,7 @@ static string ArrayKey(const string &key, const size_t i) static const int BUFFER_SIZE = 2083; Config::Config() - : m_remotesIniSize(0), m_registryIniSize(0) + : m_remotesIniSize(0) { } @@ -66,7 +62,6 @@ void Config::read(const Path &path) } readRemotes(); - readRegistry(); restoreSelfRemote(); } @@ -74,7 +69,6 @@ void Config::read(const Path &path) void Config::write() { writeRemotes(); - writeRegistry(); } void Config::restoreSelfRemote() @@ -115,35 +109,6 @@ void Config::writeRemotes() setUInt(REMOTES_GRP, SIZE_KEY, m_remotesIniSize = i); } -void Config::readRegistry() -{ - m_registryIniSize = getUInt(REGISTRY_GRP, SIZE_KEY); - - for(size_t i = 0; i < m_registryIniSize; i++) { - const string pack = getString(REGISTRY_GRP, ArrayKey(PACK_KEY, i)); - const string ver = getString(REGISTRY_GRP, ArrayKey(VER_KEY, i)); - - if(!pack.empty() && !ver.empty()) - m_registry.push(pack, ver); - } -} - -void Config::writeRegistry() -{ - size_t i = 0; - m_registryIniSize = max(m_registry.size(), m_registryIniSize); - - for(auto it = m_registry.begin(); it != m_registry.end(); it++, i++) { - setString(REGISTRY_GRP, ArrayKey(PACK_KEY, i), it->first); - setString(REGISTRY_GRP, ArrayKey(VER_KEY, i), it->second); - } - - cleanupArray(REGISTRY_GRP, PACK_KEY, i, m_registryIniSize); - cleanupArray(REGISTRY_GRP, VER_KEY, i, m_registryIniSize); - - setUInt(REGISTRY_GRP, SIZE_KEY, m_registryIniSize = i); -} - string Config::getString(const char *group, const string &key) const { char buffer[BUFFER_SIZE]; diff --git a/src/config.hpp b/src/config.hpp @@ -33,7 +33,6 @@ public: void write(); RemoteList *remotes() { return &m_remotes; } - Registry *registry() { return &m_registry; } private: std::string getString(const char *, const std::string &) const; @@ -53,11 +52,6 @@ private: void writeRemotes(); RemoteList m_remotes; size_t m_remotesIniSize; - - void readRegistry(); - void writeRegistry(); - Registry m_registry; - size_t m_registryIniSize; }; #endif diff --git a/src/main.cpp b/src/main.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "errors.hpp" #include "menu.hpp" #include "reapack.hpp" @@ -61,7 +62,13 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( if(REAPERAPI_LoadAPI(rec->GetFunc) > 0) return 0; - reapack.init(instance, rec); + try { + reapack.init(instance, rec); + } + catch(const reapack_error &e) { + ShowMessageBox(e.what(), "ReaPack Initialization Failure", 0); + return 0; + } reapack.setupAction("REAPACK_SYNC", "ReaPack: Synchronize Packages", &reapack.syncAction, bind(&ReaPack::synchronize, reapack)); diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -42,6 +42,9 @@ void ReaPack::init(REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec) m_config = new Config; m_config->read(m_resourcePath + "reapack.ini"); + const Path registryPath = m_resourcePath + "ReaPack" + "registry.db"; + m_registry = new Registry(registryPath.join()); + m_progress = Dialog::Create<Progress>(m_instance, m_mainWindow); m_manager = Dialog::Create<Manager>(m_instance, m_mainWindow, this); @@ -55,6 +58,8 @@ void ReaPack::cleanup() m_config->write(); delete m_config; + delete m_registry; + Dialog::Destroy(m_progress); Dialog::Destroy(m_manager); @@ -191,7 +196,7 @@ Transaction *ReaPack::createTransaction() if(m_transaction) return nullptr; - m_transaction = new Transaction(m_config->registry(), m_resourcePath); + m_transaction = new Transaction(m_registry, m_resourcePath); m_progress->setTransaction(m_transaction); m_progress->show(); diff --git a/src/reapack.hpp b/src/reapack.hpp @@ -30,6 +30,7 @@ typedef std::function<void()> ActionCallback; class Config; class Manager; class Progress; +class Registry; class Transaction; class ReaPack { @@ -58,6 +59,7 @@ private: std::map<int, ActionCallback> m_actions; Config *m_config; + Registry *m_registry; Transaction *m_transaction; Progress *m_progress; Manager *m_manager; diff --git a/src/registry.cpp b/src/registry.cpp @@ -25,6 +25,30 @@ using namespace std; +Registry::Registry(const std::string &filename) + : m_db(filename) +{ + m_db.query( + "PRAGMA foreign_keys = ON;" + + "CREATE TABLE IF NOT EXISTS entries (" + " package TEXT NOT NULL UNIQUE," + " version INTEGER NOT NULL" + ");" + ); + + m_insertEntry = m_db.prepare( + "INSERT OR REPLACE INTO entries " + "VALUES(?, ?);" + ); + + m_findEntry = m_db.prepare( + "SELECT rowid, version FROM entries " + "WHERE package = ? " + "LIMIT 1" + ); +} + void Registry::push(Version *ver) { Package *pkg = ver->package(); @@ -32,38 +56,30 @@ void Registry::push(Version *ver) if(!pkg) return; - const Path id = pkg->targetPath() + pkg->name(); - push(id.join('/'), ver->name()); -} - -void Registry::push(const string &key, const string &value) -{ - m_map[key] = value; + m_insertEntry->bind(1, hash(pkg).c_str()); + m_insertEntry->bind(2, ver->code()); + m_insertEntry->exec(); } Registry::QueryResult Registry::query(Package *pkg) const { - const Path id = pkg->targetPath() + pkg->name(); - const auto it = m_map.find(id.join('/')); + bool exists = false; + uint64_t version = 0; - if(it == m_map.end()) + m_findEntry->bind(1, hash(pkg).c_str()); + m_findEntry->exec([&] { + version = m_findEntry->uint64Column(1); + exists = true; + return false; + }); + + if(!exists) return {Uninstalled, 0}; Version *lastVer = pkg->lastVersion(); - const Status status = it->second == lastVer->name() - ? UpToDate : UpdateAvailable; - - uint64_t versionCode = 0; - - try { - if(status == UpdateAvailable) - versionCode = Version(it->second).code(); - else - versionCode = lastVer->code(); - } - catch(const reapack_error &) {} - return {status, versionCode}; + const Status status = version == lastVer->code() ? UpToDate : UpdateAvailable; + return {status, version}; } bool Registry::addToREAPER(Version *ver, const Path &root) @@ -84,3 +100,8 @@ bool Registry::addToREAPER(Version *ver, const Path &root) return id > 0; } + +string Registry::hash(Package *pkg) const +{ + return (pkg->targetPath() + pkg->name()).join('\30'); +} diff --git a/src/registry.hpp b/src/registry.hpp @@ -19,16 +19,17 @@ #define REAPACK_REGISTRY_HPP #include <cstdint> -#include <map> #include <string> +#include "sqlite.hpp" + class Package; class Path; class Version; class Registry { public: - typedef std::map<std::string, std::string> Map; + Registry(const std::string &filename = ":memory:"); enum Status { UpToDate, @@ -42,18 +43,17 @@ public: }; void push(Version *); - void push(const std::string &key, const std::string &value); bool addToREAPER(Version *ver, const Path &root); - size_t size() const { return m_map.size(); } QueryResult query(Package *) const; - Map::const_iterator begin() const { return m_map.begin(); } - Map::const_iterator end() const { return m_map.end(); } - private: - Map m_map; + std::string keyOf(Package *) const; + + SQLite::Database m_db; + SQLite::Statement *m_insertEntry; + SQLite::Statement *m_findEntry; }; #endif diff --git a/src/sqlite.cpp b/src/sqlite.cpp @@ -0,0 +1,113 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sqlite.hpp" + +#include "errors.hpp" + +#include <sqlite3.h> + +using namespace std; +using namespace SQLite; + +Database::Database(const string &filename) +{ + if(sqlite3_open(filename.c_str(), &m_db)) { + const auto &error = lastError(); + sqlite3_close(m_db); + + throw error; + } +} + +Database::~Database() +{ + for(Statement *stmt : m_statements) + delete stmt; + + sqlite3_close(m_db); +} + +SQLite::Statement *Database::prepare(const char *sql) +{ + Statement *stmt = new Statement(sql, this); + m_statements.push_back(stmt); + + return stmt; +} + +void Database::query(const char *sql) +{ + if(sqlite3_exec(m_db, sql, nullptr, nullptr, nullptr) != SQLITE_OK) + throw lastError(); +} + +reapack_error Database::lastError() const +{ + return reapack_error(sqlite3_errmsg(m_db)); +} + +Statement::Statement(const char *sql, Database *db) + : m_db(db) +{ + if(sqlite3_prepare_v2(db->m_db, sql, -1, &m_stmt, nullptr) != SQLITE_OK) + throw m_db->lastError(); +} + +Statement::~Statement() +{ + sqlite3_finalize(m_stmt); +} + +void Statement::bind(const int index, const char *text) +{ + if(sqlite3_bind_text(m_stmt, index, text, -1, SQLITE_TRANSIENT)) + throw m_db->lastError(); +} + +void Statement::bind(const int index, const uint64_t integer) +{ + if(sqlite3_bind_int64(m_stmt, index, (sqlite3_int64)integer)) + throw m_db->lastError(); +} + +void Statement::exec() +{ + exec([=] { return false; }); +} + +void Statement::exec(const ExecCallback &callback) +{ + while(true) { + switch(sqlite3_step(m_stmt)) { + case SQLITE_ROW: + if(callback()) + break; + case SQLITE_DONE: + sqlite3_reset(m_stmt); + return; + default: + sqlite3_reset(m_stmt); + throw m_db->lastError(); + }; + } +} + +uint64_t Statement::uint64Column(const int index) const +{ + return (uint64_t)sqlite3_column_int64(m_stmt, index); +} diff --git a/src/sqlite.hpp b/src/sqlite.hpp @@ -0,0 +1,72 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef REAPACK_SQLITE_HPP +#define REAPACK_SQLITE_HPP + +#include <functional> +#include <string> +#include <vector> + +class reapack_error; + +struct sqlite3; +struct sqlite3_stmt; + +namespace SQLite { + class Statement; + + class Database { + public: + Database(const std::string &filename = ":memory:"); + ~Database(); + + Statement *prepare(const char *sql); + void query(const char *sql); + + private: + friend Statement; + + reapack_error lastError() const; + + sqlite3 *m_db; + std::vector<Statement *> m_statements; + }; + + class Statement { + public: + typedef std::function<bool (void)> ExecCallback; + + void bind(const int index, const char *text); + void bind(const int index, const uint64_t integer); + void exec(); + void exec(const ExecCallback &); + + uint64_t uint64Column(const int index) const; + + private: + friend Database; + + Statement(const char *sql, Database *db); + ~Statement(); + + Database *m_db; + sqlite3_stmt *m_stmt; + }; +}; + +#endif diff --git a/test/registry.cpp b/test/registry.cpp @@ -57,14 +57,3 @@ TEST_CASE("bump version", M) { REQUIRE(res2.status == Registry::UpToDate); REQUIRE(res2.versionCode == Version("2.0").code()); } - -TEST_CASE("query invalid registry", M) { - MAKE_PACKAGE - - Registry reg; - reg.push(pkg.targetPath().join(), "bb"); - - // no exception should be thrown - const Registry::QueryResult res = reg.query(&pkg); - REQUIRE(res.versionCode == 0); -} diff --git a/test/sqlite.cpp b/test/sqlite.cpp @@ -0,0 +1,27 @@ +#include <catch.hpp> + +#include <sqlite.hpp> + +#include <errors.hpp> + +using namespace std; + +static const char *M = "[sqlite]"; + +TEST_CASE("open bad sqlite file path", M) { + try { + SQLite::Database db("/a\\"); + FAIL(); + } + catch(const reapack_error &e) {} +} + +TEST_CASE("execute invalid sql", M) { + SQLite::Database db; + + try { + db.query("WHERE"); + FAIL(); + } + catch(const reapack_error &e) {} +}