reapack

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

commit 2ca608ac2ca23585d3f03bff9cf5d98d3713293d
parent 66e4371564af3b7afae1f32b8799621a8cd5b4f9
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Tue,  1 Dec 2015 21:37:20 -0500

prepare the code for configurable repositories

Diffstat:
MTupfile | 1+
Asrc/config.cpp | 16++++++++++++++++
Asrc/config.hpp | 39+++++++++++++++++++++++++++++++++++++++
Msrc/main.cpp | 2+-
Msrc/package.cpp | 14+++++++++-----
Msrc/package.hpp | 34+++-------------------------------
Asrc/path.cpp | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/path.hpp | 35+++++++++++++++++++++++++++++++++++
Msrc/reapack.cpp | 75++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/reapack.hpp | 13+++++++++----
Atest/helper/io.cpp | 11+++++++++++
Atest/helper/io.hpp | 10++++++++++
Mtest/package.cpp | 51+++++++++++++++++++--------------------------------
Atest/path.cpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 358 insertions(+), 86 deletions(-)

diff --git a/Tupfile b/Tupfile @@ -28,4 +28,5 @@ WDLSOURCE += $(TINYXML)/tinyxmlparser.cpp $(TINYXML)/tinyxmlerror.cpp : build/*.o |> !link $(SOFLAGS) |> bin/reaper_reapack.dylib : foreach test/*.cpp |> !build -Isrc |> build/test/%B.o +: test/helper/*.cpp |> !build -Isrc |> build/test/helper_%B.o : build/*.o build/test/*.o |> !link |> bin/test diff --git a/src/config.cpp b/src/config.cpp @@ -0,0 +1,16 @@ +#include "config.hpp" + +#include "path.hpp" + +Config::Config() +{ +} + +void Config::read(const Path &path) +{ + m_path = path.cjoin(); +} + +void Config::write() const +{ +} diff --git a/src/config.hpp b/src/config.hpp @@ -0,0 +1,39 @@ +#ifndef REAPACK_CONFIG_HPP +#define REAPACK_CONFIG_HPP + +#include <string> +#include <vector> + +struct Repository { + Repository(const std::string &name, const char *url) + : m_name(name), m_url(url) + {} + + const std::string &name() const { return m_name; } + const char *url() const { return m_url; } + +private: + const std::string &m_name; + const char *m_url; +}; + +typedef std::vector<Repository> RepositoryList; + +class Path; + +class Config { +public: + Config(); + + void read(const Path &); + void write() const; + + const RepositoryList &repositories() const { return m_repositories; } + +private: + const char *m_path; + + RepositoryList m_repositories; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp @@ -22,7 +22,7 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( reapack.init(instance, rec); reapack.setupAction("REAPACKSYNC", "ReaPack: Synchronize Packages", - &reapack.action, std::bind(&ReaPack::synchronize, reapack)); + &reapack.action, std::bind(&ReaPack::synchronizeAll, reapack)); rec->Register("hookcommand", (void *)commandHook); diff --git a/src/package.cpp b/src/package.cpp @@ -41,7 +41,7 @@ VersionPtr Package::lastVersion() const return *prev(m_versions.end()); } -InstallLocation Package::targetLocation() const +Path Package::targetLocation() const { switch(m_type) { case ScriptType: @@ -51,13 +51,17 @@ InstallLocation Package::targetLocation() const } } -InstallLocation Package::scriptLocation() const +Path Package::scriptLocation() const { // TODO: use actual database name instead of hard-coded "ReaScripts" - InstallLocation loc("/Scripts/ReaScripts", name()); + Path path; + path.append("Scripts"); + path.append("ReaScripts"); if(m_category) - loc.appendDir("/" + category()->name()); + path.append(category()->name()); - return loc; + path.append(m_name); + + return path; } diff --git a/src/package.hpp b/src/package.hpp @@ -1,6 +1,7 @@ #ifndef REAPACK_PACKAGE_HPP #define REAPACK_PACKAGE_HPP +#include "path.hpp" #include "version.hpp" class Package; @@ -8,35 +9,6 @@ typedef std::shared_ptr<Package> PackagePtr; class Category; -class InstallLocation { -public: - InstallLocation(const std::string &d, const std::string &f) - : m_directory(d), m_filename(f) - {} - - void prependDir(const std::string &p) { m_directory.insert(0, p); } - void appendDir(const std::string &p) { m_directory.append(p); } - - const std::string directory() const { return m_directory; } - const std::string filename() const { return m_filename; } - - std::string fullPath() const { return m_directory + "/" + m_filename; } - - bool operator==(const InstallLocation &o) const - { - return m_directory == o.directory() && m_filename == o.filename(); - } - - bool operator!=(const InstallLocation &o) const - { - return !(*this == o); - } - -private: - std::string m_directory; - std::string m_filename; -}; - class Package { public: enum Type { @@ -59,10 +31,10 @@ public: VersionPtr version(const int i) const; VersionPtr lastVersion() const; - InstallLocation targetLocation() const; + Path targetLocation() const; private: - InstallLocation scriptLocation() const; + Path scriptLocation() const; Category *m_category; Type m_type; diff --git a/src/path.cpp b/src/path.cpp @@ -0,0 +1,66 @@ +#include "path.hpp" + +using namespace std; + +#ifndef _WIN32 +static const char SEPARATOR = '/'; +#else +static const char SEPARATOR = '\\'; +#endif + +void Path::prepend(const string &part) +{ + m_parts.push_front(part); +} + +void Path::append(const string &part) +{ + m_parts.push_back(part); +} + +string Path::join(const bool skipLast) const +{ + string path; + + auto end = m_parts.end(); + + if(skipLast) + end--; + + for(auto it = m_parts.begin(); it != end; it++) { + const string &part = *it; + + if(!path.empty()) + path.insert(path.end(), SEPARATOR); + + path.append(part); + } + + return path; +} + +bool Path::operator==(const Path &o) const +{ + return m_parts == o.m_parts; +} + +bool Path::operator!=(const Path &o) const +{ + return !(*this == o); +} + +Path Path::operator+(const string &part) const +{ + Path path(*this); + path.append(part); + + return path; +} + +Path Path::operator+(const Path &o) const +{ + Path path(*this); + path.m_parts.insert(path.m_parts.end(), o.m_parts.begin(), o.m_parts.end()); + + return path; +} diff --git a/src/path.hpp b/src/path.hpp @@ -0,0 +1,35 @@ +#ifndef REAPACK_PATH_HPP +#define REAPACK_PATH_HPP + +#include <string> +#include <list> + +class Path { +public: + void prepend(const std::string &part); + void append(const std::string &part); + + bool empty() const { return m_parts.empty(); } + int size() const { return m_parts.size(); } + + std::string dirname() const { return join(true); } + const char *cdirname() const { return dirname().c_str(); } + + const std::string &basename() const { return m_parts.back(); } + const char *cbasename() const { return basename().c_str(); } + + std::string join() const { return join(false); } + const char *cjoin() const { return join().c_str(); } + + bool operator==(const Path &) const; + bool operator!=(const Path &) const; + Path operator+(const std::string &) const; + Path operator+(const Path &) const; + +private: + std::string join(const bool) const; + + std::list<std::string> m_parts; +}; + +#endif diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -13,7 +13,13 @@ void ReaPack::init(REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec) m_instance = instance; m_rec = rec; m_mainHandle = GetMainHwnd(); - m_resourcePath = GetResourcePath(); + m_resourcePath.append(GetResourcePath()); + + m_config.read(m_resourcePath + "reapack.ini"); + + m_dbPath = m_resourcePath + "ReaPack"; + + RecursiveCreateDirectory(m_dbPath.cjoin(), 0); } void ReaPack::setupAction(const char *name, const char *desc, @@ -36,17 +42,62 @@ bool ReaPack::execActions(const int id, const int) return true; } -void ReaPack::synchronize() +void ReaPack::synchronizeAll() { - try { - m_database = Database::load("/Users/cfillion/Programs/reapack/reapack.xml"); + RepositoryList repos = m_config.repositories(); - for(PackagePtr pkg : m_database->packages()) { - installPackage(pkg); + if(repos.empty()) { + ShowMessageBox("No repository configured, nothing to do!", "ReaPack", 0); + return; + } + + for(const Repository &repo : repos) { + try { + synchronize(repo); + } + catch(const reapack_error &e) { + ShowMessageBox(e.what(), repo.name().c_str(), 0); } } - catch(const reapack_error &e) { - ShowMessageBox(e.what(), "Database Error", 0); +} + +void ReaPack::synchronize(const Repository &repo) +{ + m_downloadQueue.push(repo.url(), [=](const int status, const string &contents) { + if(status != 200) + return; + + const Path path = m_dbPath + (repo.name() + ".xml"); + + ofstream file(path.join()); + if(file.bad()) { + ShowMessageBox(strerror(errno), repo.name().c_str(), 0); + return; + } + + file << contents; + file.close(); + + synchronize(Database::load(path.cjoin())); + }); +} + +void ReaPack::synchronize(DatabasePtr database) +{ + if(database->packages().empty()) { + ShowMessageBox("The package database is empty, nothing to do!", + "ReaPack", 0); + + return; + } + + for(PackagePtr pkg : database->packages()) { + try { + installPackage(pkg); + } + catch(const reapack_error &e) { + ShowMessageBox(e.what(), "Package Error", 0); + } } } @@ -60,12 +111,10 @@ void ReaPack::installPackage(PackagePtr pkg) return; } - InstallLocation loc = pkg->targetLocation(); - loc.prependDir(m_resourcePath); - - RecursiveCreateDirectory(loc.directory().c_str(), 0); + const Path path = m_resourcePath + pkg->targetLocation(); + RecursiveCreateDirectory(path.cdirname(), 0); - ofstream file(loc.fullPath()); + ofstream file(path.join()); if(file.bad()) { ShowMessageBox(strerror(errno), pkg->name().c_str(), 0); return; diff --git a/src/reapack.hpp b/src/reapack.hpp @@ -4,6 +4,7 @@ #include <functional> #include <map> +#include "config.hpp" #include "database.hpp" #include "download.hpp" @@ -21,18 +22,22 @@ public: gaccel_register_t *action, ActionCallback callback); bool execActions(const int id, const int); - void synchronize(); - void installPackage(PackagePtr pkg); + void synchronizeAll(); + void synchronize(const Repository &); + void synchronize(DatabasePtr); + void installPackage(PackagePtr); private: std::map<int, ActionCallback> m_actions; - DatabasePtr m_database; + + Config m_config; DownloadQueue m_downloadQueue; REAPER_PLUGIN_HINSTANCE m_instance; reaper_plugin_info_t *m_rec; HWND m_mainHandle; - const char *m_resourcePath; + Path m_resourcePath; + Path m_dbPath; }; #endif diff --git a/test/helper/io.cpp b/test/helper/io.cpp @@ -0,0 +1,11 @@ +#include "io.hpp" + +#include <path.hpp> + +using namespace std; + +ostream &operator<<(ostream &os, const Path &path) +{ + os << "\"" + path.join() + '"'; + return os; +} diff --git a/test/helper/io.hpp b/test/helper/io.hpp @@ -0,0 +1,10 @@ +#ifndef REAPACK_TEST_HELPER_IO_HPP +#define REAPACK_TEST_HELPER_IO_HPP + +#include <ostream> + +class Path; + +std::ostream &operator<<(std::ostream &, const Path &); + +#endif diff --git a/test/package.cpp b/test/package.cpp @@ -1,5 +1,7 @@ #include <catch.hpp> +#include "helper/io.hpp" + #include <database.hpp> #include <errors.hpp> #include <package.hpp> @@ -50,32 +52,6 @@ TEST_CASE("drop empty version", M) { REQUIRE(pack.versions().empty()); } -TEST_CASE("different install location", M) { - const InstallLocation a{"/hello", "/world"}; - const InstallLocation b{"/chunky", "/bacon"}; - - REQUIRE_FALSE(a == b); -} - -TEST_CASE("set install location prefix", M) { - InstallLocation loc{"/hello", "world"}; - - REQUIRE(loc.directory() == "/hello"); - REQUIRE(loc.filename() == "world"); - REQUIRE(loc.fullPath() == "/hello/world"); - - loc.prependDir("/root"); - - REQUIRE(loc.directory() == "/root/hello"); - REQUIRE(loc.fullPath() == "/root/hello/world"); - - loc.appendDir("/to"); - - REQUIRE(loc.directory() == "/root/hello/to"); - REQUIRE(loc.filename() == "world"); - REQUIRE(loc.fullPath() == "/root/hello/to/world"); -} - TEST_CASE("unknown target location", M) { Package pack(Package::UnknownType, "a"); @@ -94,15 +70,26 @@ TEST_CASE("script target location", M) { Package pack(Package::ScriptType, "file.name"); pack.setCategory(&cat); - const InstallLocation loc = pack.targetLocation(); - REQUIRE(loc == - InstallLocation("/Scripts/ReaScripts/Category Name", "file.name")); + const Path path = pack.targetLocation(); + + Path expected; + expected.append("Scripts"); + expected.append("ReaScripts"); + expected.append("Category Name"); + expected.append("file.name"); + + REQUIRE(path == expected); } TEST_CASE("script target location without category", M) { Package pack(Package::ScriptType, "file.name"); - const InstallLocation loc = pack.targetLocation(); - REQUIRE(loc == - InstallLocation("/Scripts/ReaScripts", "file.name")); + const Path path = pack.targetLocation(); + + Path expected; + expected.append("Scripts"); + expected.append("ReaScripts"); + expected.append("file.name"); + + REQUIRE(path == expected); } diff --git a/test/path.cpp b/test/path.cpp @@ -0,0 +1,77 @@ +#include <catch.hpp> + +#include "helper/io.hpp" + +#include <path.hpp> + +using namespace std; + +static const char *M = "[path]"; + +TEST_CASE("compare paths", M) { + Path a; + a.append("hello"); + a.append("world"); + + Path b; + b.append("chunky"); + b.append("bacon"); + + REQUIRE_FALSE(a == b); + REQUIRE(a != b); + + REQUIRE(a == a); + REQUIRE_FALSE(a != a); +} + +TEST_CASE("prepend and append path components", M) { + Path path; + REQUIRE(path.empty()); + REQUIRE(path.size() == 0); + REQUIRE(path.join() == string()); + REQUIRE(path.dirname() == string()); + REQUIRE(path.basename() == string()); + + path.prepend("world"); + REQUIRE_FALSE(path.empty()); + REQUIRE(path.size() == 1); + REQUIRE(path.join() == "world"); + REQUIRE(path.dirname() == string()); + REQUIRE(path.basename() == "world"); + + path.prepend("hello"); + REQUIRE(path.size() == 2); +#ifndef _WIN32 + REQUIRE(path.join() == "hello/world"); +#else + REQUIRE(path.join() == "hello\\world"); +#endif + REQUIRE(path.dirname() == "hello"); + REQUIRE(path.basename() == "world"); + + path.append("test"); + REQUIRE(path.size() == 3); +#ifndef _WIN32 + REQUIRE(path.join() == "hello/world/test"); + REQUIRE(path.dirname() == "hello/world"); +#else + REQUIRE(path.join() == "hello\\world\\test"); + REQUIRE(path.dirname() == "hello\\world"); +#endif + REQUIRE(path.basename() == "test"); +} + +TEST_CASE("concatenate paths", M) { + Path a; + a.append("hello"); + + Path b; + b.append("world"); + + Path c; + c.append("hello"); + c.append("world"); + + REQUIRE(a + b == c); + REQUIRE(a + "world" == c); +}