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 141 of file ResourceRefreshHandler.cpp.

143  :
144  ListenedFiles(filestowatch.size()),
145  UpdatedFiles(filestowatch.size(), false),
146  ID(IDFactory::GetID()), CallbackFunction(notifyfunction)
147 {
148 #ifdef _WIN32
149  // Avoid having to re-allocate the vector later //
150  SignalingHandles.reserve(1+1);
151 
152 #else
153 
154  InotifyID = inotify_init();
155 
156  if(InotifyID < -1){
157 
158  Logger::Get()->Error("ResourceRefreshHandler: ResourceFolderListener: "
159  "failed to create inotify instance");
160  return;
161  }
162 
163 #endif //_WIN32
164 
165  // Copy the target files //
166  for(size_t i = 0; i < ListenedFiles.size(); i++){
167 
168  // Get the folder on the first loop //
169  if(i == 0){
170 
171 
172  TargetFolder = StringOperations::GetPath<std::string>(*filestowatch[i]);
173  }
174 
175  ListenedFiles[i] = make_unique<std::string>(
176  StringOperations::RemovePath<std::string>(*filestowatch[i]));
177  }
178 
179 }
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 181 of file ResourceRefreshHandler.cpp.

181  {
182  LEVIATHAN_ASSERT(ShouldQuit,
183  "ResourceFolderListener should have been stopped before destructor");
184 }
bool ShouldQuit
Property set when quitting.

Member Function Documentation

◆ _RunListeningThread()

void Leviathan::ResourceFolderListener::_RunListeningThread ( )
protected

Definition at line 340 of file ResourceRefreshHandler.cpp.

340  {
341  // Run until quit is requested //
342  while(!ShouldQuit){
343 
344 #ifdef _WIN32
345 
346  // Wait for the handles //
347  DWORD waitstatus = WaitForMultipleObjects(static_cast<DWORD>(SignalingHandles.size()), &SignalingHandles[0],
348  FALSE, INFINITE);
349 
350  // Check what happened //
351  switch(waitstatus){
352  case WAIT_OBJECT_0:
353 
354  // Quit has been called //
355  return;
356 
357  case WAIT_OBJECT_0 + 1:
358  {
359  // A modification has been detected //
360 
361  // Check what is detected //
362  FILE_NOTIFY_INFORMATION* dataptr = OurReadBuffer;
363 
364  DWORD numread;
365 
366  GetOverlappedResult(TargetFolderHandle, OverlappedInfo, &numread, FALSE);
367 
368  if(numread < 1){
369  // Nothing read/failed //
370  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: result "
371  "has 0 bytes: ");
372  continue;
373  }
374 
375  bool working = true;
376 
377  while(working){
378  // Check what the notification is //
379  if(dataptr->Action == FILE_ACTION_REMOVED ||
380  dataptr->Action == FILE_ACTION_RENAMED_OLD_NAME){
381 
382  goto movetonextdatalabel;
383  }
384 
385  {
386  // Get the filename //
387  std::wstring entrydata;
388 
389  size_t filenameinwchars = dataptr->FileNameLength/sizeof(wchar_t);
390 
391  entrydata.resize(filenameinwchars);
392 
393  // Copy the data //
394  memcpy_s(&entrydata[0], entrydata.size()*sizeof(wchar_t),
395  dataptr->FileName, dataptr->FileNameLength);
396 
397  std::string utf8file = Convert::Utf16ToUtf8(entrydata);
398 
399  // Skip if nothing //
400  if(utf8file.empty()){
401 
402  goto movetonextdatalabel;
403  }
404 
405 
406  // Check which file matches and set it as updated //
407  for(size_t i = 0; i < ListenedFiles.size(); i++){
408 
409  if(*ListenedFiles[i] == utf8file){
410 
411  // Updated //
412  UpdatedFiles[i] = true;
413  break;
414  }
415  }
416  }
417 movetonextdatalabel:
418 
419  if(!dataptr->NextEntryOffset){
420 
421  // No more entries
422  working = false;
423  break;
424  } else {
425  // Move to next entry //
426 
427  char* tmpptr = reinterpret_cast<char*>(dataptr);
428  tmpptr += dataptr->NextEntryOffset;
429 
430  dataptr = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(tmpptr);
431  }
432  }
433 
434  // Start listening again //
435  BOOL createresult = ReadDirectoryChangesW(TargetFolderHandle, OurReadBuffer,
436  sizeof(FILE_NOTIFY_INFORMATION)*100,
437  // Only the top level directory is watched
438  FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, OverlappedInfo, NULL);
439 
440  if(!createresult){
441 
442  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: "
443  "re-queuing file change read failed: "+
444  Convert::ToHexadecimalString(GetLastError()));
445  return;
446  }
447 
448  }
449  break;
450 
451  case WAIT_TIMEOUT:
452  // NO such thing, continue!
453  continue;
454 
455  default:
456  Logger::Get()->Error("ResourceFolderListener: _RunListeningThread: invalid wait "
457  "result: "+Convert::ToString(waitstatus));
458  break;
459  }
460 
461 #else
462 
463  // Read the changes until the end of time //
464  int readcount = read(InotifyID, ReadBuffer, IN_READ_BUFFER_SIZE);
465 
466  if(readcount < 0){
467 
468  // Failed reading, quit //
469  Logger::Get()->Warning("ResourceFolderListener: read failed, quitting thread");
470  return;
471  }
472 
473  // Handle all the data //
474  for(int i = 0; i < readcount; ){
475 
476  // Break if invalid buffer //
477  if(!ReadBuffer)
478  return;
479 
480  inotify_event* event = reinterpret_cast<inotify_event*>(&ReadBuffer[i]);
481 
482  if(event->len){
483 
484  if(event->mask & IN_MODIFY){
485  if(!(event->mask & IN_ISDIR)){
486 
487  // Some file was modified, check was it one of ours //
488  const string modifiedfile(event->name, event->len);
489 
490  if(!modifiedfile.empty()){
491 
492  // Check which file matches and set it as updated //
493  for(size_t i = 0; i < ListenedFiles.size(); i++){
494 
495  if(*ListenedFiles[i] == modifiedfile){
496 
497  // Updated //
498  UpdatedFiles[i] = true;
499  break;
500  }
501  }
502  }
503  }
504  }
505  }
506 
507  i += IN_EVENT_SIZE+event->len;
508  }
509 
510 
511 
512 #endif //_Win32
513  }
514 }
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 516 of file ResourceRefreshHandler.cpp.

516  {
517  // Check are some updated files readable //
518  for(size_t i = 0; i < UpdatedFiles.size(); i++){
519 
520  if(UpdatedFiles[i]){
521 
522  // Check is it readable //
523  const std::string checkread = TargetFolder+*ListenedFiles[i];
524 
525  ifstream reader(checkread);
526 
527  if(reader.is_open()){
528 
529  reader.close();
530 
531  // Notify that the file is now available //
532  CallbackFunction(*ListenedFiles[i], *this);
533  UpdatedFiles[i] = false;
534  }
535  }
536  }
537 }
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 186 of file ResourceRefreshHandler.cpp.

186  {
187  return ID;
188 }
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 547 of file ResourceRefreshHandler.cpp.

547  {
548  // Try to find a set bool //
549  auto end = UpdatedFiles.end();
550  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter){
551 
552  if((*iter)){
553 
554  return true;
555  }
556  }
557 
558  // No file is marked as updated //
559  return false;
560 }
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 539 of file ResourceRefreshHandler.cpp.

539  {
540  auto end = UpdatedFiles.end();
541  for(auto iter = UpdatedFiles.begin(); iter != end; ++iter){
542 
543  (*iter) = false;
544  }
545 }
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 190 of file ResourceRefreshHandler.cpp.

190  {
191 
192 #ifdef _WIN32
193  // First create the stop signaler //
194  HANDLE ourstopper = CreateEvent(NULL, FALSE, FALSE, NULL);
195 
196  if(!ourstopper){
197 
198  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to create stop "
199  "notify handle, CreateEvent failed");
200  return false;
201  }
202 
203 
204  SignalingHandles.push_back(ourstopper);
205 
206  // Now the folder listener //
207  TargetFolderHandle = CreateFileA(TargetFolder.c_str(), FILE_READ_DATA | FILE_TRAVERSE |
208  FILE_READ_EA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
209  NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
210 
211  if(TargetFolderHandle == INVALID_HANDLE_VALUE || !TargetFolderHandle){
212 
213  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to open folder for "
214  "reading, error: "+Convert::ToHexadecimalString(GetLastError()));
215  return false;
216  }
217 
218 
219  // Create the OVERLAPPED struct next //
220  OverlappedInfo = new OVERLAPPED;
221 
222  ZeroMemory(OverlappedInfo, sizeof(OVERLAPPED));
223 
224  // Create an event for notification //
225  HANDLE readcompleteevent = CreateEvent(NULL, FALSE, FALSE, NULL);
226 
227  if(!readcompleteevent){
228 
229  Logger::Get()->Error("ResourceFolderListener: StartListening: "
230  "failed to create read notify handle, CreateEvent failed, error: " +
231  Convert::ToHexadecimalString(GetLastError()));
232  return false;
233  }
234 
235  // Add it to the overlapped //
236  OverlappedInfo->hEvent = readcompleteevent;
237 
238 
239 
240  OurReadBuffer = new FILE_NOTIFY_INFORMATION[100];
241 
242 
243  // Create the update notification read thing //
244  BOOL createresult = ReadDirectoryChangesW(TargetFolderHandle, OurReadBuffer,
245  sizeof(FILE_NOTIFY_INFORMATION)*100,
246  // Only the top level directory is watched
247  FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, OverlappedInfo, NULL);
248 
249  if(!createresult){
250 
251  CloseHandle(TargetFolderHandle);
252  Logger::Get()->Error("ResourceFolderListener: StartListening: failed to start reading "
253  "directory changes, error: "+Convert::ToHexadecimalString(GetLastError()));
254  return false;
255  }
256 
257  SignalingHandles.push_back(readcompleteevent);
258 
259 #else
260 
261  // Create watches for all of them //
262 
263  InotifyWatches = inotify_add_watch(InotifyID, TargetFolder.c_str(), IN_MODIFY);
264 
265 
266  if(InotifyWatches < 0){
267 
268 
269  Logger::Get()->Error("ResourceRefreshHandler: ResourceFolderListener: failed to add "
270  "watch for folder: "+TargetFolder);
271  return false;
272  }
273 
274 
275  // Allocate the read buffer //
276  ReadBuffer = new char[IN_READ_BUFFER_SIZE];
277 
278 #endif //_WIN32
279 
280 
281  ShouldQuit = false;
282 
283  // Finally start the thread //
285  this));
286 
287  return true;
288 }
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&#39;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 290 of file ResourceRefreshHandler.cpp.

290  {
291 #ifdef _WIN32
292  // Don't do anything if already done //
293  if(ShouldQuit && SignalingHandles.empty())
294  return;
295 
296  ShouldQuit = true;
297 
298  // Signal the close handle //
299  SetEvent(SignalingHandles[0]);
300 
301  // Join the thread to wait for it to quit //
302  ListenerThread.join();
303 
304  // Close the handles //
305  CloseHandle(SignalingHandles[0]);
306  CloseHandle(SignalingHandles[1]);
307  CloseHandle(TargetFolderHandle);
308 
309  SignalingHandles.clear();
310 
311  if(OurReadBuffer){
312  delete[] OurReadBuffer;
313  OurReadBuffer = NULL;
314  }
315 
316  SAFE_DELETE(OverlappedInfo);
317 #else
318 
319  if(ShouldQuit && InotifyID == -1)
320  return;
321 
322  ShouldQuit = true;
323 
324  inotify_rm_watch(InotifyID, InotifyWatches);
325  close(InotifyID);
326 
327  ListenerThread.join();
328 
329  if(ReadBuffer){
330  delete[] ReadBuffer;
331  ReadBuffer = NULL;
332  }
333 
334  InotifyID = -1;
335  InotifyWatches = -1;
336 
337 #endif //_WIN32
338 }
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:116
int InotifyWatches
Inotify&#39;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: