#pragma once #include #include #include #include #include #include #include #include #include #include "exception.hpp" #include "services/builder.hpp" #include "services/inotify.hpp" #include "services/logger.hpp" #include "services/event_throttler.hpp" #include "utils/config.hpp" #include "utils/string.hpp" #include "utils/concurrency.hpp" using namespace std::chrono_literals; #define Concat(one, two) one ## two #define _Stringify(expr) #expr #define Stringify(expr) _Stringify(expr) #define DefineModule(ModuleName, ModuleType) struct ModuleName : public ModuleType #define DefineModule2(ModuleName, ModuleType, P2) struct ModuleName : public ModuleType #define DefineModule3(ModuleName, ModuleType, P2, P3) struct ModuleName : public ModuleType #define CastModule(ModuleName) static_cast(this) #define ConstCastModule(ModuleName) static_cast(*this) #define DEFAULT_FORMAT "format" DefineBaseException(ModuleError); DefineChildException(UndefinedFormat, ModuleError); DefineChildException(UndefinedFormatTag, ModuleError); class ModuleFormatter { public: struct Format { std::string value; std::vector tags; std::string fg, bg, ul, ol; int spacing, padding, margin, offset; std::string decorate(Builder *builder, std::string output); }; std::string module_name; std::map> formats; public: explicit ModuleFormatter(std::string module_name) : module_name(module_name) {} void add(std::string name, std::string fallback, std::vector&& tags, std::vector&& whitelist = {}); std::shared_ptr get(std::string format_name); bool has(std::string tag, std::string format_name); bool has(std::string tag); }; namespace modules { void broadcast_module_update(std::string module_name); std::string get_tag_name(std::string tag); struct ModuleInterface { public: virtual ~ModuleInterface(){} virtual std::string name() const = 0; virtual void start() = 0; virtual void stop() = 0; virtual void refresh() = 0; virtual std::string operator()() = 0; virtual bool handle_command(std::string cmd) = 0; }; template class Module : public ModuleInterface { concurrency::Atomic enabled_flag { false }; concurrency::Value cache { "" }; protected: concurrency::SpinLock output_lock; concurrency::SpinLock broadcast_lock; concurrency::SpinLock update_lock; std::mutex sleep_lock; std::condition_variable sleep_handler; std::string name_; std::shared_ptr logger; std::unique_ptr builder; std::unique_ptr broadcast_throttler; std::unique_ptr formatter; std::vector threads; event_throttler::limit_t broadcast_throttler_limit() const { return event_throttler::limit_t(1); } event_throttler::timewindow_t broadcast_throttler_timewindow() const { return event_throttler::timewindow_t(25); } public: Module(std::string name, bool lazy_builder = true) : name_("module/"+ name) , logger(get_logger()) , builder(std::make_unique(lazy_builder)) , broadcast_throttler(std::make_unique(ConstCastModule(ModuleImpl).broadcast_throttler_limit(), ConstCastModule(ModuleImpl).broadcast_throttler_timewindow())) , formatter(std::make_unique(ConstCastModule(ModuleImpl).name())) {} ~Module() { if (this->enabled()) this->stop(); std::lock_guard lck(this->update_lock); { for (auto &&t : this->threads) { if (t.joinable()) t.join(); else this->logger->warning("["+ ConstCastModule(ModuleImpl).name() +"] Runner thread not joinable"); } this->threads.clear(); } log_trace2(this->logger, name()); } std::string name() const { return name_; } void stop() { std::lock_guard lck(this->broadcast_lock); log_trace2(this->logger, name()); this->wakeup(); this->enable(false); } void refresh() { this->cache = CastModule(ModuleImpl)->get_output(); } std::string operator()() { return this->cache(); } bool handle_command(std::string cmd) { return CastModule(ModuleImpl)->handle_command(cmd); } protected: bool enabled() { return this->enabled_flag(); } void enable(bool state) { this->enabled_flag = state; } void broadcast() { std::lock_guard lck(this->broadcast_lock); if (!this->broadcast_throttler->passthrough()) { log_trace2(this->logger, "Throttled broadcast for: "+ this->name_); return; } broadcast_module_update(ConstCastModule(ModuleImpl).name()); } void sleep(std::chrono::duration sleep_duration) { std::unique_lock lck(this->sleep_lock); this->sleep_handler.wait_for(lck, sleep_duration); } void wakeup() { log_trace2(this->logger, "Releasing sleep lock for "+ this->name_); this->sleep_handler.notify_all(); } std::string get_format() { return DEFAULT_FORMAT; } std::string get_output() { std::lock_guard lck(this->output_lock); if (!this->enabled()) { log_trace2(this->logger, ConstCastModule(ModuleImpl).name() +" is disabled"); return ""; } else { log_trace2(this->logger, ConstCastModule(ModuleImpl).name()); } auto format_name = CastModule(ModuleImpl)->get_format(); auto format = this->formatter->get(format_name); int i = 0; bool tag_built = true; for (auto tag : string::split(format->value, ' ')) { bool is_blankspace = tag.empty(); if (tag[0] == '<' && tag[tag.length()-1] == '>') { if (i > 0) this->builder->space(format->spacing); if (!(tag_built = CastModule(ModuleImpl)->build(this->builder.get(), tag)) && i > 0) this->builder->remove_trailing_space(format->spacing); if (tag_built) i++; } else if (is_blankspace && tag_built) { this->builder->node(" "); } else if (!is_blankspace) { this->builder->node(tag); } } return format->decorate(this->builder.get(), this->builder->flush()); } }; template class StaticModule : public Module { using Module::Module; public: void start() { this->enable(true); this->threads.emplace_back(std::thread(&StaticModule::broadcast, this)); } bool build(Builder *builder, std::string tag) { return true; } }; template class TimerModule : public Module { protected: std::chrono::duration interval = 1s; void runner() { while (this->enabled()) { { std::lock_guard lck(this->update_lock); if (CastModule(ModuleImpl)->update()) CastModule(ModuleImpl)->broadcast(); } this->sleep(this->interval); } } public: template TimerModule(std::string name, I const &interval) : Module(name), interval(interval) {} void start() { this->enable(true); this->threads.emplace_back(std::thread(&TimerModule::runner, this)); } }; template class EventModule : public Module { using Module::Module; protected: void runner() { // warmup CastModule(ModuleImpl)->update(); CastModule(ModuleImpl)->broadcast(); while (this->enabled()) { std::lock_guard lck(this->update_lock); if (!CastModule(ModuleImpl)->has_event()) continue; if (!CastModule(ModuleImpl)->update()) continue; CastModule(ModuleImpl)->broadcast(); } } public: void start() { this->enable(true); this->threads.emplace_back(std::thread(&EventModule::runner, this)); } }; template class InotifyModule : public Module { protected: concurrency::SpinLock poll_lock; std::unique_ptr poll_throttler; std::map watch_list; void runner() { // warmup CastModule(ModuleImpl)->on_event(nullptr); CastModule(ModuleImpl)->broadcast(); while (this->enabled()) { try { std::lock_guard lck(this->poll_lock); if (this->poll_throttler->passthrough()) this->poll_events(); } catch (InotifyException &e) { get_logger()->fatal(e.what()); } } } void idle() const { // this->sleep(1s); } void poll_events() { std::vector> watches; try { for (auto &&w : this->watch_list) watches.emplace_back(std::make_unique(w.first, w.second)); } catch (InotifyException &e) { watches.clear(); log_error(e.what()); this->sleep(0.1s); return; } while (this->enabled()) { ConstCastModule(ModuleImpl).idle(); for (auto &&w : watches) { log_trace2(this->logger, "Polling inotify event for watch at "+ (*w)()); if (w->has_event(500 / watches.size())) { std::unique_ptr event = w->get_event(); watches.clear(); std::lock_guard lck(this->update_lock); if (!this->poll_throttler->passthrough()) return; if (CastModule(ModuleImpl)->on_event(event.get())) CastModule(ModuleImpl)->broadcast(); return; } } } } void watch(std::string path, int mask = InotifyEvent::ALL) { log_trace2(this->logger, path); this->watch_list.insert(std::make_pair(path, mask)); } public: explicit InotifyModule(std::string name) : Module(name) , poll_throttler(std::make_unique(event_throttler::limit_t(ThrottleLimit), event_throttler::timewindow_t(ThrottleWindowMs))) {} InotifyModule(std::string name, std::string path, int mask = InotifyEvent::ALL) : Module(name) , poll_throttler(std::make_unique(event_throttler::limit_t(ThrottleLimit), event_throttler::timewindow_t(ThrottleWindowMs))) { this->watch(path, mask); } InotifyModule(std::string name, std::vector paths, int mask = InotifyEvent::ALL) : Module(name) , poll_throttler(std::make_unique(event_throttler::limit_t(ThrottleLimit), event_throttler::timewindow_t(ThrottleWindowMs))) { for (auto &&path : paths) this->watch(path, mask); } ~InotifyModule() { std::lock_guard lck(this->poll_lock); } void start() { this->enable(true); this->threads.emplace_back(std::thread(&InotifyModule::runner, this)); } }; }