text: /* +------------------------------------------------------------------------+
     |                     Mobile Robot Programming Toolkit (MRPT)            |
     |                          https://www.mrpt.org/                         |
     |                                                                        |
     | Copyright (c) 2005-2019, Individual contributors, see AUTHORS file     |
     | See: https://www.mrpt.org/Authors - All rights reserved.               |
     | Released under BSD License. See: https://www.mrpt.org/License          |
     +------------------------------------------------------------------------+ */
  
  #include "system-precomp.h"  // Precompiled headers
  
  #include <mrpt/config.h>
  
  #ifdef _WIN32
  #ifndef _WIN32_WINNT
  #define _WIN32_WINNT 0x0400
  #endif
  #include <windows.h>
  #else
  #if MRPT_HAS_INOTIFY
  #include <sys/inotify.h>
  #endif
  
  //	#include <time.h>
  #include <unistd.h>
  #include <cerrno>
  #include <cstdio>
  #endif
  
  #include <mrpt/core/exceptions.h>  // for THROW_EXCEPTION, ASSERT_
  #include <mrpt/system/CFileSystemWatcher.h>
  #include <mrpt/system/filesystem.h>  // for directoryExists()
  #include <cstring>  // for NULL, memcpy
  
  using namespace mrpt::system;
  using namespace std;
  
  /*---------------------------------------------------------------
  					Constructor
   ---------------------------------------------------------------*/
  CFileSystemWatcher::CFileSystemWatcher(const std::string& path)
  	: m_watchedDirectory(path)
  {
  	MRPT_START
  	ASSERT_(!path.empty());
  
  	if (m_watchedDirectory[m_watchedDirectory.size() - 1] != '/' &&
  		m_watchedDirectory[m_watchedDirectory.size() - 1] != '\\')
  		m_watchedDirectory.push_back('/');
  
  #ifdef _WIN32
  	// Windows version:
  	HANDLE hDir = CreateFileA(
  		path.c_str(), FILE_LIST_DIRECTORY,
  		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  		nullptr,  // security attributes
  		OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
  	if (hDir == INVALID_HANDLE_VALUE)
  	{
  		m_hNotif = nullptr;
  		THROW_EXCEPTION("FindFirstChangeNotificationA returned error!");
  	}
  
  	m_hNotif = static_cast<void*>(hDir);
  
  	m_watchThread = std::thread(&CFileSystemWatcher::thread_win32_watch, this);
  
  #else
  #if MRPT_HAS_INOTIFY
  	// Linux version:
  	m_wd = -1;
  
  	m_fd = inotify_init();
  	if (m_fd < 0) THROW_EXCEPTION("inotify_init returned error!");
  
  	// Create watcher:
  	m_wd = inotify_add_watch(
  		m_fd, path.c_str(),
  		IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM | IN_CREATE |
  			IN_ACCESS);
  
  	if (m_wd < 0) THROW_EXCEPTION("inotify_add_watch returned error!");
  #endif
  #endif
  	MRPT_END
  }
  
  /*---------------------------------------------------------------
  					Destructor
   ---------------------------------------------------------------*/
  CFileSystemWatcher::~CFileSystemWatcher()
  {
  #ifdef _WIN32
  	// Windows version:
  	if (m_hNotif)
  	{
  		// Kill thread:
  		CloseHandle(HANDLE(m_hNotif));
  		m_hNotif = nullptr;
  	}
  #else
  #if MRPT_HAS_INOTIFY
  	// Linux version:
  	if (m_fd >= 0)
  	{
  		close(m_fd);
  		m_fd = -1;
  		if (m_wd >= 0) inotify_rm_watch(m_fd, m_wd);
  	}
  #endif
  #endif
  }
  
  /*---------------------------------------------------------------
  					getChanges
   ---------------------------------------------------------------*/
  void CFileSystemWatcher::getChanges(TFileSystemChangeList& out_list)
  {
  	out_list.clear();
  
  #ifdef _WIN32
  	// Windows version:
  	ASSERTMSG_(
  		m_hNotif != nullptr,
  		"CFileSystemWatcher was not initialized correctly.");
  
  	// Work is done in thread_win32_watch().
  	// Just check for incoming mail:
  	while (!m_queue_events_win32_msgs.empty())
  	{
  		TFileSystemChange* obj = nullptr;
  		{
  			std::lock_guard<std::mutex> lock(m_queue_events_win32_cs);
  			if (!m_queue_events_win32_msgs.empty())
  			{
  				auto ret = m_queue_events_win32_msgs.front();
  				m_queue_events_win32_msgs.pop();
  				obj = ret;
  			}
  		}
  		if (obj)
  		{
  			out_list.push_back(*obj);
  			delete obj;
  		}
  	}
  
  #else
  #if MRPT_HAS_INOTIFY
  	if (m_fd < 0) return;  // Not open?
  
  	// Linux version:
  	// Refer to:
  	//  http://www.linuxjournal.com/article/8478
  	//  http://inotify.aiken.cz/?section=common&page=home&lang=en
  	struct timeval time
  	{
  	};
  	fd_set rfds;
  	int ret;
  
  	// timeout
  	time.tv_sec = 0;
  	time.tv_usec = 100;
  
  	// zero-out the fd_set
  	FD_ZERO(&rfds);
  
  	// Add inotify fd
  	FD_SET(m_fd, &rfds);
  
  	ret = select(m_fd + 1, &rfds, nullptr, nullptr, &time);
  	if (ret < 0)
  	{
  		perror("[CFileSystemWatcher::getChanges] select");
  		return;
  	}
  
  	else if (!ret)
  	{
  		// timed out!
  	}
  	else if (FD_ISSET(m_fd, &rfds))
  	{
  // inotify events are available! : Read them!
  
  /* size of the event structure, not counting name */
  #define EVENT_SIZE (sizeof(struct inotify_event))
  
  /* reasonable guess as to size of 1024 events */
  #define BUF_LEN (1024 * (EVENT_SIZE + 16))
  
  		char buf[BUF_LEN];
  		int len, i = 0;
  
  		len = read(m_fd, buf, BUF_LEN);
  		if (len < 0)
  		{
  			if (errno == EINTR)
  			{
  				/* need to reissue system call */
  			}
  			else
  				perror("[CFileSystemWatcher::getChanges] read");
  		}
  		else if (!len)
  		{
  			/* BUF_LEN too small? */
  		}
  
  		while (i < len)
  		{
  			struct inotify_event event_val
  			{
  			};
  			::memcpy(
  				&event_val, &buf[i],
  				sizeof(event_val));  // Was: event = (struct inotify_event *) ;
  			struct inotify_event* event = &event_val;
  
  			i += EVENT_SIZE + event->len;
  
  			//			printf ("wd=%d mask=%u cookie=%u len=%u\n",event->wd,
  			// event->mask,event->cookie, event->len);
  
  			string eventName;
  			if (event->len) eventName = event->name;
  
  			// Add event to output list:
  			// ---------------------------------------------
  			if (0 == (event->mask & IN_UNMOUNT) &&
  				0 == (event->mask & IN_Q_OVERFLOW) &&
  				0 == (event->mask & IN_IGNORED))
  			{
  				TFileSystemChange newEntry;
  
  				newEntry.path = m_watchedDirectory + eventName;
  				newEntry.isDir = event->mask & IN_ISDIR;
  				newEntry.eventModified = event->mask & IN_MODIFY;
  				newEntry.eventCloseWrite = event->mask & IN_CLOSE_WRITE;
  				newEntry.eventDeleted = event->mask & IN_DELETE;
  				newEntry.eventMovedTo = event->mask & IN_MOVED_TO;
  				newEntry.eventMovedFrom = event->mask & IN_MOVED_FROM;
  				newEntry.eventCreated = event->mask & IN_CREATE;
  				newEntry.eventAccessed = event->mask & IN_ACCESS;
  
  				out_list.push_back(newEntry);
  			}
  		}
  	}
  #endif
  #endif
  }
  
  #ifdef _WIN32
  
  void CFileSystemWatcher::thread_win32_watch()
  {
  	uint8_t buf[8 * 1024];
  	DWORD dwRead = 0;
  
  	while (ReadDirectoryChangesW(
  		HANDLE(m_hNotif), buf, sizeof(buf),
  		false,  // No subtree
  		FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
  			FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
  			FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS |
  			FILE_NOTIFY_CHANGE_CREATION,
  		&dwRead, nullptr, nullptr))
  	{
  		// Interpret read data as FILE_NOTIFY_INFORMATION:
  		// There might be several notifications in the same data block:
  		size_t idx = 0;
  		for (;;)
  		{
  			// Yep... this is MS's idea of a beautiful and easy way to return
  			// data:
  			FILE_NOTIFY_INFORMATION* fni =
  				reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buf[idx]);
  
  			// Extract the name (stored as a WCHAR*) into a UTF-8 std::string:
  			ASSERTMSG_(
  				fni->FileNameLength < 10000,
  				"Name length >10K... this is probably an error");
  
  			int reqLen = WideCharToMultiByte(
  				CP_UTF8, 0, fni->FileName, fni->FileNameLength >> 1, nullptr, 0,
  				nullptr, nullptr);
  			std::vector<char> tmpBuf(reqLen);
  			int actLen = WideCharToMultiByte(
  				CP_UTF8, 0, fni->FileName, fni->FileNameLength >> 1, &tmpBuf[0],
  				tmpBuf.size(), nullptr, nullptr);
  			ASSERTMSG_(
  				actLen > 0, "Error converting filename from WCHAR* to UTF8");
  
  			const std::string filName(&tmpBuf[0], actLen);
  
  			TFileSystemChange newEntry;
  			newEntry.path = m_watchedDirectory + filName;
  			newEntry.isDir = mrpt::system::directoryExists(newEntry.path);
  
  			// Fill out the new entry:
  			std::lock_guard<std::mutex> lock(m_queue_events_win32_cs);
  			switch (fni->Action)
  			{
  				case FILE_ACTION_ADDED:
  				{
  					newEntry.eventCreated = true;
  					m_queue_events_win32_msgs.push(
  						new TFileSystemChange(newEntry));
  				}
  				break;
  				case FILE_ACTION_REMOVED:
  				{
  					newEntry.eventDeleted = true;
  					m_queue_events_win32_msgs.push(
  						new TFileSystemChange(newEntry));
  				}
  				break;
  				case FILE_ACTION_MODIFIED:
  				{
  					newEntry.eventModified = true;
  					m_queue_events_win32_msgs.push(
  						new TFileSystemChange(newEntry));
  				}
  				break;
  				case FILE_ACTION_RENAMED_OLD_NAME:
  				{
  					newEntry.eventMovedFrom = true;
  					m_queue_events_win32_msgs.push(
  						new TFileSystemChange(newEntry));
  				}
  				break;
  				case FILE_ACTION_RENAMED_NEW_NAME:
  				{
  					newEntry.eventMovedTo = true;
  					m_queue_events_win32_msgs.push(
  						new TFileSystemChange(newEntry));
  				}
  				break;
  			}
  
  			// Next entry?
  			if (fni->NextEntryOffset > 0)
  				idx += fni->NextEntryOffset;
  			else
  				break;  // done
  		}
  	}
  
  	printf("Done!");
  }
  #endif
  