Leviathan  0.8.0.0
Leviathan game engine
Leviathan::ResourceFolderListener Class Reference

A file listener instance which listens for file changes in a folder. More...

#include <ResourceRefreshHandler.h>

Public Member Functions

 ResourceFolderListener (const std::vector< const std::string * > &filestowatch, std::function< void(const std::string &, ResourceFolderListener &)> notifyfunction)
 Creates a new listener. More...
 
 ~ResourceFolderListener ()
 
DLLEXPORT int GetID () const
 Gets the ID of this object. More...
 
bool StartListening ()
 Starts a listening thread. More...
 
void StopThread ()
 Sets the internal thread to die and signals it. More...
 
void CheckUpdatesEnded ()
 Checks if files marked as updated (only the first is actually checked) are available for reading. More...
 
DLLEXPORT void MarkAllAsNotUpdated ()
 Marks all files as not updated. More...
 
DLLEXPORT bool IsAFileStillUpdated () const
 Checks whether a file is still marked as updated. More...
 

Protected Member Functions

void _RunListeningThread ()
 

Protected Attributes

std::thread ListenerThread
 The listening thread. More...
 
std::string TargetFolder
 The folder in which to listen for stuff. More...
 
std::vector< std::unique_ptr< std::string > > ListenedFiles
 The files which are listened for. More...
 
std::vector< bool > UpdatedFiles
 Marks the files that have been updated. More...
 
bool ShouldQuit = false
 Property set when quitting. More...
 
int ID
 ID used to find this specific object. More...
 
std::function< void(const std::string &, ResourceFolderListener &)> CallbackFunction
 The function called when a change is detected. More...
 
int InotifyID = -1
 The ID of our inotify instance thing. More...
 
int InotifyWatches = -1
 Inotify's folder id which is being monitored. More...
 
char * ReadBuffer = nullptr
 The read result buffer. More...
 

Detailed Description

A file listener instance which listens for file changes in a folder.

Todo:
Use only one inotify instance on linux

Definition at line 19 of file ResourceRefreshHandler.h.

Constructor & Destructor Documentation

◆ ResourceFolderListener()

Leviathan::ResourceFolderListener::ResourceFolderListener ( const std::vector< const std::string * > &  filestowatch,
std::function< void(const std::string &, ResourceFolderListener &)>  notifyfunction 
)

Creates a new listener.

See also
ResourceRefreshHandler::ListenForFileChanges

Definition at line 143 of file ResourceRefreshHandler.cpp.

145  :
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 }
int InotifyID
The ID of our inotify instance thing.
static int GetID()
Definition: IDFactory.h:17
std::string TargetFolder
The folder in which to listen for stuff.
static DLLEXPORT Logger * Get()
Definition: Logger.cpp:106
std::vector< bool > UpdatedFiles
Marks the files that have been updated.
int ID
ID used to find this specific object.
std::vector< std::unique_ptr< std::string > > ListenedFiles
The files which are listened for.
DLLEXPORT void Error(const std::string &data) override
Definition: Logger.cpp:177
std::function< void(const std::string &, ResourceFolderListener &)> CallbackFunction
The function called when a change is detected.

◆ ~ResourceFolderListener()

Leviathan::ResourceFolderListener::~ResourceFolderListener ( )

Definition at line 182 of file ResourceRefreshHandler.cpp.

183 {
185  ShouldQuit, "ResourceFolderListener should have been stopped before destructor");
186 }
#define LEVIATHAN_ASSERT(x, msg)
Definition: Define.h:104
bool ShouldQuit
Property set when quitting.

Member Function Documentation

◆ _RunListeningThread()

void Leviathan::ResourceFolderListener::_RunListeningThread ( )
protected

Definition at line 351 of file ResourceRefreshHandler.cpp.

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 }
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
DLLEXPORT void Warning(const std::string &data) override
Definition: Logger.cpp:190
static std::string ToString(const T &val)
Definition: Convert.h:72
static std::string ToHexadecimalString(const T &val)
Definition: Convert.h:93
static DLLEXPORT Logger * Get()
Definition: Logger.cpp:106
char * ReadBuffer
The read result buffer.
bool ShouldQuit
Property set when quitting.
std::vector< bool > UpdatedFiles
Marks the files that have been updated.
std::vector< std::unique_ptr< std::string > > ListenedFiles
The files which are listened for.
DLLEXPORT void Error(const std::string &data) override
Definition: Logger.cpp:177

◆ CheckUpdatesEnded()

void Leviathan::ResourceFolderListener::CheckUpdatesEnded ( )

Checks if files marked as updated (only the first is actually checked) are available for reading.

Definition at line 527 of file ResourceRefreshHandler.cpp.

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 }
std::string TargetFolder
The folder in which to listen for stuff.
std::vector< bool > UpdatedFiles
Marks the files that have been updated.
std::vector< std::unique_ptr< std::string > > ListenedFiles
The files which are listened for.
std::function< void(const std::string &, ResourceFolderListener &)> CallbackFunction
The function called when a change is detected.

◆ GetID()

int Leviathan::ResourceFolderListener::GetID ( ) const

Gets the ID of this object.

Definition at line 188 of file ResourceRefreshHandler.cpp.

189 {
190  return ID;
191 }
int ID
ID used to find this specific object.

◆ IsAFileStillUpdated()

DLLEXPORT bool Leviathan::ResourceFolderListener::IsAFileStillUpdated ( ) const

Checks whether a file is still marked as updated.

Definition at line 560 of file ResourceRefreshHandler.cpp.

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 }
std::vector< bool > UpdatedFiles
Marks the files that have been updated.

◆ MarkAllAsNotUpdated()

void Leviathan::ResourceFolderListener::MarkAllAsNotUpdated ( )

Marks all files as not updated.

Useful for getting just one notification from all the files

Definition at line 551 of file ResourceRefreshHandler.cpp.

552 {
553  auto end = UpdatedFiles.end();
554  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter) {
555 
556  (*iter) = false;
557  }
558 }
std::vector< bool > UpdatedFiles
Marks the files that have been updated.

◆ StartListening()

bool Leviathan::ResourceFolderListener::StartListening ( )

Starts a listening thread.

See also
StopThread

Definition at line 193 of file ResourceRefreshHandler.cpp.

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 //
295  std::thread(std::bind(&ResourceFolderListener::_RunListeningThread, this));
296 
297  return true;
298 }
std::thread ListenerThread
The listening thread.
int InotifyID
The ID of our inotify instance thing.
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
char * ReadBuffer
The read result buffer.
bool ShouldQuit
Property set when quitting.
int InotifyWatches
Inotify's folder id which is being monitored.
DLLEXPORT void Error(const std::string &data) override
Definition: Logger.cpp:177

◆ StopThread()

void Leviathan::ResourceFolderListener::StopThread ( )

Sets the internal thread to die and signals it.

Definition at line 300 of file ResourceRefreshHandler.cpp.

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 }
std::thread ListenerThread
The listening thread.
int InotifyID
The ID of our inotify instance thing.
char * ReadBuffer
The read result buffer.
bool ShouldQuit
Property set when quitting.
#define SAFE_DELETE(x)
Definition: Define.h:153
int InotifyWatches
Inotify's folder id which is being monitored.

Member Data Documentation

◆ CallbackFunction

std::function<void (const std::string &, ResourceFolderListener&)> Leviathan::ResourceFolderListener::CallbackFunction
protected

The function called when a change is detected.

Definition at line 74 of file ResourceRefreshHandler.h.

◆ ID

int Leviathan::ResourceFolderListener::ID
protected

ID used to find this specific object.

Definition at line 71 of file ResourceRefreshHandler.h.

◆ InotifyID

int Leviathan::ResourceFolderListener::InotifyID = -1
protected

The ID of our inotify instance thing.

Definition at line 95 of file ResourceRefreshHandler.h.

◆ InotifyWatches

int Leviathan::ResourceFolderListener::InotifyWatches = -1
protected

Inotify's folder id which is being monitored.

Definition at line 98 of file ResourceRefreshHandler.h.

◆ ListenedFiles

std::vector<std::unique_ptr<std::string> > Leviathan::ResourceFolderListener::ListenedFiles
protected

The files which are listened for.

Definition at line 62 of file ResourceRefreshHandler.h.

◆ ListenerThread

std::thread Leviathan::ResourceFolderListener::ListenerThread
protected

The listening thread.

Definition at line 56 of file ResourceRefreshHandler.h.

◆ ReadBuffer

char* Leviathan::ResourceFolderListener::ReadBuffer = nullptr
protected

The read result buffer.

Definition at line 101 of file ResourceRefreshHandler.h.

◆ ShouldQuit

bool Leviathan::ResourceFolderListener::ShouldQuit = false
protected

Property set when quitting.

Definition at line 68 of file ResourceRefreshHandler.h.

◆ TargetFolder

std::string Leviathan::ResourceFolderListener::TargetFolder
protected

The folder in which to listen for stuff.

Definition at line 59 of file ResourceRefreshHandler.h.

◆ UpdatedFiles

std::vector<bool> Leviathan::ResourceFolderListener::UpdatedFiles
protected

Marks the files that have been updated.

Definition at line 65 of file ResourceRefreshHandler.h.


The documentation for this class was generated from the following files: