Added thread safety

This commit is contained in:
2026-07-08 14:50:46 +03:00
parent 9970b30397
commit b9ed4fa76a

View File

@@ -1,5 +1,3 @@
// Start using chrono when C++20 becomes available
// #include <chrono>
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include <list> #include <list>
@@ -7,6 +5,7 @@
#include <memory> #include <memory>
#include <random> #include <random>
#include <set> #include <set>
#include <shared_mutex>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -15,7 +14,6 @@
#include <stdint.h> #include <stdint.h>
#include <time.h> #include <time.h>
// Stop using these when C++20 becomes available
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -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_URL[] = "https://trmnl.com/images/setup/setup-logo.bmp";
constexpr char DEFAULT_IMAGE_FNAME[] = "2024-09-20T00:00:00"; 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<string, string> find_image_for_friendly(const string& folder_images, const string& friendly_id);
string generate_api_key(int len)
{ {
string result = ""; string result = "";
std::random_device rand; std::random_device rand;
@@ -82,14 +94,32 @@ string generate_api_key(int len = API_KEY_LENGHT)
return result; 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; 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) if (!ok)
{ {
return false; return false;
} }
// Lock Write (exclusive) Mutex & automatically unlock on scope exit
unique_lock l(mut_container);
container.clear(); container.clear();
if (j.is_array()) if (j.is_array())
{ {
@@ -98,6 +128,7 @@ bool reload_container(TRMNLContainer& container, const string& filename)
container.add_device(j[i]); container.add_device(j[i]);
} }
} }
last_mod_time = get_timestamp_from_filename(filename);
return true; return true;
} }
@@ -127,31 +158,6 @@ string get_timestamp_from_filename(const string& filepath)
return result; 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<string, string> find_image_for_friendly(const string& folder_images, const string& friendly_id) pair<string, string> find_image_for_friendly(const string& folder_images, const string& friendly_id)
{ {
pair<string, string> result = {"", ""}; pair<string, string> result = {"", ""};
@@ -169,8 +175,6 @@ pair<string, string> find_image_for_friendly(const string& folder_images, const
{ {
result.first = entry.path().filename(); result.first = entry.path().filename();
result.second = get_timestamp_from_filename(entry.path()); 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 cfg;
json devs; json devs;
TRMNLContainer container; TRMNLContainer container;
shared_mutex mut_container;
if (argc > 2) if (argc > 2)
{ {
@@ -264,14 +269,15 @@ int main(int argc, char **argv)
server = make_shared<httplib::Server>(); server = make_shared<httplib::Server>();
} }
ok = reload_container(container, devices_filename); ok = reload_container(container, mut_container, devices_filename);
if (!ok) if (!ok)
{ {
cout << "Could not read devices file" << endl; cout << "Could not read devices file" << endl;
return -1; 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; json response;
@@ -291,8 +297,10 @@ int main(int argc, char **argv)
// Refresh data from file // Refresh data from file
// Someone might have put a new device in // 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"); string id = req.get_header_value("ID");
TRMNL* trmnl = container.get_device_by_id(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) (const httplib::Request& req, httplib::Response& res)
{ {
json response; json response;
@@ -361,8 +369,10 @@ int main(int argc, char **argv)
// Refresh data from file // Refresh data from file
// Someone might have put a new device in // 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 id = req.get_header_value("ID");
string api_key = req.get_header_value("Access-Token"); string api_key = req.get_header_value("Access-Token");
TRMNL* trmnl = container.get_device_by_id(id); TRMNL* trmnl = container.get_device_by_id(id);