fswatcher.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /////////////////////////////////////////////////////////////////////////////
  2. // Name: wx/msw/private/fswatcher.h
  3. // Purpose: File system watcher impl classes
  4. // Author: Bartosz Bekier
  5. // Created: 2009-05-26
  6. // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
  7. // Licence: wxWindows licence
  8. /////////////////////////////////////////////////////////////////////////////
  9. #ifndef WX_MSW_PRIVATE_FSWATCHER_H_
  10. #define WX_MSW_PRIVATE_FSWATCHER_H_
  11. #include "wx/filename.h"
  12. #include "wx/vector.h"
  13. #include "wx/msw/private.h"
  14. // ============================================================================
  15. // wxFSWatcherEntry implementation & helper declarations
  16. // ============================================================================
  17. class wxFSWatcherImplMSW;
  18. class wxFSWatchEntryMSW : public wxFSWatchInfo
  19. {
  20. public:
  21. enum
  22. {
  23. BUFFER_SIZE = 4096 // TODO parametrize
  24. };
  25. wxFSWatchEntryMSW(const wxFSWatchInfo& winfo) :
  26. wxFSWatchInfo(winfo)
  27. {
  28. // get handle for this path
  29. m_handle = OpenDir(m_path);
  30. m_overlapped = (OVERLAPPED*)calloc(1, sizeof(OVERLAPPED));
  31. wxZeroMemory(m_buffer);
  32. }
  33. virtual ~wxFSWatchEntryMSW()
  34. {
  35. wxLogTrace(wxTRACE_FSWATCHER, "Deleting entry '%s'", m_path);
  36. if (m_handle != INVALID_HANDLE_VALUE)
  37. {
  38. if (!CloseHandle(m_handle))
  39. {
  40. wxLogSysError(_("Unable to close the handle for '%s'"),
  41. m_path);
  42. }
  43. }
  44. free(m_overlapped);
  45. }
  46. bool IsOk() const
  47. {
  48. return m_handle != INVALID_HANDLE_VALUE;
  49. }
  50. HANDLE GetHandle() const
  51. {
  52. return m_handle;
  53. }
  54. void* GetBuffer()
  55. {
  56. return m_buffer;
  57. }
  58. OVERLAPPED* GetOverlapped() const
  59. {
  60. return m_overlapped;
  61. }
  62. private:
  63. // opens dir with all flags, attributes etc. necessary to be later
  64. // asynchronous watched with ReadDirectoryChangesW
  65. static HANDLE OpenDir(const wxString& path)
  66. {
  67. HANDLE handle = CreateFile(path.t_str(),
  68. FILE_LIST_DIRECTORY,
  69. FILE_SHARE_READ |
  70. FILE_SHARE_WRITE |
  71. FILE_SHARE_DELETE,
  72. NULL,
  73. OPEN_EXISTING,
  74. FILE_FLAG_BACKUP_SEMANTICS |
  75. FILE_FLAG_OVERLAPPED,
  76. NULL);
  77. if (handle == INVALID_HANDLE_VALUE)
  78. {
  79. wxLogSysError(_("Failed to open directory \"%s\" for monitoring."),
  80. path);
  81. }
  82. return handle;
  83. }
  84. HANDLE m_handle; // handle to opened directory
  85. char m_buffer[BUFFER_SIZE]; // buffer for fs events
  86. OVERLAPPED* m_overlapped;
  87. wxDECLARE_NO_COPY_CLASS(wxFSWatchEntryMSW);
  88. };
  89. // ============================================================================
  90. // wxFSWatcherImplMSW helper classes implementations
  91. // ============================================================================
  92. class wxIOCPService
  93. {
  94. public:
  95. wxIOCPService() :
  96. m_iocp(INVALID_HANDLE_VALUE)
  97. {
  98. Init();
  99. }
  100. ~wxIOCPService()
  101. {
  102. if (m_iocp != INVALID_HANDLE_VALUE)
  103. {
  104. if (!CloseHandle(m_iocp))
  105. {
  106. wxLogSysError(_("Unable to close I/O completion port handle"));
  107. }
  108. }
  109. m_watches.clear();
  110. }
  111. // associates a wxFSWatchEntryMSW with completion port
  112. bool Add(wxSharedPtr<wxFSWatchEntryMSW> watch)
  113. {
  114. wxCHECK_MSG( m_iocp != INVALID_HANDLE_VALUE, false, "IOCP not init" );
  115. wxCHECK_MSG( watch->IsOk(), false, "Invalid watch" );
  116. // associate with IOCP
  117. HANDLE ret = CreateIoCompletionPort(watch->GetHandle(), m_iocp,
  118. (ULONG_PTR)watch.get(), 0);
  119. if (ret == NULL)
  120. {
  121. wxLogSysError(_("Unable to associate handle with "
  122. "I/O completion port"));
  123. return false;
  124. }
  125. else if (ret != m_iocp)
  126. {
  127. wxFAIL_MSG(_("Unexpectedly new I/O completion port was created"));
  128. return false;
  129. }
  130. // add to watch map
  131. wxFSWatchEntries::value_type val(watch->GetPath(), watch);
  132. return m_watches.insert(val).second;
  133. }
  134. // Removes a watch we're currently using. Notice that this doesn't happen
  135. // immediately, CompleteRemoval() must be called later when it's really
  136. // safe to delete the watch, i.e. after completion of the IO operation
  137. // using it.
  138. bool ScheduleForRemoval(const wxSharedPtr<wxFSWatchEntryMSW>& watch)
  139. {
  140. wxCHECK_MSG( m_iocp != INVALID_HANDLE_VALUE, false, "IOCP not init" );
  141. wxCHECK_MSG( watch->IsOk(), false, "Invalid watch" );
  142. const wxString path = watch->GetPath();
  143. wxFSWatchEntries::iterator it = m_watches.find(path);
  144. wxCHECK_MSG( it != m_watches.end(), false,
  145. "Can't remove a watch we don't use" );
  146. // We can't just delete the watch here as we can have pending events
  147. // for it and if we destroyed it now, we could get a dangling (or,
  148. // worse, reused to point to another object) pointer in ReadEvents() so
  149. // just remember that this one should be removed when CompleteRemoval()
  150. // is called later.
  151. m_removedWatches.push_back(watch);
  152. m_watches.erase(it);
  153. return true;
  154. }
  155. // Really remove the watch previously passed to ScheduleForRemoval().
  156. //
  157. // It's ok to call this for a watch that hadn't been removed before, in
  158. // this case we'll just return false and do nothing.
  159. bool CompleteRemoval(wxFSWatchEntryMSW* watch)
  160. {
  161. for ( Watches::iterator it = m_removedWatches.begin();
  162. it != m_removedWatches.end();
  163. ++it )
  164. {
  165. if ( (*it).get() == watch )
  166. {
  167. // Removing the object from here will result in deleting the
  168. // watch itself as it's not referenced from anywhere else now.
  169. m_removedWatches.erase(it);
  170. return true;
  171. }
  172. }
  173. return false;
  174. }
  175. // post completion packet
  176. bool PostEmptyStatus()
  177. {
  178. wxCHECK_MSG( m_iocp != INVALID_HANDLE_VALUE, false, "IOCP not init" );
  179. // The special values of 0 will make GetStatus() return Status_Exit.
  180. int ret = PostQueuedCompletionStatus(m_iocp, 0, 0, NULL);
  181. if (!ret)
  182. {
  183. wxLogSysError(_("Unable to post completion status"));
  184. }
  185. return ret != 0;
  186. }
  187. // Possible return values of GetStatus()
  188. enum Status
  189. {
  190. // Status successfully retrieved into the provided arguments.
  191. Status_OK,
  192. // Special status indicating that we should exit retrieved.
  193. Status_Exit,
  194. // An error occurred because the watched directory was deleted.
  195. Status_Deleted,
  196. // Some other error occurred.
  197. Status_Error
  198. };
  199. // Wait for completion status to arrive.
  200. // This function can block forever in it's wait for completion status.
  201. // Use PostEmptyStatus() to wake it up (and end the worker thread)
  202. Status
  203. GetStatus(DWORD* count, wxFSWatchEntryMSW** watch,
  204. OVERLAPPED** overlapped)
  205. {
  206. wxCHECK_MSG( m_iocp != INVALID_HANDLE_VALUE, Status_Error,
  207. "Invalid IOCP object" );
  208. wxCHECK_MSG( count && watch && overlapped, Status_Error,
  209. "Output parameters can't be NULL" );
  210. int ret = GetQueuedCompletionStatus(m_iocp, count, (ULONG_PTR *)watch,
  211. overlapped, INFINITE);
  212. if ( ret != 0 )
  213. {
  214. return *count || *watch || *overlapped ? Status_OK : Status_Exit;
  215. }
  216. // An error is returned if the underlying directory has been deleted,
  217. // but this is not really an unexpected failure, so handle it
  218. // specially.
  219. if ( wxSysErrorCode() == ERROR_ACCESS_DENIED &&
  220. *watch && !wxFileName::DirExists((*watch)->GetPath()) )
  221. return Status_Deleted;
  222. // Some other error, at least log it.
  223. wxLogSysError(_("Unable to dequeue completion packet"));
  224. return Status_Error;
  225. }
  226. protected:
  227. bool Init()
  228. {
  229. m_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
  230. if (m_iocp == NULL)
  231. {
  232. wxLogSysError(_("Unable to create I/O completion port"));
  233. }
  234. return m_iocp != NULL;
  235. }
  236. HANDLE m_iocp;
  237. // The hash containing all the wxFSWatchEntryMSW objects currently being
  238. // watched keyed by their paths.
  239. wxFSWatchEntries m_watches;
  240. // Contains the watches which had been removed but are still pending.
  241. typedef wxVector< wxSharedPtr<wxFSWatchEntryMSW> > Watches;
  242. Watches m_removedWatches;
  243. };
  244. class wxIOCPThread : public wxThread
  245. {
  246. public:
  247. wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp);
  248. // finishes this thread
  249. bool Finish();
  250. protected:
  251. // structure to hold information needed to process one native event
  252. // this is just a dummy holder, so it doesn't take ownership of it's data
  253. struct wxEventProcessingData
  254. {
  255. wxEventProcessingData(const FILE_NOTIFY_INFORMATION* ne,
  256. const wxFSWatchEntryMSW* watch) :
  257. nativeEvent(ne), watch(watch)
  258. {}
  259. const FILE_NOTIFY_INFORMATION* nativeEvent;
  260. const wxFSWatchEntryMSW* watch;
  261. };
  262. virtual ExitCode Entry();
  263. // wait for events to occur, read them and send to interested parties
  264. // returns false it empty status was read, which means we whould exit
  265. // true otherwise
  266. bool ReadEvents();
  267. void ProcessNativeEvents(wxVector<wxEventProcessingData>& events);
  268. void SendEvent(wxFileSystemWatcherEvent& evt);
  269. static int Native2WatcherFlags(int flags);
  270. static wxString FileNotifyInformationToString(
  271. const FILE_NOTIFY_INFORMATION& e);
  272. static wxFileName GetEventPath(const wxFSWatchEntryMSW& watch,
  273. const FILE_NOTIFY_INFORMATION& e);
  274. wxFSWatcherImplMSW* m_service;
  275. wxIOCPService* m_iocp;
  276. };
  277. #endif /* WX_MSW_PRIVATE_FSWATCHER_H_ */