1+ #include < iostream>
2+ #include < map>
3+ #include < unordered_map>
4+ #include < mutex>
5+ #include < thread>
6+ #include < chrono>
7+ #include < filesystem>
8+ #include < fstream>
9+ #include < set>
10+ #include < sys/inotify.h>
11+ #include < unistd.h>
12+ #include < cstring>
13+ #include " App.h" // Ensure uWS is installed and this header is available
14+
15+ int cooldown = 0 ; // how much to sleep after reloading files, seconds
16+
17+ // Custom hasher for both std::string and std::string_view
18+ struct 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+ }
28+ };
29+
30+ // Custom equality comparator for transparent lookup
31+ struct 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+ }
38+ };
39+
40+
41+ // Global variables for the file map, mutex, and inotify file descriptor
42+ std::unordered_map<std::string, std::pair<std::string, bool >, StringViewHasher, StringViewEqual> file_map;
43+ std::mutex map_mutex;
44+ int inotify_fd;
45+ unsigned long fileSizes = 0 ;
46+
47+ #include < zlib.h>
48+ #include < stdexcept>
49+
50+
51+ std::pair<std::string, bool > load_file_content (const std::filesystem::path& path) {
52+ // Load file content
53+ std::ifstream file (path, std::ios::binary);
54+ if (!file) {
55+ std::cerr << " Failed to open file: " << path << std::endl;
56+ return {" " , false };
57+ }
58+ file.seekg (0 , std::ios::end);
59+ size_t size = file.tellg ();
60+ file.seekg (0 , std::ios::beg);
61+ std::string content (size, 0 );
62+ file.read (&content[0 ], size);
63+ // std::cout << "Loaded file: " << path << " (" << size << " bytes)" << std::endl;
64+
65+ // Compress in memory using zlib
66+ z_stream zs = {};
67+ zs.zalloc = Z_NULL;
68+ zs.zfree = Z_NULL;
69+ zs.opaque = Z_NULL;
70+ 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;
72+ return {" " , false };
73+ }
74+
75+ zs.next_in = reinterpret_cast <Bytef*>(content.data ());
76+ zs.avail_in = content.size ();
77+
78+ // Calculate output buffer size (zlib guarantees max output size)
79+ size_t bound = deflateBound (&zs, content.size ());
80+ std::string compressed (bound, 0 );
81+ zs.next_out = reinterpret_cast <Bytef*>(&compressed[0 ]);
82+ zs.avail_out = bound;
83+
84+ int ret = deflate (&zs, Z_FINISH);
85+ if (ret != Z_STREAM_END) {
86+ deflateEnd (&zs);
87+ std::cerr << " Failed to compress data" << std::endl;
88+ return {" " , false };
89+ }
90+
91+ size_t compressed_size = zs.total_out ;
92+ deflateEnd (&zs);
93+
94+ // Resize to actual compressed size
95+ compressed.resize (compressed_size);
96+
97+ if (compressed_size > size) {
98+ fileSizes += size;
99+ return {content, false };
100+ }
101+
102+ // std::cout << "Compressed to: " << compressed_size << " bytes" << std::endl;
103+ fileSizes += compressed_size;
104+ return {compressed, true };
105+ }
106+
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) {
109+ fileSizes = 0 ;
110+ std::unordered_map<std::string, std::pair<std::string, bool >, StringViewHasher, StringViewEqual> new_map;
111+ for (const auto & entry : std::filesystem::recursive_directory_iterator (root)) {
112+ if (entry.is_regular_file () && !entry.path ().filename ().string ().starts_with (" ." )) {
113+ std::string relative_path = " /" + std::filesystem::relative (entry.path (), root).generic_string ();
114+ auto [content, compressed] = load_file_content (entry.path ());
115+ 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);
118+ } 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);
121+ }
122+ }
123+ {
124+ std::lock_guard<std::mutex> lock (map_mutex);
125+ file_map = std::move (new_map);
126+ std::cout << " Reloaded " << (fileSizes / 1024 / 1024 ) << " MB of files into RAM" << std::endl;
127+ }
128+ }
129+
130+ // Background thread function to monitor inotify events and reload files on changes
131+ void inotify_reloader_function (const std::string& root, int inotify_fd) {
132+ load_files (root, inotify_fd);
133+ char buffer[4096 ];
134+ while (true ) {
135+ int length = read (inotify_fd, buffer, sizeof (buffer));
136+ if (length < 0 ) {
137+ std::cerr << " Error reading inotify events: " << strerror (errno) << std::endl;
138+ break ;
139+ }
140+ load_files (root, inotify_fd);
141+ if (cooldown) {
142+ std::cout << " Sleeping for " << cooldown << " seconds after reload" << std::endl;
143+ std::this_thread::sleep_for (std::chrono::seconds (cooldown));
144+ }
145+ }
146+ }
147+
148+ int main (int argc, char ** argv) {
149+ if (argc != 3 ) {
150+ std::cerr << " Usage: " << argv[0 ] << " <root_folder> <cooldown>" << std::endl;
151+ return 1 ;
152+ }
153+ std::string root = argv[1 ];
154+ cooldown = std::stoi (argv[2 ]);
155+ if (cooldown < 0 ) {
156+ std::cerr << " Cooldown must be a positive integer" << std::endl;
157+ return 1 ;
158+ }
159+
160+ inotify_fd = inotify_init ();
161+ if (inotify_fd < 0 ) {
162+ std::cerr << " Failed to initialize inotify: " << strerror (errno) << std::endl;
163+ return 1 ;
164+ }
165+
166+ std::thread inotify_reloader (inotify_reloader_function, root, inotify_fd);
167+
168+ uWS::App app;
169+
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*/) {
172+ std::lock_guard<std::mutex> lock (map_mutex);
173+ });
174+
175+ uWS::Loop::get ()->addPreHandler(&inotify_reloader, [](uWS::Loop */*loop*/) {
176+ std::lock_guard<std::mutex> lock (map_mutex);
177+ });
178+
179+ app.get(" /" , [](auto * res, auto * req) {
180+ auto it = file_map.find (std::string_view (" /index.html" , 11 ));
181+ if (it != file_map.end ()) {
182+ if (it->second .second ) {
183+ res->writeHeader (" Content-Encoding" , " gzip" );
184+ }
185+ res->end (it->second .first );
186+ } else {
187+ res->writeStatus (" 404 Not Found" );
188+ res->end (" Not Found" );
189+ }
190+ });
191+ app.get(" /*" , [](auto * res, auto * req) {
192+ auto it = file_map.find (req->getUrl ());
193+ if (it != file_map.end ()) {
194+ if (it->second .second ) {
195+ res->writeHeader (" Content-Encoding" , " gzip" );
196+ }
197+ res->end (it->second .first );
198+ } else {
199+ res->writeStatus (" 404 Not Found" );
200+ res->end (" Not Found" );
201+ }
202+ });
203+
204+ app.listen(8000 , [](auto * token) {
205+ if (token) {
206+ std::cout << " Listening on port 8000" << std::endl;
207+ }
208+ });
209+
210+ app.run();
211+
212+ inotify_reloader.join();
213+ close (inotify_fd);
214+ return 0 ;
215+ }
0 commit comments