diff --git a/main.cpp b/main.cpp index 1829b25..edb7637 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,3 @@ -// Start using chrono when C++20 becomes available -// #include #include #include #include @@ -7,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -15,7 +14,6 @@ #include #include -// Stop using these when C++20 becomes available #include #include @@ -34,7 +32,21 @@ constexpr int API_KEY_LENGHT = 16; constexpr char DEFAULT_IMAGE_URL[] = "https://trmnl.com/images/setup/setup-logo.bmp"; constexpr char DEFAULT_IMAGE_FNAME[] = "2024-09-20T00:00:00"; -string generate_api_key(int len = API_KEY_LENGHT) +// Generates a random api key with specified length +string generate_api_key(int len = API_KEY_LENGHT); + +// Reloads devices from specified filename +// If forced is false - checks last modification time to determine whether to actually do it +bool reload_container(TRMNLContainer& container, shared_mutex& mut_container, const string& filename, bool forced = false); + +// Returns the last modification time down to seconds in ISO format - 'YYYY-MM-DDThh:mm:ss' +string get_timestamp_from_filename(const string& filepath); + +// First string is image filename for URL +// Second string is timestamp for TRMNL filename +pair find_image_for_friendly(const string& folder_images, const string& friendly_id); + +string generate_api_key(int len) { string result = ""; std::random_device rand; @@ -82,14 +94,32 @@ string generate_api_key(int len = API_KEY_LENGHT) return result; } -bool reload_container(TRMNLContainer& container, const string& filename) +bool reload_container(TRMNLContainer& container, shared_mutex& mut_container, const string& filename, bool forced) { + static mutex mut_mod_time; + static string last_mod_time = ""; json j; - bool ok = read_file_json(j, filename, &cout); + bool ok; + lock_guard guard(mut_mod_time); + + // Check if file mod time has changed since last reload + if (!forced) + { + if (last_mod_time == get_timestamp_from_filename(filename)) + { + return true; + } + } + + ok = read_file_json(j, filename, &cout); if (!ok) { return false; } + + // Lock Write (exclusive) Mutex & automatically unlock on scope exit + unique_lock l(mut_container); + container.clear(); if (j.is_array()) { @@ -98,6 +128,7 @@ bool reload_container(TRMNLContainer& container, const string& filename) container.add_device(j[i]); } } + last_mod_time = get_timestamp_from_filename(filename); return true; } @@ -127,31 +158,6 @@ string get_timestamp_from_filename(const string& filepath) return result; } -// Use this when C++20 becomes available -#if 0 -string get_timestamp_from_file_time(const fs::file_time_type& ftime) -{ - char result[128] = ""; - time_t file_time = std::chrono::system_clock::to_time_t(std::chrono::file_clock::to_sys(ftime)); - - tm* file_tm; - file_tm = gmtime(&file_time); - - snprintf(result, sizeof(result) - 1, "%04d-%02d-%02dT%02d:%02d:%02d", - file_tm->tm_year + 1900, - file_tm->tm_mon + 1, - file_tm->tm_mday, - file_tm->tm_hour, - file_tm->tm_min, - file_tm->tm_sec - ); - - return result; -} -#endif - -// First string is image filename for URL -// Second string is timestamp for TRMNL filename pair find_image_for_friendly(const string& folder_images, const string& friendly_id) { pair result = {"", ""}; @@ -169,8 +175,6 @@ pair find_image_for_friendly(const string& folder_images, const { result.first = entry.path().filename(); result.second = get_timestamp_from_filename(entry.path()); - // Change to this when C++20 becomes available - // result.second = get_timestamp_from_file_time(entry.last_write_time()); } } @@ -199,6 +203,7 @@ int main(int argc, char **argv) json cfg; json devs; TRMNLContainer container; + shared_mutex mut_container; if (argc > 2) { @@ -264,14 +269,15 @@ int main(int argc, char **argv) server = make_shared(); } - ok = reload_container(container, devices_filename); + ok = reload_container(container, mut_container, devices_filename); if (!ok) { cout << "Could not read devices file" << endl; return -1; } - auto setup_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res) + auto setup_handler = [&container, &mut_container, &devices_filename] + (const httplib::Request& req, httplib::Response& res) { json response; @@ -291,8 +297,10 @@ int main(int argc, char **argv) // Refresh data from file // Someone might have put a new device in - reload_container(container, devices_filename); + reload_container(container, mut_container, devices_filename); + // Write Lock - setup does not happen often enough, but 2 setups can lead to race condition on swapping read/write locks + unique_lock l(mut_container); string id = req.get_header_value("ID"); TRMNL* trmnl = container.get_device_by_id(id); @@ -341,7 +349,7 @@ int main(int argc, char **argv) } }; - auto display_handler = [&container, &devices_filename, &folder_images, &base_url, refresh_min, refresh_offset] + auto display_handler = [&container, &mut_container, &devices_filename, &folder_images, &base_url, refresh_min, refresh_offset] (const httplib::Request& req, httplib::Response& res) { json response; @@ -361,8 +369,10 @@ int main(int argc, char **argv) // Refresh data from file // Someone might have put a new device in - reload_container(container, devices_filename); + reload_container(container, mut_container, devices_filename); + // Lock for reading + shared_lock l(mut_container); string id = req.get_header_value("ID"); string api_key = req.get_header_value("Access-Token"); TRMNL* trmnl = container.get_device_by_id(id);