Leviathan  0.8.0.0
Leviathan game engine
ResourceRefreshHandler.cpp
Go to the documentation of this file.
1 // ------------------------------------ //
3 
4 #include "../TimeIncludes.h"
6 #include "IDFactory.h"
7 #ifdef __linux__
8 #include <sys/inotify.h>
9 #include <sys/types.h>
10 #endif
11 using namespace Leviathan;
12 using namespace std;
13 // ------------------------------------ //
14 #ifdef __linux__
15 
16 #define IN_EVENT_SIZE (sizeof(inotify_event))
17 #define IN_READ_BUFFER_SIZE (124 * (IN_EVENT_SIZE + 16))
18 
19 
20 #endif
21 
23 
25 {
26  LEVIATHAN_ASSERT(Staticaccess != this,
27  "ResourceRefreshHandler should have been released before destructor");
28 }
29 
31 {
32  return Staticaccess;
33 }
34 
36 // ------------------------------------ //
38 {
39  // Set the next update time //
40  NextUpdateTime = Time::GetCurrentTimePoint() + MillisecondDuration(1000);
41 
42  Staticaccess = this;
43  return true;
44 }
45 
47 {
48  GUARD_LOCK();
49 
50  Staticaccess = NULL;
51 
52  // Release all listeners //
53  auto end = ActiveFileListeners.end();
54  for(auto iter = ActiveFileListeners.begin(); iter != end; ++iter) {
55 
56  (*iter)->StopThread();
57  }
58 
59 
60  ActiveFileListeners.clear();
61 }
62 // ------------------------------------ //
64  const std::vector<const std::string*>& filestowatch,
65  std::function<void(const std::string&, ResourceFolderListener&)> notifyfunction,
66  int& createdid)
67 {
68 
69  unique_ptr<ResourceFolderListener> tmpcreated(
70  new ResourceFolderListener(filestowatch, notifyfunction));
71 
72  createdid = tmpcreated->GetID();
73 
74  if(!tmpcreated->StartListening())
75  return false;
76 
77  GUARD_LOCK();
78 
79  // Add it //
80  ActiveFileListeners.push_back(move(tmpcreated));
81 
82  return true;
83 }
84 
86 {
87 
88  GUARD_LOCK();
89 
90  // Find the specific listener //
91  auto end = ActiveFileListeners.end();
92  for(auto iter = ActiveFileListeners.begin(); iter != end; ++iter) {
93 
94  if((*iter)->GetID() == idoflistener) {
95 
96  (*iter)->StopThread();
97  ActiveFileListeners.erase(iter);
98  return;
99  }
100  }
101 }
102 
104 {
105  GUARD_LOCK();
106 
107  if(Time::GetCurrentTimePoint() > NextUpdateTime) {
108  // Update all the file listeners //
109  for(size_t i = 0; i < ActiveFileListeners.size(); i++) {
110 
111  ActiveFileListeners[i]->CheckUpdatesEnded();
112  }
113 
114  // Set new update time //
115  NextUpdateTime = Time::GetCurrentTimePoint() + MillisecondDuration(1000);
116  }
117 }
118 // ------------------------------------ //
120  const std::vector<int>& ids)
121 {
122 
123  GUARD_LOCK();
124 
125  // Find any listeners matching any of the ids //
126  auto end = ActiveFileListeners.end();
127  for(auto iter = ActiveFileListeners.begin(); iter != end; ++iter) {
128 
129  const int& curid = (*iter)->GetID();
130 
131  // Check against all the ids //
132  auto end2 = ids.end();
133  for(auto iter2 = ids.begin(); iter2 != end2; ++iter2) {
134  if(curid == *iter2) {
135 
136  (*iter)->MarkAllAsNotUpdated();
137  break;
138  }
139  }
140  }
141 }
142 // ------------------ ResourceFolderListener ------------------ //
144  const std::vector<const std::string*>& filestowatch,
145  std::function<void(const std::string&, ResourceFolderListener&)> notifyfunction) :
146  ListenedFiles(filestowatch.size()),
147  UpdatedFiles(filestowatch.size(), false), ID(IDFactory::GetID()),
148  CallbackFunction(notifyfunction)
149 {
150 #ifdef _WIN32
151  // Avoid having to re-allocate the vector later //
152  SignalingHandles.reserve(1 + 1);
153 
154 #else
155 
156  InotifyID = inotify_init();
157 
158  if(InotifyID < -1) {
159 
160  Logger::Get()->Error("ResourceRefreshHandler: ResourceFolderListener: "
161  "failed to create inotify instance");
162  return;
163  }
164 
165 #endif //_WIN32
166 
167  // Copy the target files //
168  for(size_t i = 0; i < ListenedFiles.size(); i++) {
169 
170  // Get the folder on the first loop //
171  if(i == 0) {
172 
173 
174  TargetFolder = StringOperations::GetPath<std::string>(*filestowatch[i]);
175  }
176 
177  ListenedFiles[i] = make_unique<std::string>(
178  StringOperations::RemovePath<std::string>(*filestowatch[i]));
179  }
180 }
181 
183 {
185  ShouldQuit, "ResourceFolderListener should have been stopped before destructor");
186 }
187 // ------------------------------------ //
189 {
190  return ID;
191 }
192 // ------------------------------------ //
194 {
195 
196 #ifdef _WIN32
197  // First create the stop signaler //
198  HANDLE ourstopper = CreateEvent(NULL, FALSE, FALSE, NULL);
199 
200  if(!ourstopper) {
201 
202  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to create stop "
203  "notify handle, CreateEvent failed");
204  return false;
205  }
206 
207 
208  SignalingHandles.push_back(ourstopper);
209 
210  // Now the folder listener //
211  TargetFolderHandle =
212  CreateFileA(TargetFolder.c_str(), FILE_READ_DATA | FILE_TRAVERSE | FILE_READ_EA,
213  FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
214  FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
215 
216  if(TargetFolderHandle == INVALID_HANDLE_VALUE || !TargetFolderHandle) {
217 
218  Logger::Get()->Error(
219  "ResourceFolderListener: StartListening: failed to open folder for "
220  "reading, error: " +
221  Convert::ToHexadecimalString(GetLastError()));
222  return false;
223  }
224 
225 
226  // Create the OVERLAPPED struct next //
227  OverlappedInfo = new OVERLAPPED;
228 
229  ZeroMemory(OverlappedInfo, sizeof(OVERLAPPED));
230 
231  // Create an event for notification //
232  HANDLE readcompleteevent = CreateEvent(NULL, FALSE, FALSE, NULL);
233 
234  if(!readcompleteevent) {
235 
236  Logger::Get()->Error(
237  "ResourceFolderListener: StartListening: "
238  "failed to create read notify handle, CreateEvent failed, error: " +
239  Convert::ToHexadecimalString(GetLastError()));
240  return false;
241  }
242 
243  // Add it to the overlapped //
244  OverlappedInfo->hEvent = readcompleteevent;
245 
246 
247 
248  OurReadBuffer = new FILE_NOTIFY_INFORMATION[100];
249 
250 
251  // Create the update notification read thing //
252  BOOL createresult = ReadDirectoryChangesW(TargetFolderHandle, OurReadBuffer,
253  sizeof(FILE_NOTIFY_INFORMATION) * 100,
254  // Only the top level directory is watched
255  FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, OverlappedInfo, NULL);
256 
257  if(!createresult) {
258 
259  CloseHandle(TargetFolderHandle);
260  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to start reading "
261  "directory changes, error: " +
262  Convert::ToHexadecimalString(GetLastError()));
263  return false;
264  }
265 
266  SignalingHandles.push_back(readcompleteevent);
267 
268 #else
269 
270  // Create watches for all of them //
271 
272  InotifyWatches = inotify_add_watch(InotifyID, TargetFolder.c_str(), IN_MODIFY);
273 
274 
275  if(InotifyWatches < 0) {
276 
277 
278  Logger::Get()->Error("ResourceRefreshHandler: ResourceFolderListener: failed to add "
279  "watch for folder: " +
280  TargetFolder);
281  return false;
282  }
283 
284 
285  // Allocate the read buffer //
286  ReadBuffer = new char[IN_READ_BUFFER_SIZE];
287 
288 #endif //_WIN32
289 
290 
291  ShouldQuit = false;
292 
293  // Finally start the thread //
294  ListenerThread =
295  std::thread(std::bind(&ResourceFolderListener::_RunListeningThread, this));
296 
297  return true;
298 }
299 
301 {
302 #ifdef _WIN32
303  // Don't do anything if already done //
304  if(ShouldQuit && SignalingHandles.empty())
305  return;
306 
307  ShouldQuit = true;
308 
309  // Signal the close handle //
310  SetEvent(SignalingHandles[0]);
311 
312  // Join the thread to wait for it to quit //
313  ListenerThread.join();
314 
315  // Close the handles //
316  CloseHandle(SignalingHandles[0]);
317  CloseHandle(SignalingHandles[1]);
318  CloseHandle(TargetFolderHandle);
319 
320  SignalingHandles.clear();
321 
322  if(OurReadBuffer) {
323  delete[] OurReadBuffer;
324  OurReadBuffer = NULL;
325  }
326 
327  SAFE_DELETE(OverlappedInfo);
328 #else
329 
330  if(ShouldQuit && InotifyID == -1)
331  return;
332 
333  ShouldQuit = true;
334 
335  inotify_rm_watch(InotifyID, InotifyWatches);
336  close(InotifyID);
337 
338  ListenerThread.join();
339 
340  if(ReadBuffer) {
341  delete[] ReadBuffer;
342  ReadBuffer = NULL;
343  }
344 
345  InotifyID = -1;
346  InotifyWatches = -1;
347 
348 #endif //_WIN32
349 }
350 // ------------------------------------ //
352 {
353  // Run until quit is requested //
354  while(!ShouldQuit) {
355 
356 #ifdef _WIN32
357 
358  // Wait for the handles //
359  DWORD waitstatus = WaitForMultipleObjects(static_cast<DWORD>(SignalingHandles.size()),
360  &SignalingHandles[0], FALSE, INFINITE);
361 
362  // Check what happened //
363  switch(waitstatus) {
364  case WAIT_OBJECT_0:
365 
366  // Quit has been called //
367  return;
368 
369  case WAIT_OBJECT_0 + 1: {
370  // A modification has been detected //
371 
372  // Check what is detected //
373  FILE_NOTIFY_INFORMATION* dataptr = OurReadBuffer;
374 
375  DWORD numread;
376 
377  GetOverlappedResult(TargetFolderHandle, OverlappedInfo, &numread, FALSE);
378 
379  if(numread < 1) {
380  // Nothing read/failed //
381  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: result "
382  "has 0 bytes: ");
383  continue;
384  }
385 
386  bool working = true;
387 
388  while(working) {
389  // Check what the notification is //
390  if(dataptr->Action == FILE_ACTION_REMOVED ||
391  dataptr->Action == FILE_ACTION_RENAMED_OLD_NAME) {
392 
393  goto movetonextdatalabel;
394  }
395 
396  {
397  // Get the filename //
398  std::wstring entrydata;
399 
400  size_t filenameinwchars = dataptr->FileNameLength / sizeof(wchar_t);
401 
402  entrydata.resize(filenameinwchars);
403 
404  // Copy the data //
405  memcpy_s(&entrydata[0], entrydata.size() * sizeof(wchar_t),
406  dataptr->FileName, dataptr->FileNameLength);
407 
408  std::string utf8file = Convert::Utf16ToUtf8(entrydata);
409 
410  // Skip if nothing //
411  if(utf8file.empty()) {
412 
413  goto movetonextdatalabel;
414  }
415 
416 
417  // Check which file matches and set it as updated //
418  for(size_t i = 0; i < ListenedFiles.size(); i++) {
419 
420  if(*ListenedFiles[i] == utf8file) {
421 
422  // Updated //
423  UpdatedFiles[i] = true;
424  break;
425  }
426  }
427  }
428  movetonextdatalabel:
429 
430  if(!dataptr->NextEntryOffset) {
431 
432  // No more entries
433  working = false;
434  break;
435  } else {
436  // Move to next entry //
437 
438  char* tmpptr = reinterpret_cast<char*>(dataptr);
439  tmpptr += dataptr->NextEntryOffset;
440 
441  dataptr = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(tmpptr);
442  }
443  }
444 
445  // Start listening again //
446  BOOL createresult = ReadDirectoryChangesW(TargetFolderHandle, OurReadBuffer,
447  sizeof(FILE_NOTIFY_INFORMATION) * 100,
448  // Only the top level directory is watched
449  FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, OverlappedInfo, NULL);
450 
451  if(!createresult) {
452 
453  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: "
454  "re-queuing file change read failed: " +
455  Convert::ToHexadecimalString(GetLastError()));
456  return;
457  }
458 
459  } break;
460 
461  case WAIT_TIMEOUT:
462  // NO such thing, continue!
463  continue;
464 
465  default:
466  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: invalid wait "
467  "result: " +
468  Convert::ToString(waitstatus));
469  break;
470  }
471 
472 #else
473 
474  // Read the changes until the end of time //
475  int readcount = read(InotifyID, ReadBuffer, IN_READ_BUFFER_SIZE);
476 
477  if(readcount < 0) {
478 
479  // Failed reading, quit //
480  Logger::Get()->Warning("ResourceFolderListener: read failed, quitting thread");
481  return;
482  }
483 
484  // Handle all the data //
485  for(int i = 0; i < readcount;) {
486 
487  // Break if invalid buffer //
488  if(!ReadBuffer)
489  return;
490 
491  inotify_event* event = reinterpret_cast<inotify_event*>(&ReadBuffer[i]);
492 
493  if(event->len) {
494 
495  if(event->mask & IN_MODIFY) {
496  if(!(event->mask & IN_ISDIR)) {
497 
498  // Some file was modified, check was it one of ours //
499  const string modifiedfile(event->name, event->len);
500 
501  if(!modifiedfile.empty()) {
502 
503  // Check which file matches and set it as updated //
504  for(size_t i = 0; i < ListenedFiles.size(); i++) {
505 
506  if(*ListenedFiles[i] == modifiedfile) {
507 
508  // Updated //
509  UpdatedFiles[i] = true;
510  break;
511  }
512  }
513  }
514  }
515  }
516  }
517 
518  i += IN_EVENT_SIZE + event->len;
519  }
520 
521 
522 
523 #endif //_Win32
524  }
525 }
526 // ------------------------------------ //
528 {
529  // Check are some updated files readable //
530  for(size_t i = 0; i < UpdatedFiles.size(); i++) {
531 
532  if(UpdatedFiles[i]) {
533 
534  // Check is it readable //
535  const std::string checkread = TargetFolder + *ListenedFiles[i];
536 
537  ifstream reader(checkread);
538 
539  if(reader.is_open()) {
540 
541  reader.close();
542 
543  // Notify that the file is now available //
544  CallbackFunction(*ListenedFiles[i], *this);
545  UpdatedFiles[i] = false;
546  }
547  }
548  }
549 }
550 // ------------------------------------ //
552 {
553  auto end = UpdatedFiles.end();
554  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter) {
555 
556  (*iter) = false;
557  }
558 }
559 // ------------------------------------ //
561 {
562  // Try to find a set bool //
563  auto end = UpdatedFiles.end();
564  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter) {
565 
566  if((*iter)) {
567 
568  return true;
569  }
570  }
571 
572  // No file is marked as updated //
573  return false;
574 }
bool StartListening()
Starts a listening thread.
void CheckUpdatesEnded()
Checks if files marked as updated (only the first is actually checked) are available for reading.
DLLEXPORT void CheckFileStatus()
Called by Engine to check are updated files available.
DLLEXPORT int GetID() const
Gets the ID of this object.
DLLEXPORT void StopListeningForFileChanges(int idoflistener)
Stops a listener with a specific id.
int InotifyID
The ID of our inotify instance thing.
static DLLEXPORT std::string Utf16ToUtf8(const std::wstring &utf16str)
Encodes an UTF8 string from a wide string (wstring/utf16)
Definition: Convert.cpp:108
void StopThread()
Sets the internal thread to die and signals it.
ResourceFolderListener(const std::vector< const std::string * > &filestowatch, std::function< void(const std::string &, ResourceFolderListener &)> notifyfunction)
Creates a new listener.
DLLEXPORT void Warning(const std::string &data) override
Definition: Logger.cpp:190
Allows various resource loaders to get notified when the file on disk changes.
DLLEXPORT void MarkAllAsNotUpdated()
Marks all files as not updated.
std::chrono::duration< int64_t, std::milli > MillisecondDuration
Definition: TimeIncludes.h:14
static DLLEXPORT TimePoint GetCurrentTimePoint()
#define LEVIATHAN_ASSERT(x, msg)
Definition: Define.h:104
static std::string ToString(const T &val)
Definition: Convert.h:72
static std::string ToHexadecimalString(const T &val)
Definition: Convert.h:93
std::string TargetFolder
The folder in which to listen for stuff.
static DLLEXPORT Logger * Get()
Definition: Logger.cpp:106
DLLEXPORT bool ListenForFileChanges(const std::vector< const std::string * > &filestowatch, std::function< void(const std::string &, ResourceFolderListener &)> notifyfunction, int &createdid)
Starts listening for changes made to filestowatch.
A file listener instance which listens for file changes in a folder.
DLLEXPORT void MarkListenersAsNotUpdated(const std::vector< int > &ids)
Marks all files in all listeners matching any of the IDs as not updated.
#define DLLEXPORT
Definition: Include.h:84
std::vector< std::unique_ptr< std::string > > ListenedFiles
The files which are listened for.
The access mask controls which registered functions and classes a script sees.
Definition: GameModule.h:12
DLLEXPORT bool IsAFileStillUpdated() const
Checks whether a file is still marked as updated.
#define SAFE_DELETE(x)
Definition: Define.h:153
DLLEXPORT void Error(const std::string &data) override
Definition: Logger.cpp:177
static DLLEXPORT ResourceRefreshHandler * Get()
#define GUARD_LOCK()
Definition: ThreadSafe.h:111
static ResourceRefreshHandler * Staticaccess