#include"Boss/Mod/UnmanagedManager.hpp" #include"Boss/Msg/CommandFail.hpp" #include"Boss/Msg/CommandRequest.hpp" #include"Boss/Msg/CommandResponse.hpp" #include"Boss/Msg/DbResource.hpp" #include"Boss/Msg/Manifestation.hpp" #include"Boss/Msg/ManifestCommand.hpp" #include"Boss/Msg/ProvideUnmanagement.hpp" #include"Boss/Msg/ProvideStatus.hpp" #include"Boss/Msg/SolicitStatus.hpp" #include"Boss/Msg/SolicitUnmanagement.hpp" #include"Boss/concurrent.hpp" #include"Ev/Io.hpp" #include"Ev/foreach.hpp" #include"Ln/NodeId.hpp" #include"S/Bus.hpp" #include"Sqlite3.hpp" #include"Util/make_unique.hpp" #include #include #include #include #include namespace { /* Splits a comma-separated string of tags into multiple tags. */ std::set split_tags(std::string const& tagspec) { auto rv = std::set(); auto is = std::istringstream(tagspec); while (is) { auto str = std::string(); std::getline(is, str, ','); if (str != "") rv.insert(str); } return rv; } /* Combines multiple tags into a single tagspec. */ std::string combine_tags(std::set const& tags) { auto os = std::ostringstream(); auto first = true; for (auto const& tag : tags) { if (first) first = false; else os << ", "; os << tag; } return os.str(); } } namespace Boss { namespace Mod { class UnmanagedManager::Impl { private: S::Bus& bus; Sqlite3::Db db; bool soliciting; std::map< std::string, std::function(Ln::NodeId const&, bool)> > tag_informs; void start() { soliciting = false; tag_informs.clear(); /* Initialization and solicitation. */ bus.subscribe([this](Msg::DbResource const& m) { db = m.db; return initialize() + solicit(); }); bus.subscribe([this](Msg::ProvideUnmanagement const& m) { if (!soliciting) return Ev::lift(); tag_informs[m.tag] = m.inform; return initial_inform(m.tag); }); /* Command handling. */ bus.subscribe([this](Msg::Manifestation const& m) { return bus.raise(Msg::ManifestCommand{ "clboss-unmanage", "nodeid tags", "Specify that a {nodeid} will not be fully managed by " "CLBOSS, disabling the management of {tags}.", false }); }); bus.subscribe([this](Msg::CommandRequest const& m) { if (m.command != "clboss-unmanage") return Ev::lift(); return unmanage(m.id, m.params); }); /* Status report. */ bus.subscribe([this](Msg::SolicitStatus const& _) { return db.transact().then([this](Sqlite3::Tx tx) { auto out = Json::Out(); auto obj = out.start_object(); obj.field( "comment" , "Use 'clboss-unmanage' to disable management of " "some nodes." ); auto nodes = std::vector(); auto fetch = tx.query(R"QRY( SELECT nodeid FROM "UnmanagedManager_nodes" ; )QRY") .execute() ; for (auto& r : fetch) nodes.push_back(Ln::NodeId(r.get(0))); for (auto const& n : nodes) { auto tags = get_tags(tx, n); /* Do not display nodes that are fully managed. */ if (tags.empty()) continue; obj.field( std::string(n) , combine_tags(tags) ); } obj.end_object(); tx.commit(); return bus.raise(Msg::ProvideStatus{ "unmanaged", std::move(out) }); }); }); } Ev::Io initialize() { return db.transact().then([](Sqlite3::Tx tx) { tx.query_execute(R"QRY( CREATE TABLE IF NOT EXISTS "UnmanagedManager_nodes" ( nodedbid INTEGER PRIMARY KEY NOT NULL , nodeid TEXT UNIQUE NOT NULL ); CREATE TABLE IF NOT EXISTS "UnmanagedManager_tags" ( nodedbid INTEGER NOT NULL , tag TEXT NOT NULL , FOREIGN KEY(nodedbid) REFERENCES "UnmanagedManager_nodes" (nodedbid) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS "UnmanagedManager_tagsidx" ON "UnmanagedManager_tags" (nodedbid, tag); CREATE INDEX IF NOT EXISTS "UnmanagedManager_tags_tagidx" ON "UnmanagedManager_tags" (tag); )QRY"); tx.commit(); return Ev::lift(); }); } Ev::Io solicit() { return Ev::lift().then([this]() { soliciting = true; return bus.raise(Msg::SolicitUnmanagement{}); }).then([this]() { soliciting = false; return Ev::lift(); }); } /* Call the inform function for this tag during initial solicitation. */ Ev::Io initial_inform(std::string const& tag) { return db.transact().then([this, tag](Sqlite3::Tx tx) { auto nodes = std::vector(); auto fetch = tx.query(R"QRY( SELECT nodeid FROM "UnmanagedManager_nodes" NATURAL JOIN "UnmanagedManager_tags" WHERE tag = :tag ; )QRY") .bind(":tag", tag) .execute() ; for (auto& r : fetch) nodes.push_back(Ln::NodeId(r.get(0))); tx.commit(); auto inform = tag_informs[tag]; auto f = [inform](Ln::NodeId const& node) { return inform(node, true); }; return Ev::foreach(std::move(f), std::move(nodes)); }); } Ev::Io unmanage(Ln::CommandId id, Jsmn::Object const& params) { return db.transact().then([this, params](Sqlite3::Tx tx) { auto nodeid = Ln::NodeId(); auto new_tags = std::set(); parse_params(nodeid, new_tags, params); /* Get tags. */ auto old_tags = get_tags(tx, nodeid); auto act = Ev::lift(); /* For every old tag that is not in new tag, remove. */ for (auto const& tag : old_tags) { auto it = new_tags.find(tag); if (it != new_tags.end()) continue; act += Boss::concurrent(tag_informs[tag](nodeid, false)); } /* For every new tag that is not in old tag, add. */ for (auto const& tag : new_tags) { auto it = old_tags.find(tag); if (it != old_tags.end()) continue; act += Boss::concurrent(tag_informs[tag](nodeid, true)); } /* Update tags. */ set_tags(tx, nodeid, new_tags); tx.commit(); return act; }).then([this, id]() { return bus.raise(Msg::CommandResponse{ id, Json::Out::empty_object() }); }).catching([this, id](std::exception const& e) { return bus.raise(Msg::CommandFail{ id, -32602, std::string("Parameter error: ") + e.what(), Json::Out::empty_object() }); }); } void parse_params( Ln::NodeId& nodeid , std::set& tags , Jsmn::Object const& params ) { auto nodeid_j = Jsmn::Object(); auto tags_j = Jsmn::Object(); if (params.is_array()) { nodeid_j = params[0]; tags_j = params[1]; } else { nodeid_j = params["nodeid"]; tags_j = params["tags"]; } nodeid = Ln::NodeId(std::string(nodeid_j)); tags = split_tags(std::string(tags_j)); for (auto const& tag : tags) { auto it = tag_informs.find(tag); if (it == tag_informs.end()) throw Util::BacktraceException( std::string("Unknown tag: ") + tag ); } } /* Database access. */ std::uint64_t get_nodedbid( Sqlite3::Tx& tx , Ln::NodeId const& nodeid ) { auto fetch = tx.query(R"QRY( SELECT nodedbid FROM "UnmanagedManager_nodes" WHERE nodeid = :nodeid ; )QRY") .bind(":nodeid", std::string(nodeid)) .execute() ; for (auto& r : fetch) return r.get(0); /* Not found. */ tx.query(R"QRY( INSERT OR IGNORE INTO "UnmanagedManager_nodes" VALUES( NULL , :nodeid ); )QRY") .bind(":nodeid", std::string(nodeid)) .execute() ; /* Redo. */ return get_nodedbid(tx, nodeid); } std::set get_tags( Sqlite3::Tx& tx , Ln::NodeId const& nodeid ) { auto nodedbid = get_nodedbid(tx, nodeid); auto fetch = tx.query(R"QRY( SELECT tag FROM "UnmanagedManager_tags" WHERE nodedbid = :nodedbid ; )QRY") .bind(":nodedbid", nodedbid) .execute() ; auto rv = std::set(); for (auto& r : fetch) rv.insert(r.get(0)); return rv; } void set_tags( Sqlite3::Tx& tx , Ln::NodeId const& nodeid , std::set const& tags ) { auto nodedbid = get_nodedbid(tx, nodeid); /* First, erase. */ tx.query(R"QRY( DELETE FROM "UnmanagedManager_tags" WHERE nodedbid = :nodedbid ; )QRY") .bind(":nodedbid", nodedbid) .execute() ; /* Then insert. */ for (auto const& tag : tags) tx.query(R"QRY( INSERT INTO "UnmanagedManager_tags" VALUES( :nodedbid , :tag ); )QRY") .bind(":nodedbid", nodedbid) .bind(":tag", tag) .execute() ; } public: Impl() =delete; Impl(Impl&&) =delete; explicit Impl(S::Bus& bus_) : bus(bus_) { start(); } }; UnmanagedManager::UnmanagedManager(UnmanagedManager&&) =default; UnmanagedManager::~UnmanagedManager() =default; UnmanagedManager::UnmanagedManager(S::Bus& bus) : pimpl(Util::make_unique(bus)) { } }}