From 23e2fbd995e499c8c07590e8a605fab51c4e1ec2 Mon Sep 17 00:00:00 2001 From: nedko Date: Mon, 22 Jun 2026 18:11:31 +0300 Subject: [PATCH] Moved TRMNL stuff to its own file --- Makefile | 1 + TRMNL.cpp | 232 ++++++++++++++++++++++++++++++++++++++++++++ TRMNL.h | 73 ++++++++++++++ config_example.json | 1 + helpers.cpp | 21 +++- helpers.h | 7 ++ main.cpp | 200 ++++++++------------------------------ 7 files changed, 373 insertions(+), 162 deletions(-) create mode 100644 TRMNL.cpp create mode 100644 TRMNL.h diff --git a/Makefile b/Makefile index e4f242f..6e4be76 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ srcs += main.cpp srcs += helpers.cpp +srcs += TRMNL.cpp all: g++ ${srcs} -o server diff --git a/TRMNL.cpp b/TRMNL.cpp new file mode 100644 index 0000000..ef36a9a --- /dev/null +++ b/TRMNL.cpp @@ -0,0 +1,232 @@ +#include "TRMNL.h" + +using std::list; +using std::map; +using std::optional; +using std::string; + +using nlohmann::json; + +TRMNL::TRMNL(const string& id, + const string& api_key, + const string& friendly_id, + int refresh_rate) +: m_id(id), +m_api_key(api_key), +m_friendly_id(friendly_id), +m_refresh_rate(refresh_rate), +m_update_handler() +{} + +const string& TRMNL::id() const +{ + return m_id; +} + +const string& TRMNL::api_key() const +{ + return m_api_key; +} + +const string& TRMNL::friendly_id() const +{ + return m_friendly_id; +} + +int TRMNL::refresh_rate() const +{ + return m_refresh_rate; +} + +void TRMNL::id(const std::string& id) +{ + m_id = id; + if (m_update_handler) + { + m_update_handler(*this); + } +} + +void TRMNL::api_key(const std::string& api_key) +{ + m_api_key = api_key; + if (m_update_handler) + { + m_update_handler(*this); + } +} + +void TRMNL::friendly_id(const std::string& friendly_id) +{ + m_friendly_id = friendly_id; + if (m_update_handler) + { + m_update_handler(*this); + } +} + +void TRMNL::refresh_rate(int refresh_rate) +{ + m_refresh_rate = refresh_rate; + if (m_update_handler) + { + m_update_handler(*this); + } +} + +void TRMNL::set_update_handler(std::function handler) +{ + m_update_handler = handler; +} + +string TRMNL::friendly_from_id(string id) +{ + size_t pos = id.find(':'); + while (pos != string::npos) + { + id.erase(pos, 1); + pos = id.find(':'); + } + + if (id.size() <= 6) + { + return id; + } + else + { + return id.substr(id.size() - 6); + } +} + +void to_json(json& j, const TRMNL& trmnl) +{ + j = json{ + {"ID", trmnl.m_id}, + {"api_key", trmnl.m_api_key}, + {"friendly_id", trmnl.m_friendly_id}, + {"refresh_rate", trmnl.m_refresh_rate}, + }; +} + +void from_json(const json& j, TRMNL& trmnl) +{ + bool updated = false; + try + { + j.at("ID").get_to(trmnl.m_id); + updated = true; + } + catch (const std::exception& e) + {} + + try + { + j.at("api_key").get_to(trmnl.m_api_key); + updated = true; + } + catch (const std::exception& e) + {} + + try + { + j.at("friendly_id").get_to(trmnl.m_friendly_id); + updated = true; + } + catch (const std::exception& e) + {} + + try + { + j.at("refresh_rate").get_to(trmnl.m_refresh_rate); + updated = true; + } + catch (const std::exception& e) + {} + + if (updated && trmnl.m_update_handler) + { + trmnl.m_update_handler(trmnl); + } +} + +void TRMNLContainer::TRMNL_update_handler(const TRMNL& trmnl) +{ + auto it = m_by_id.find(trmnl.m_id); + if (it == m_by_id.end()) + { + return; + } + + // An update is needed only if friendly ID changes + if (0 == m_by_friendly.count(trmnl.m_friendly_id)) + { + m_by_friendly[trmnl.m_friendly_id] = it->second; + } +} + +void TRMNLContainer::add_device(TRMNL trmnl) +{ + if (trmnl.m_id.empty()) + { + return; + } + + trmnl.set_update_handler([this](const TRMNL& trmnl){ TRMNL_update_handler(trmnl); }); + auto it = m_devices.insert(m_devices.end(), trmnl); + + m_by_id[trmnl.m_id] = it; + + if (!trmnl.m_friendly_id.empty()) + { + m_by_friendly[trmnl.m_friendly_id] = it; + } +} + +TRMNL* TRMNLContainer::get_device_by_id(const string& id) +{ + TRMNL* result = nullptr; + + if (0 != m_by_id.count(id)) + { + result = &(*m_by_id[id]); + } + + return result; +} + +TRMNL* TRMNLContainer::get_device_by_friendly(const string& friendly) +{ + TRMNL* result = nullptr; + + if (0 != m_by_friendly.count(friendly)) + { + result = &(*m_by_friendly[friendly]); + } + + return result; +} + +void TRMNLContainer::clear() +{ + m_by_friendly.clear(); + m_by_id.clear(); + m_devices.clear(); +} + +void to_json(json& j, const TRMNLContainer& cont) +{ + j = cont.m_devices; +} + +void from_json(const json& j, TRMNLContainer& cont) +{ + if (!j.is_array()) + { + return; + } + + for (int i = 0; i < j.size(); ++i) + { + cont.add_device(j[i]); + } +} diff --git a/TRMNL.h b/TRMNL.h new file mode 100644 index 0000000..b22e92b --- /dev/null +++ b/TRMNL.h @@ -0,0 +1,73 @@ +#ifndef TRMNL_H_ +#define TRMNL_H_ + +#include +#include +#include +#include +#include + +#include "json.hpp" + +class TRMNL +{ +private: + std::string m_id; + std::string m_api_key; + std::string m_friendly_id; + int m_refresh_rate; + + std::function m_update_handler; + +public: + // Constructor + TRMNL(const std::string& id = "", + const std::string& api_key = "", + const std::string& friendly_id = "", + int refresh_rate = 600); + + // Getters + const std::string& id() const; + const std::string& api_key() const; + const std::string& friendly_id() const; + int refresh_rate() const; + + // Setters + void id(const std::string& id); + void api_key(const std::string& api_key); + void friendly_id(const std::string& friendly_id); + void refresh_rate(int refresh_rate); + + void set_update_handler(std::function handler); + + static std::string friendly_from_id(std::string id); + + friend void to_json(nlohmann::json& j, const TRMNL& trmnl); + friend void from_json(const nlohmann::json& j, TRMNL& trmnl); + friend class TRMNLContainer; +}; + +class TRMNLContainer +{ +private: + std::list m_devices; + std::map::iterator> m_by_id; + std::map::iterator> m_by_friendly; + + void TRMNL_update_handler(const TRMNL& trmnl); + +public: + TRMNLContainer() = default; + + void add_device(TRMNL trmnl); + + TRMNL* get_device_by_id(const std::string& id); + TRMNL* get_device_by_friendly(const std::string& friendly); + + void clear(); + + friend void to_json(nlohmann::json& j, const TRMNLContainer& cont); + friend void from_json(const nlohmann::json& j, TRMNLContainer& cont); +}; + +#endif // TRMNL_H_ diff --git a/config_example.json b/config_example.json index 81e9faf..e97841d 100644 --- a/config_example.json +++ b/config_example.json @@ -5,6 +5,7 @@ // Optional "devices_filename": "devices.json", + "folder_images": "images", "cert_file": "path/to/file", "key_file": "path/to/file" } diff --git a/helpers.cpp b/helpers.cpp index 01d08b4..b91b919 100644 --- a/helpers.cpp +++ b/helpers.cpp @@ -6,6 +6,7 @@ using nlohmann::json; using std::endl; using std::ifstream; +using std::ofstream; using std::istream; using std::ostream; using std::string; @@ -18,7 +19,7 @@ bool read_file_json(json& j, const string& filename, ostream* log) { if (nullptr != log) { - *log << "Could not open file" << endl; + *log << "Could not open file to read - " << filename << endl; } return false; } @@ -45,6 +46,24 @@ bool read_stream_json(json& j, istream& in, ostream* log) return true; } +bool write_file_json(const json& j, const string& filename, ostream* log) +{ + ofstream file(filename); + + if (!file.is_open()) + { + if (nullptr != log) + { + *log << "Could not open file to write - " << filename << endl; + } + return false; + } + + file << j.dump(4) << endl; + + return true; +} + bool json_extract(const json& j, const string& key, string& out) { bool result = false; diff --git a/helpers.h b/helpers.h index cc1fca1..c1d2f68 100644 --- a/helpers.h +++ b/helpers.h @@ -23,6 +23,13 @@ bool read_file_json(nlohmann::json& j, const std::string& filename, std::ostream // Returns - true if read and parse is successful bool read_stream_json(nlohmann::json& j, std::istream& in, std::ostream* log); +// Writes json to supplied filename +// j - input json +// filename - filepath to which to write json +// log - ostream to log human readable errors to - can be nullptr +// Returns - true if write is successful +bool write_file_json(const nlohmann::json& j, const std::string& filename, std::ostream* log); + // JSON Extraction Helpers // out value is modified only if an extraction happened // Returns - whether an extraction happened diff --git a/main.cpp b/main.cpp index 6b24d75..2837c7b 100644 --- a/main.cpp +++ b/main.cpp @@ -12,149 +12,18 @@ #include "json.hpp" #include "helpers.h" +#include "TRMNL.h" using nlohmann::json; using namespace std; -struct device -{ - string id; - string api_key; - string friendly_id; - int refresh_rate; - - // Default constructor - device() - : id(""), - api_key(""), - friendly_id(""), - refresh_rate(600) - {} -}; - -void to_json(json& j, const device& d) -{ - j = json{ - {"ID", d.id}, - {"api_key", d.api_key}, - {"friendly_id", d.friendly_id}, - {"refresh_rate", d.refresh_rate} - }; -} - -bool json_extract(const json& j, const string& key, device& out) -{ - bool result = false; - - if (!key.empty()) - { - if (j.contains(key) && j[key].is_object()) - { - result |= json_extract(j[key], "ID", out.id); - result |= json_extract(j[key], "api_key", out.api_key); - result |= json_extract(j[key], "friendly_id", out.friendly_id); - result |= json_extract(j[key], "refresh_rate", out.refresh_rate); - } - } - else - { - result |= json_extract(j, "ID", out.id); - result |= json_extract(j, "api_key", out.api_key); - result |= json_extract(j, "friendly_id", out.friendly_id); - result |= json_extract(j, "refresh_rate", out.refresh_rate); - } - - return result; -} - -string device_id_to_friendly(string id) -{ - size_t pos = id.find(':'); - while (pos != string::npos) - { - id.erase(pos, 1); - pos = id.find(':'); - } - - if (id.size() <= 6) - { - return id; - } - else - { - return id.substr(id.size() - 6); - } -} - -class DeviceContainer -{ -private: - list m_devices; - map::iterator> m_by_id; - map::iterator> m_by_friendly; - -public: - DeviceContainer() = default; - - void add_device(const device& d) - { - if (d.id.empty()) - { - return; - } - - auto it = m_devices.insert(m_devices.end(), d); - m_by_id.insert(make_pair(it->id, it)); - - if (!it->friendly_id.empty()) - { - m_by_friendly.insert(make_pair(it->friendly_id, it)); - } - } - - const device* get_device_by_id(const string& id) - { - if (0 == m_by_id.count(id)) - { - return nullptr; - } - else - { - return &(*(*(m_by_id.find(id))).second); - } - } - - const device* get_device_by_friendly(const string& friendly) - { - if (0 == m_by_friendly.count(friendly)) - { - return nullptr; - } - else - { - return &(*(*(m_by_friendly.find(friendly))).second); - } - } - - void set_device_friendly(const string& id, const string& friendly) - { - if (0 == m_by_id.count(id)) - { - return; - } - - auto it = m_by_id[id]; - it->friendly_id = friendly; - m_by_friendly.insert(make_pair(it->friendly_id, it)); - } -}; - int main(int argc, char **argv) { string config_filename = "config.json"; string devices_filename = "devices.json"; string host = ""; + string folder_images = "images"; string cert_file = ""; string key_file = ""; uint16_t port = 0; @@ -163,8 +32,7 @@ int main(int argc, char **argv) bool ok; json cfg; json devs; - vector devices; - DeviceContainer container; + TRMNLContainer container; if (argc > 2) { @@ -210,30 +78,29 @@ int main(int argc, char **argv) return -1; } - if (devs.is_array()) - { - for (int i = 0; i < devs.size(); ++i) - { - device d; - json_extract(devs[i], "", d); - devices.push_back(d); - } - } + container.clear(); + container = devs; - for (auto device : devices) - { - container.add_device(device); - } - - - auto setup_handler = [&container](const httplib::Request& req, httplib::Response& res) + auto setup_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res) { if (req.has_header("ID")) { json response; string id = req.get_header_value("ID"); - const device* dev = container.get_device_by_id(id); - if (nullptr == dev) + TRMNL* trmnl = container.get_device_by_id(id); + + // Refresh date from file + // Someone might have put the new device in + if (nullptr == trmnl) + { + json j; + read_file_json(j, devices_filename, &cout); + container.clear(); + container = j; + trmnl = container.get_device_by_id(id); + } + + if (nullptr == trmnl) { res.status = 404; @@ -247,29 +114,40 @@ int main(int argc, char **argv) } else { + bool should_dump = false; + res.status = 200; - if (dev->friendly_id.empty()) + if (trmnl->friendly_id().empty()) { - container.set_device_friendly(id, device_id_to_friendly(id)); + should_dump = true; + trmnl->friendly_id(TRMNL::friendly_from_id(id)); } - device* dev_mut = const_cast(dev); + if (trmnl->api_key().empty()) + { + should_dump = true; + trmnl->api_key("nullptr"); + } response["status"] = 200; - response["api_key"] = "nullptr"; - response["friendly_id"] = dev->friendly_id; + response["api_key"] = trmnl->api_key(); + response["friendly_id"] = trmnl->friendly_id(); + // TODO: Check for image in folder response["image_url"] = "https://trmnl.com/images/setup/setup-logo.bmp"; response["filename"] = "welcome"; res.body = response.dump(); - json test = *dev_mut; - cout << test.dump(4) << endl; + if (should_dump) + { + write_file_json(container, devices_filename, &cout); + } } } else { - res.status = 500; + // Bad Request - No ID header + res.status = 400; } };