reapack

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

commit 815cb605c745d4d32802fb309b73616ae3452c8d
parent 16ace30d78acfa34dd6ffc49dfaa746fb4b9cfd3
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Thu,  6 Oct 2016 01:50:04 -0400

Merge branch 'multiple-sections'

Diffstat:
Msrc/about.cpp | 33+++++++++++++++++++++++++++------
Msrc/database.hpp | 6+++---
Msrc/index_v1.cpp | 12++++++++++--
Msrc/registry.cpp | 29+++++++++++++++++++++++++----
Msrc/registry.hpp | 3++-
Msrc/source.cpp | 43++++++++++++++++++++++++++++++++++++++++++-
Msrc/source.hpp | 17++++++++++++++---
Msrc/task.cpp | 6++----
Msrc/transaction.cpp | 51++++++++++++++++++++++++++++++++++-----------------
Mtest/index_v1.cpp | 14++++++++++++--
Atest/indexes/v1/ReaPack/cache/explicit_sections.xml | 9+++++++++
Mtest/indexes/v1/ReaPack/cache/valid_index.xml | 2+-
Mtest/registry.cpp | 8++++----
Mtest/source.cpp | 53++++++++++++++++++++++++++++++++++++++++++++++++++---
14 files changed, 235 insertions(+), 51 deletions(-)

diff --git a/src/about.cpp b/src/about.cpp @@ -33,7 +33,7 @@ #include "tabbar.hpp" #include "transaction.hpp" -#include <boost/algorithm/string/replace.hpp> +#include <boost/algorithm/string.hpp> #include <iomanip> #include <sstream> @@ -323,7 +323,7 @@ void AboutIndexDelegate::initInstalledFiles() for(const Registry::File &file : allFiles) { stream << file.path.join(); - if(file.main) + if(file.sections) // is this file registered in the action list? stream << '*'; stream << "\r\n"; } @@ -463,8 +463,8 @@ void AboutPackageDelegate::init(About *dialog) dialog->menu()->addColumn({AUTO_STR("Version"), 142}); - dialog->list()->addColumn({AUTO_STR("File"), 484}); - dialog->list()->addColumn({AUTO_STR("Action List"), 74}); + dialog->list()->addColumn({AUTO_STR("File"), 474}); + dialog->list()->addColumn({AUTO_STR("Action List"), 84}); for(const Version *ver : m_package->versions()) dialog->menu()->addRow({make_autostring(ver->name())}); @@ -479,6 +479,11 @@ void AboutPackageDelegate::init(About *dialog) void AboutPackageDelegate::updateList(const int index) { + static const map<Source::Section, string> sectionMap{ + {Source::MainSection, "Main"}, + {Source::MIDIEditorSection, "MIDI Editor"}, + }; + if(index < 0) return; @@ -491,10 +496,26 @@ void AboutPackageDelegate::updateList(const int index) m_sources = &ver->sources(); for(const Source *src : ver->sources()) { + int sections = src->sections(); string actionList; - if(src->isMain() && src->type() == Package::ScriptType) - actionList = "Yes"; + if(sections && src->type() == Package::ScriptType) { + vector<string> sectionNames; + + for(const auto &pair : sectionMap) { + if(sections & pair.first) { + sectionNames.push_back(pair.second); + sections &= ~pair.first; + } + } + + if(sections) // In case we forgot to add a section to sectionMap! + sectionNames.push_back("Other"); + + actionList = "Yes ("; + actionList += boost::algorithm::join(sectionNames, ", "); + actionList += ')'; + } else actionList = "No"; diff --git a/src/database.hpp b/src/database.hpp @@ -69,6 +69,9 @@ class Statement { public: typedef std::function<bool (void)> ExecCallback; + Statement(const char *sql, const Database *db); + ~Statement(); + void bind(int index, const std::string &text); void bind(int index, int64_t integer); void exec(); @@ -81,9 +84,6 @@ public: private: friend Database; - Statement(const char *sql, const Database *db); - ~Statement(); - const Database *m_db; sqlite3_stmt *m_stmt; }; diff --git a/src/index_v1.cpp b/src/index_v1.cpp @@ -19,6 +19,7 @@ #include "errors.hpp" +#include <sstream> #include <WDL/tinyxml/tinyxml.h> using namespace std; @@ -175,6 +176,9 @@ void LoadSourceV1(TiXmlElement *node, Version *ver) const char *file = node->Attribute("file"); if(!file) file = ""; + const char *main = node->Attribute("main"); + if(!main) main = ""; + const char *url = node->GetText(); if(!url) url = ""; @@ -184,8 +188,12 @@ void LoadSourceV1(TiXmlElement *node, Version *ver) src->setPlatform(platform); src->setTypeOverride(Package::getType(type)); - if(node->Attribute("main")) - src->setMain(true); + int sections = 0; + string section; + istringstream mainStream(main); + while(getline(mainStream, section, '\x20')) + sections |= Source::getSection(section.c_str()); + src->setSections(sections); if(ver->addSource(src)) ptr.release(); diff --git a/src/registry.cpp b/src/registry.cpp @@ -74,8 +74,8 @@ Registry::Registry(const Path &path) void Registry::migrate() { + const Database::Version version{0, 5}; const Database::Version &current = m_db.version(); - const Database::Version version{0, 4}; if(!current) { // new database! @@ -126,6 +126,8 @@ void Registry::migrate() m_db.exec("ALTER TABLE files ADD COLUMN type INTEGER NOT NULL DEFAULT 0;"); case 3: m_db.exec("ALTER TABLE entries ADD COLUMN desc TEXT NOT NULL DEFAULT '';"); + case 4: + convertImplicitSections(); } m_db.setVersion(version); @@ -178,7 +180,7 @@ auto Registry::push(const Version *ver, vector<Path> *conflicts) -> Entry m_insertFile->bind(1, entryId); m_insertFile->bind(2, path.join('/')); - m_insertFile->bind(3, src->isMain()); + m_insertFile->bind(3, src->sections()); m_insertFile->bind(4, src->typeOverride()); try { @@ -259,7 +261,7 @@ auto Registry::getFiles(const Entry &entry) const -> vector<File> m_getFiles->bind(1, entry.id); m_getFiles->exec([&] { File file{m_getFiles->stringColumn(0)}; - file.main = m_getFiles->boolColumn(1); + file.sections = m_getFiles->intColumn(1); file.type = static_cast<Package::Type>(m_getFiles->intColumn(2)); if(!file.type) // < v1.0rc2 @@ -281,7 +283,7 @@ auto Registry::getMainFiles(const Entry &entry) const -> vector<File> vector<File> mainFiles; copy_if(allFiles.begin(), allFiles.end(), - back_inserter(mainFiles), [&](const File &f) { return f.main; }); + back_inserter(mainFiles), [&](const File &f) { return f.sections; }); return mainFiles; } @@ -324,6 +326,25 @@ void Registry::commit() m_db.commit(); } +void Registry::convertImplicitSections() +{ + // convert from v1.0 main=true format to v1.1 flag format + + Statement entries("SELECT id, category FROM entries", &m_db); + entries.exec([&] { + const int id = entries.intColumn(0); + const string &category = entries.stringColumn(1); + const int section = Source::detectSection(category); + + Statement update("UPDATE files SET main = ? WHERE entry = ? AND main != 0", &m_db); + update.bind(1, section); + update.bind(2, id); + update.exec(); + + return true; + }); +} + void Registry::fillEntry(const Statement *stmt, Entry *entry) const { int col = 0; diff --git a/src/registry.hpp b/src/registry.hpp @@ -44,7 +44,7 @@ public: struct File { Path path; - bool main; + int sections; Package::Type type; bool operator<(const File &o) const { return path < o.path; } @@ -66,6 +66,7 @@ public: private: void migrate(); + void convertImplicitSections(); void fillEntry(const Statement *, Entry *) const; Database m_db; diff --git a/src/source.cpp b/src/source.cpp @@ -20,10 +20,37 @@ #include "errors.hpp" #include "index.hpp" +#include <boost/algorithm/string.hpp> + using namespace std; +auto Source::getSection(const char *name) -> Section +{ + if(!strcmp(name, "main")) + return MainSection; + else if(!strcmp(name, "midi_editor")) + return MIDIEditorSection; + else if(!strcmp(name, "true")) + return ImplicitSection; + else + return UnknownSection; +} + +auto Source::detectSection(const string &category) -> Section +{ + // this is for compatibility with v1.0 + + string topcategory = Path(category).first(); + boost::algorithm::to_lower(topcategory); + + if(topcategory == "midi editor") + return MIDIEditorSection; + else + return MainSection; +} + Source::Source(const string &file, const string &url, const Version *ver) - : m_type(Package::UnknownType), m_file(file), m_url(url), m_main(false), + : m_type(Package::UnknownType), m_file(file), m_url(url), m_sections(0), m_version(ver) { if(m_url.empty()) @@ -56,6 +83,20 @@ const string &Source::file() const throw reapack_error("empty source file name and no package"); } +void Source::setSections(int sections) +{ + if(sections == ImplicitSection) { + const Package *pkg = package(); + const Category *cat = pkg ? pkg->category() : nullptr; + if(!cat) + throw reapack_error("cannot resolve implicit section: category is unset"); + + sections = detectSection(cat->name()); + } + + m_sections = sections; +} + string Source::fullName() const { if(!m_version) diff --git a/src/source.hpp b/src/source.hpp @@ -27,6 +27,17 @@ class Version; class Source { public: + enum Section { + UnknownSection = 0, + MainSection = 1<<0, + MIDIEditorSection = 1<<1, + + ImplicitSection = -1, // for compatibility with v1.0 + }; + + static Section getSection(const char *); + static Section detectSection(const std::string &category); + Source(const std::string &file, const std::string &url, const Version * = nullptr); @@ -38,8 +49,8 @@ public: Package::Type type() const; const std::string &file() const; const std::string &url() const { return m_url; } - void setMain(bool main) { m_main = main; } - bool isMain() const { return m_main; } + void setSections(int); + int sections() const { return m_sections; } const Version *version() const { return m_version; } const Package *package() const; @@ -52,7 +63,7 @@ private: Package::Type m_type; std::string m_file; std::string m_url; - bool m_main; + int m_sections; const Version *m_version; }; diff --git a/src/task.cpp b/src/task.cpp @@ -121,8 +121,7 @@ void InstallTask::commit() if(FS::remove(file.path)) tx()->receipt()->addRemoval(file.path); - if(file.main) - tx()->registerFile({false, m_oldEntry, file}); + tx()->registerFile({false, m_oldEntry, file}); } InstallTicket::Type type; @@ -179,8 +178,7 @@ void UninstallTask::commit() else tx()->receipt()->addError({FS::lastError(), file.path.join()}); - if(file.main) - tx()->registerFile({false, m_entry, file}); + tx()->registerFile({false, m_entry, file}); } tx()->registry()->forget(m_entry); diff --git a/src/transaction.cpp b/src/transaction.cpp @@ -25,8 +25,6 @@ #include "remote.hpp" #include "task.hpp" -#include <boost/algorithm/string.hpp> - #include <reaper_plugin_functions.h> using namespace std; @@ -308,26 +306,45 @@ void Transaction::registerQueued() } } -void Transaction::registerScript(const HostTicket &reg, const bool isLast) +void Transaction::registerScript(const HostTicket &reg, const bool isLastCall) { - enum Section { MainSection = 0, MidiEditorSection = 32060 }; + static const map<Source::Section, int> sectionMap{ + {Source::MainSection, 0}, + {Source::MIDIEditorSection, 32060}, + }; - if(!AddRemoveReaScript) - return; // do nothing if REAPER < v5.12 + if(!AddRemoveReaScript || !reg.file.sections) + return; // do nothing if REAPER < v5.12 and skip non-main files - Section section; - string category = Path(reg.entry.category).first(); - boost::algorithm::to_lower(category); + const string &fullPath = Path::prefixRoot(reg.file.path).join(); - if(category == "midi editor") - section = MidiEditorSection; - else - section = MainSection; + vector<int> sections; - const string &fullPath = Path::prefixRoot(reg.file.path).join(); - if(!AddRemoveReaScript(reg.add, section, fullPath.c_str(), isLast) && reg.add) { - m_receipt.addError({"This script could not be registered in REAPER.", - reg.file.path.join()}); + for(const auto &pair : sectionMap) { + if(reg.file.sections & pair.first) + sections.push_back(pair.second); + } + + assert(!sections.empty()); // is a section missing in sectionMap? + + bool enableError = reg.add; + auto it = sections.begin(); + + while(true) { + const int section = *it++; + const bool isLastSection = it == sections.end(); + + int id = AddRemoveReaScript(reg.add, section, fullPath.c_str(), + isLastCall && isLastSection); + + if(!id && enableError) { + m_receipt.addError({"This script could not be registered in REAPER.", + reg.file.path.join()}); + enableError = false; + } + + if(isLastSection) + break; } } diff --git a/test/index_v1.cpp b/test/index_v1.cpp @@ -174,13 +174,13 @@ TEST_CASE("full index", M) { const Source *source1 = ver->source(0); REQUIRE(source1->platform() == Platform::GenericPlatform); REQUIRE(source1->file() == "test.lua"); - REQUIRE(source1->isMain()); + REQUIRE(source1->sections() == Source::MainSection); REQUIRE(source1->url() == "https://google.com/"); const Source *source2 = ver->source(1); REQUIRE(source2->platform() == Platform::GenericPlatform); REQUIRE(source2->file() == "background.png"); - REQUIRE_FALSE(source2->isMain()); + REQUIRE_FALSE(source2->sections() == Source::MainSection); REQUIRE(source2->url() == "http://cfillion.tk/"); } @@ -293,3 +293,13 @@ TEST_CASE("read package description", M) { CHECK(ri->packages().size() == 1); REQUIRE(ri->category(0)->package(0)->description() == "From the New World"); } + +TEST_CASE("read multiple sections", M) { + UseRootPath root(RIPATH); + + IndexPtr ri = Index::load("explicit_sections"); + + CHECK(ri->packages().size() == 1); + REQUIRE(ri->category(0)->package(0)->version(0)->source(0)->sections() + == (Source::MainSection | Source::MIDIEditorSection)); +} diff --git a/test/indexes/v1/ReaPack/cache/explicit_sections.xml b/test/indexes/v1/ReaPack/cache/explicit_sections.xml @@ -0,0 +1,9 @@ +<index version="1"> + <category name="MIDI Editor"> + <reapack name="packname" type="script"> + <version name="1.0"> + <source main=" main midi_editor HELLO ">https://google.com/</source> + </version> + </reapack> + </category> +</index> diff --git a/test/indexes/v1/ReaPack/cache/valid_index.xml b/test/indexes/v1/ReaPack/cache/valid_index.xml @@ -2,7 +2,7 @@ <category name="Category Name"> <reapack name="Hello World.lua" type="script"> <version name="1.0"> - <source platform="all" file="test.lua" main="true">https://google.com/</source> + <source platform="all" file="test.lua" main="main">https://google.com/</source> <source platform="all" file="background.png">http://cfillion.tk/</source> <changelog>Fixed a division by zero error.</changelog> </version> diff --git a/test/registry.cpp b/test/registry.cpp @@ -92,7 +92,7 @@ TEST_CASE("get file list", M) { const vector<Registry::File> &files = reg.getFiles(reg.getEntry(&pkg)); REQUIRE(files.size() == 1); REQUIRE(files[0].path == src->targetPath()); - REQUIRE(files[0].main == false); + REQUIRE(files[0].sections == 0); REQUIRE(files[0].type == pkg.type()); } @@ -170,12 +170,12 @@ TEST_CASE("get main files", M) { REQUIRE((reg.getMainFiles({})).empty()); Source *main1 = new Source({}, "url", &ver); - main1->setMain(true); + main1->setSections(Source::MIDIEditorSection); main1->setTypeOverride(Package::EffectType); ver.addSource(main1); Source *main2 = new Source({}, "url", &ver); // duplicate file ignored - main2->setMain(true); + main2->setSections(Source::MainSection); main2->setTypeOverride(Package::EffectType); ver.addSource(main2); @@ -184,7 +184,7 @@ TEST_CASE("get main files", M) { const vector<Registry::File> &current = reg.getMainFiles(entry); REQUIRE(current.size() == 1); REQUIRE(current[0].path == main1->targetPath()); - REQUIRE(current[0].main == true); + REQUIRE(current[0].sections == Source::MIDIEditorSection); REQUIRE(current[0].type == Package::EffectType); } diff --git a/test/source.cpp b/test/source.cpp @@ -55,12 +55,59 @@ TEST_CASE("empty source file name and no package", M) { } } +TEST_CASE("parse file section", M) { + REQUIRE(-1 == Source::getSection("true")); + REQUIRE(0 == Source::getSection("hello")); + REQUIRE(Source::MainSection == Source::getSection("main")); + REQUIRE(Source::MIDIEditorSection == Source::getSection("midi_editor")); +} + TEST_CASE("main source", M) { Source source("filename", "url"); - REQUIRE_FALSE(source.isMain()); + REQUIRE(source.sections() == 0); + + source.setSections(Source::MainSection | Source::MIDIEditorSection); + REQUIRE(source.sections() == (Source::MainSection | Source::MIDIEditorSection)); +} + +TEST_CASE("implicit source section") { + SECTION("main") { + Category cat("Category Name"); + Package pack(Package::UnknownType, "package name", &cat); + Version ver("1.0", &pack); + + Source source("filename", "url", &ver); + source.setSections(Source::ImplicitSection); + REQUIRE(source.sections() == Source::MainSection); + } + + SECTION("midi editor") { + Category cat("MIDI Editor"); + Package pack(Package::UnknownType, "package name", &cat); + Version ver("1.0", &pack); + + Source source("filename", "url", &ver); + source.setSections(Source::ImplicitSection); + REQUIRE(source.sections() == Source::MIDIEditorSection); + } + + SECTION("no category") { + Source source("filename", "url"); + try { + source.setSections(Source::ImplicitSection); + FAIL(); // should throw (or crash if buggy, but not do nothing) + } + catch(const reapack_error &) {} + } +} + +TEST_CASE("implicit section detection", M) { + REQUIRE(Source::MainSection == Source::detectSection("Hello World")); + REQUIRE(Source::MainSection == Source::detectSection("Hello/World")); + REQUIRE(Source::MainSection == Source::detectSection("Hello/midi editor")); - source.setMain(true); - REQUIRE(source.isMain()); + REQUIRE(Source::MIDIEditorSection == Source::detectSection("midi editor")); + REQUIRE(Source::MIDIEditorSection == Source::detectSection("midi editor/Hello")); } TEST_CASE("empty source url", M) {