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:
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) {}
+}