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:
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 ¤t = 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 ®, const bool isLast)
+void Transaction::registerScript(const HostTicket ®, 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> ¤t = 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) {