11#include < iostream>
2- #include < map>
32#include < unordered_map>
43#include < mutex>
54#include < thread>
65#include < chrono>
76#include < filesystem>
87#include < fstream>
9- #include < set>
8+ #include < string>
9+ #include < cstdint>
10+ #include < cstring>
11+ #include " App.h"
12+ #include < zlib.h>
13+
14+ #if defined(__linux__)
1015#include < sys/inotify.h>
1116#include < unistd.h>
12- #include < cstring>
13- #include " App.h" // Ensure uWS is installed and this header is available
17+ #endif
1418
15- int cooldown = 0 ; // how much to sleep after reloading files, seconds
19+ int cooldown = 0 ; // Seconds to sleep after reloading files (Linux only)
1620
17- // Custom hasher for both std::string and std::string_view
21+ // Custom hasher for transparent lookup with std::string and std::string_view
1822struct StringViewHasher {
19- using is_transparent = void ; // Enable transparent lookup
20-
21- std::size_t operator ()(const std::string& s) const {
22- return std::hash<std::string_view>{}(s);
23- }
24-
25- std::size_t operator ()(std::string_view s) const {
26- return std::hash<std::string_view>{}(s);
27- }
23+ using is_transparent = void ;
24+ std::size_t operator ()(const std::string& s) const { return std::hash<std::string_view>{}(s); }
25+ std::size_t operator ()(std::string_view s) const { return std::hash<std::string_view>{}(s); }
2826};
2927
3028// Custom equality comparator for transparent lookup
3129struct StringViewEqual {
32- using is_transparent = void ; // Enable transparent lookup
33-
34- // Single operator() that handles all combinations
35- bool operator ()(std::string_view lhs, std::string_view rhs) const {
36- return lhs == rhs;
37- }
30+ using is_transparent = void ;
31+ bool operator ()(std::string_view lhs, std::string_view rhs) const { return lhs == rhs; }
3832};
3933
40-
41- // Global variables for the file map, mutex, and inotify file descriptor
34+ // Global variables
4235std::unordered_map<std::string, std::pair<std::string, bool >, StringViewHasher, StringViewEqual> file_map;
4336std::mutex map_mutex;
37+ #if defined(__linux__)
4438int inotify_fd;
39+ #endif
4540unsigned long fileSizes = 0 ;
4641
47- #include < zlib.h>
48- #include < stdexcept>
49-
50-
42+ // Loads file content and compresses it with zlib if beneficial
5143std::pair<std::string, bool > load_file_content (const std::filesystem::path& path) {
52- // Load file content
5344 std::ifstream file (path, std::ios::binary);
5445 if (!file) {
5546 std::cerr << " Failed to open file: " << path << std::endl;
@@ -60,76 +51,68 @@ std::pair<std::string, bool> load_file_content(const std::filesystem::path& path
6051 file.seekg (0 , std::ios::beg);
6152 std::string content (size, 0 );
6253 file.read (&content[0 ], size);
63- // std::cout << "Loaded file: " << path << " (" << size << " bytes)" << std::endl;
6454
65- // Compress in memory using zlib
6655 z_stream zs = {};
67- zs.zalloc = Z_NULL;
68- zs.zfree = Z_NULL;
69- zs.opaque = Z_NULL;
7056 if (deflateInit2 (&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16 , 8 , Z_DEFAULT_STRATEGY) != Z_OK) {
71- std::cerr << " Failed to initialize zlib" << std::endl;
57+ std::cerr << " Failed to initialize zlib for " << path << std::endl;
7258 return {" " , false };
7359 }
74-
7560 zs.next_in = reinterpret_cast <Bytef*>(content.data ());
7661 zs.avail_in = content.size ();
77-
78- // Calculate output buffer size (zlib guarantees max output size)
7962 size_t bound = deflateBound (&zs, content.size ());
8063 std::string compressed (bound, 0 );
8164 zs.next_out = reinterpret_cast <Bytef*>(&compressed[0 ]);
8265 zs.avail_out = bound;
83-
8466 int ret = deflate (&zs, Z_FINISH);
8567 if (ret != Z_STREAM_END) {
8668 deflateEnd (&zs);
87- std::cerr << " Failed to compress data " << std::endl;
69+ std::cerr << " Failed to compress " << path << std::endl;
8870 return {" " , false };
8971 }
90-
9172 size_t compressed_size = zs.total_out ;
9273 deflateEnd (&zs);
93-
94- // Resize to actual compressed size
9574 compressed.resize (compressed_size);
9675
97- if (compressed_size > size) {
98- fileSizes += size ;
99- return {content, false };
76+ if (compressed_size < size) {
77+ fileSizes += compressed_size ;
78+ return {compressed, true };
10079 }
101-
102- // std::cout << "Compressed to: " << compressed_size << " bytes" << std::endl;
103- fileSizes += compressed_size;
104- return {compressed, true };
80+ fileSizes += size;
81+ return {content, false };
10582}
10683
107- // Function to load all files from the root folder into the map and add inotify watches
108- void load_files (const std::string& root, int inotify_fd) {
84+ // Loads all files from the root folder into the map; adds inotify watches on Linux if inotify_fd >= 0
85+ void load_files (const std::string& root, int inotify_fd = - 1 ) {
10986 fileSizes = 0 ;
11087 std::unordered_map<std::string, std::pair<std::string, bool >, StringViewHasher, StringViewEqual> new_map;
11188 for (const auto & entry : std::filesystem::recursive_directory_iterator (root)) {
11289 if (entry.is_regular_file () && !entry.path ().filename ().string ().starts_with (" ." )) {
11390 std::string relative_path = " /" + std::filesystem::relative (entry.path (), root).generic_string ();
11491 auto [content, compressed] = load_file_content (entry.path ());
11592 new_map[relative_path] = {content, compressed};
116- // Add watch to file for IN_MODIFY
117- inotify_add_watch (inotify_fd, entry.path ().c_str (), IN_MODIFY);
93+ #if defined(__linux__)
94+ if (inotify_fd >= 0 ) {
95+ inotify_add_watch (inotify_fd, entry.path ().c_str (), IN_MODIFY);
96+ }
97+ #endif
11898 } else if (entry.is_directory ()) {
119- // Add watch to directory for IN_CREATE | IN_DELETE | IN_MOVE
120- inotify_add_watch (inotify_fd, entry.path ().c_str (), IN_CREATE | IN_DELETE | IN_MOVE);
99+ #if defined(__linux__)
100+ if (inotify_fd >= 0 ) {
101+ inotify_add_watch (inotify_fd, entry.path ().c_str (), IN_CREATE | IN_DELETE | IN_MOVE);
102+ }
103+ #endif
121104 }
122105 }
123106 {
124107 std::lock_guard<std::mutex> lock (map_mutex);
125108 file_map = std::move (new_map);
126- std::cout << " Reloaded " << (fileSizes / 1024 / 1024 ) << " MB of files into RAM" << std::endl;
109+ std::cout << " Loaded " << (fileSizes / 1024 / 1024 ) << " MB of files into RAM" << std::endl;
127110 }
128111}
129112
130- // Background thread function to monitor inotify events and reload files on changes
113+ #if defined(__linux__)
114+ // Background thread to monitor inotify events and reload files on changes (Linux only)
131115void inotify_reloader_function (const std::string& root, int inotify_fd) {
132- load_files (root, inotify_fd);
133116 char buffer[4096 ];
134117 while (true ) {
135118 int length = read (inotify_fd, buffer, sizeof (buffer));
@@ -144,6 +127,7 @@ void inotify_reloader_function(const std::string& root, int inotify_fd) {
144127 }
145128 }
146129}
130+ #endif
147131
148132int main (int argc, char ** argv) {
149133 if (argc != 3 ) {
@@ -153,26 +137,32 @@ int main(int argc, char** argv) {
153137 std::string root = argv[1 ];
154138 cooldown = std::stoi (argv[2 ]);
155139 if (cooldown < 0 ) {
156- std::cerr << " Cooldown must be a positive integer" << std::endl;
140+ std::cerr << " Cooldown must be a non-negative integer" << std::endl;
157141 return 1 ;
158142 }
159143
144+ #if defined(__linux__)
160145 inotify_fd = inotify_init ();
161146 if (inotify_fd < 0 ) {
162147 std::cerr << " Failed to initialize inotify: " << strerror (errno) << std::endl;
163148 return 1 ;
164149 }
165-
150+ load_files (root, inotify_fd);
166151 std::thread inotify_reloader (inotify_reloader_function, root, inotify_fd);
152+ #else
153+ load_files (root); // Load once at startup on macOS and Windows
154+ #endif
167155
168156 uWS::App app;
169157
170- // We only need to lock once per event loop iteration, not on every request
171- uWS::Loop::get ()->addPostHandler (&inotify_reloader, [](uWS::Loop */*loop*/) {
158+ // Static key for uWS handlers
159+ static char handler_key;
160+
161+ // Add post and pre handlers to lock the mutex around event loop iterations (unchanged as requested)
162+ uWS::Loop::get ()->addPostHandler (&handler_key, [](uWS::Loop* /* loop*/ ) {
172163 std::lock_guard<std::mutex> lock (map_mutex);
173164 });
174-
175- uWS::Loop::get ()->addPreHandler(&inotify_reloader, [](uWS::Loop */*loop*/) {
165+ uWS::Loop::get ()->addPreHandler (&handler_key, [](uWS::Loop* /* loop*/ ) {
176166 std::lock_guard<std::mutex> lock (map_mutex);
177167 });
178168
@@ -188,6 +178,7 @@ int main(int argc, char** argv) {
188178 res->end (" Not Found" );
189179 }
190180 });
181+
191182 app.get (" /*" , [](auto * res, auto * req) {
192183 auto it = file_map.find (req->getUrl ());
193184 if (it != file_map.end ()) {
@@ -209,7 +200,10 @@ int main(int argc, char** argv) {
209200
210201 app.run ();
211202
203+ #if defined(__linux__)
212204 inotify_reloader.join ();
213205 close (inotify_fd);
206+ #endif
207+
214208 return 0 ;
215209}
0 commit comments